From fc23bb30e55d74a1fd0c2b4a3daf144a65856a12 Mon Sep 17 00:00:00 2001 From: Jason Kuster Date: Thu, 21 Apr 2016 10:55:42 -0700 Subject: [PATCH 1/5] Initial mergebot commit. Signed-off-by: Jason Kuster --- .travis.yml | 2 +- {travis => tools}/README.md | 0 tools/mergebot/mergebot.py | 123 ++++++++++++++++++ .../travis_wordcount.sh | 0 4 files changed, 124 insertions(+), 1 deletion(-) rename {travis => tools}/README.md (100%) create mode 100644 tools/mergebot/mergebot.py rename travis/test_wordcount.sh => tools/travis_wordcount.sh (100%) diff --git a/.travis.yml b/.travis.yml index f1d9e3bf1afb..7b427a69b9d9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,4 +33,4 @@ install: script: - travis_retry mvn -B $MAVEN_OVERRIDE install -U - - travis_retry travis/test_wordcount.sh + - travis_retry tools/travis_wordcount.sh diff --git a/travis/README.md b/tools/README.md similarity index 100% rename from travis/README.md rename to tools/README.md diff --git a/tools/mergebot/mergebot.py b/tools/mergebot/mergebot.py new file mode 100644 index 000000000000..09e60e9b8ade --- /dev/null +++ b/tools/mergebot/mergebot.py @@ -0,0 +1,123 @@ +"""TODO(jasonkuster): DO NOT SUBMIT without one-line documentation for mergebot. + +TODO(jasonkuster): DO NOT SUBMIT without a detailed description of mergebot. +""" +import requests +import time + +AUTHORIZED_USERS = ["davor"] +BOT_NAME = 'beam-testing' +GITHUB_API_ROOT = 'https://api.github.com' +GITHUB_ORG = 'apache' +GITHUB_PROJ = 'incubator-beam' +GITHUB_REPO_FMT_URL = GITHUB_API_ROOT + '/repos/{0}/{1}' +GITHUB_REPO_URL = GITHUB_REPO_FMT_URL.format(GITHUB_ORG, GITHUB_PROJ) +CMDS = ['merge'] +ISSUES_URL = GITHUB_REPO_URL + '/issues' +COMMENT_FMT_URL = ISSUES_URL + '/{pr_num}/comments' +PULLS_URL = GITHUB_REPO_URL + '/pulls' +SECRET_FILE = '../../github_auth/apache-beam.secret' + +def main(): + print('Starting up.') + # Load github key from filesystem + key_file = open(SECRET_FILE, 'r') + bot_key = key_file.read().strip() + print('Loaded key file.') + # Loop: Forever, once per minute. + while True: + print('Loading pull requests from Github at {}.'.format(PULLS_URL)) + # Load list of pull requests from Github + r = requests.get(PULLS_URL, auth=(BOT_NAME, bot_key)) + if r.status_code != 200: + print('Oops, that didn\'t work. Error below, waiting then trying again.') + print(r.text) + time.sleep(60) + continue + + print('Loaded.') + pr_json = r.json() + # Loop: Each pull request + for pr in pr_json: + pr_num = pr['number'] + print('Looking at PR #{}.'.format(pr_num)) + # Load comments for each pull request + cmt_url = COMMENT_FMT_URL.format(pr_num=pr_num) + print('Loading comments.') + r = requests.get(cmt_url, auth=(BOT_NAME, bot_key)) + if r.status_code != 200: + print('Oops, that didn\'t work. Error below, waiting then trying again.') + print(r.text) + continue + + cmt_json = r.json() + if len(cmt_json) < 1: + print('No comments on PR #{}. Moving on.'.format(pr_num)) + continue + # FUTURE: Loop over comments to make sure PR has been LGTMed + cmt = cmt_json[-1] + cmt_body = cmt['body'].encode('ascii', 'ignore') + # Look for @apache-beam request comments + # FUTURE: Look for @apache-beam reply comments + if not cmt_body.startswith('@apache-beam'): + print('Last comment: {}, not a command. Moving on.'.format(cmt_body)) + continue + cmd_str = cmt_body.split('@apache-beam ', 1)[1] + cmd = cmd_str.split(' ')[0] + if cmd not in CMDS: + # Post back to PR + post_error('Command was {}, not a valid command.'.format(cmd), pr_num) + print('Command was {}, not a valid command.'.format(cmd)) + continue + + if cmd == 'merge': + if cmt['user']['login'] not in AUTHORIZED_USERS: + post_error('Unauthorized users cannot merge: {}'.format(cmt['user']['login'])) + print('Unauthorized user {} attempted to merge PR {}.'.format(cmt['user']['login'], pr_num)) + continue + # Kick off merge workflow + print('Command was merge, merging.') + if merge(pr_num): + post_info('Merge of PR#{} succeeded. Please close this pull request.', pr) + # Clean up + time.sleep(60) + +def merge(pr): + # Make temp directory and cd into. + # Clone repository and configure. + # Rebase PR onto main. + if not rebase_success: + post_error('Rebase was not successful. Please rebase against main and try again.', pr) + return False + + # Check out target branch to here + if not checkout_success: + post_error('Error checking out target branch: master. Please try again.', pr) + return False + + # Merge + if not merge_success: + post_error('Merge was not successful against target branch: master. Please try again.', pr) + return False + + # mvn clean verify + if not mvn_success: + post_error('mvn clean verify against HEAD + PR#{} failed. Not merging.'.format(pr), pr) + return False + + # git push + if not push_success: + post_error('Git push failed. Please try again.', pr) + return False + return True + +def post_error(content, pr_num): + post("ERROR: {}".format(content), COMMENT_FMT_URL.format(pr_num=pr_num)) + +def post(content, endpoint): + payload = {"body": content} + r = requests.post(endpoint, data=payload) + if r. + +if __name__ == "__main__": + main() diff --git a/travis/test_wordcount.sh b/tools/travis_wordcount.sh similarity index 100% rename from travis/test_wordcount.sh rename to tools/travis_wordcount.sh From ef10e98c0f01acb3222804ab95cb585e2402cf2e Mon Sep 17 00:00:00 2001 From: Jason Kuster Date: Thu, 21 Apr 2016 11:02:10 -0700 Subject: [PATCH 2/5] Mergebot initial documentation. Signed-off-by: Jason Kuster --- tools/mergebot/mergebot.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/mergebot/mergebot.py b/tools/mergebot/mergebot.py index 09e60e9b8ade..776913ada375 100644 --- a/tools/mergebot/mergebot.py +++ b/tools/mergebot/mergebot.py @@ -1,6 +1,8 @@ -"""TODO(jasonkuster): DO NOT SUBMIT without one-line documentation for mergebot. +"""Mergebot is a script which talks to GitHub and submits all ready pull requests. -TODO(jasonkuster): DO NOT SUBMIT without a detailed description of mergebot. +Mergebot talks to a specified GitHub project and watches for @mentions for its account. +Acceptable commands are: + @ merge """ import requests import time From d01582dc0aa41e42230213ecec939d71e06edb31 Mon Sep 17 00:00:00 2001 From: Jason Kuster Date: Mon, 25 Apr 2016 16:22:29 -0700 Subject: [PATCH 3/5] tools -> testing Signed-off-by: Jason Kuster --- .travis.yml | 2 +- {tools => testing}/README.md | 0 {tools => testing}/mergebot/mergebot.py | 33 ++++++++++++++++++++++--- {tools => testing}/travis_wordcount.sh | 0 4 files changed, 31 insertions(+), 4 deletions(-) rename {tools => testing}/README.md (100%) rename {tools => testing}/mergebot/mergebot.py (73%) rename {tools => testing}/travis_wordcount.sh (100%) diff --git a/.travis.yml b/.travis.yml index 7b427a69b9d9..513dd131c5e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,4 +33,4 @@ install: script: - travis_retry mvn -B $MAVEN_OVERRIDE install -U - - travis_retry tools/travis_wordcount.sh + - travis_retry testing/travis_wordcount.sh diff --git a/tools/README.md b/testing/README.md similarity index 100% rename from tools/README.md rename to testing/README.md diff --git a/tools/mergebot/mergebot.py b/testing/mergebot/mergebot.py similarity index 73% rename from tools/mergebot/mergebot.py rename to testing/mergebot/mergebot.py index 776913ada375..996b3c58e6c5 100644 --- a/tools/mergebot/mergebot.py +++ b/testing/mergebot/mergebot.py @@ -4,6 +4,7 @@ Acceptable commands are: @ merge """ +from subprocess import call import requests import time @@ -85,29 +86,45 @@ def main(): time.sleep(60) def merge(pr): + if not set_up(): + post_error('Error setting up - please try again.', pr) # Make temp directory and cd into. # Clone repository and configure. + clone_success = call(['git', 'clone', '-b', 'master', 'https://github.com/apache/incubator-beam.git', '~/tmp/'], cwd='~/tmp/') + call(['git', 'remote', 'add', 'apache', 'https://git-wip-us.apache.org/repos/asf/incubator-beam.git'], cwd='~/tmp/') + call(['git', 'remote', 'rename', 'origin', 'github'], cwd='~/tmp/') + call(['git', 'config', '--local', '--add', 'remote.github.fetch', '"+refs/pull/*/head:refs/remotes/${SOURCE_REMOTE}/pr/*"'], cwd='~/tmp/') + call(['git', 'fetch', '--all'], cwd='~/tmp/') + # Clean up fetch + initial_checkout = call(['git', 'checkout', '-b', 'finish-pr-{}'.format(pr), 'github/pr/{}'.format(pr)], cwd='~/tmp/') + if not initial_checkout == 0: + # aah # Rebase PR onto main. - if not rebase_success: + rebase_success = call(['git', 'rebase', 'apache/master'], cwd='~/tmp/') + if not rebase_success == 0: post_error('Rebase was not successful. Please rebase against main and try again.', pr) return False # Check out target branch to here + checkout_success = call(['git', 'checkout', 'apache/master'], cwd='~/tmp/') if not checkout_success: post_error('Error checking out target branch: master. Please try again.', pr) return False # Merge + merge_success = call(['git', 'merge', '--no-ff', '-m', 'This closes #{}'.format(pr), 'finish-pr-{}'.format(pr)], cwd='~/tmp/') if not merge_success: post_error('Merge was not successful against target branch: master. Please try again.', pr) return False # mvn clean verify + mvn_success = call(['mvn', 'clean', 'verify'], cwd='~/tmp/') if not mvn_success: post_error('mvn clean verify against HEAD + PR#{} failed. Not merging.'.format(pr), pr) return False # git push + push_success = call(['git', 'push', 'apache', 'HEAD:master'], cwd='~/tmp/') if not push_success: post_error('Git push failed. Please try again.', pr) return False @@ -118,8 +135,18 @@ def post_error(content, pr_num): def post(content, endpoint): payload = {"body": content} - r = requests.post(endpoint, data=payload) - if r. + requests.post(endpoint, data=payload) + +def set_up(): + if not call(['mkdir', '~/tmp']) == 0: + # call clean up? + return False + return True + +def clean_up(): + if not call(['rm', '-rf', '~/tmp']) == 0: + return False + return True if __name__ == "__main__": main() diff --git a/tools/travis_wordcount.sh b/testing/travis_wordcount.sh similarity index 100% rename from tools/travis_wordcount.sh rename to testing/travis_wordcount.sh From dc042e2b9d0cc5d909e9b064b983d83b88bd96af Mon Sep 17 00:00:00 2001 From: Jason Kuster Date: Mon, 25 Apr 2016 18:07:29 -0700 Subject: [PATCH 4/5] Updates to make mergebot command-line capable. Signed-off-by: Jason Kuster --- testing/mergebot/mergebot.py | 203 +++++++++++++++++++++-------------- 1 file changed, 122 insertions(+), 81 deletions(-) diff --git a/testing/mergebot/mergebot.py b/testing/mergebot/mergebot.py index 996b3c58e6c5..1c56fc19c0b1 100644 --- a/testing/mergebot/mergebot.py +++ b/testing/mergebot/mergebot.py @@ -6,20 +6,26 @@ """ from subprocess import call import requests +import sys import time AUTHORIZED_USERS = ["davor"] BOT_NAME = 'beam-testing' -GITHUB_API_ROOT = 'https://api.github.com' GITHUB_ORG = 'apache' -GITHUB_PROJ = 'incubator-beam' +REPOSITORY = 'incubator-beam' +SECRET_FILE = '../../github_auth/apache-beam.secret' +SOURCE_REMOTE = 'github' +TARGET_BRANCH = 'master' +TARGET_REMOTE = 'apache' + +GITHUB_API_ROOT = 'https://api.github.com' GITHUB_REPO_FMT_URL = GITHUB_API_ROOT + '/repos/{0}/{1}' -GITHUB_REPO_URL = GITHUB_REPO_FMT_URL.format(GITHUB_ORG, GITHUB_PROJ) +GITHUB_REPO_URL = GITHUB_REPO_FMT_URL.format(GITHUB_ORG, REPOSITORY) CMDS = ['merge'] ISSUES_URL = GITHUB_REPO_URL + '/issues' COMMENT_FMT_URL = ISSUES_URL + '/{pr_num}/comments' PULLS_URL = GITHUB_REPO_URL + '/pulls' -SECRET_FILE = '../../github_auth/apache-beam.secret' + def main(): print('Starting up.') @@ -29,124 +35,159 @@ def main(): print('Loaded key file.') # Loop: Forever, once per minute. while True: - print('Loading pull requests from Github at {}.'.format(PULLS_URL)) - # Load list of pull requests from Github - r = requests.get(PULLS_URL, auth=(BOT_NAME, bot_key)) - if r.status_code != 200: - print('Oops, that didn\'t work. Error below, waiting then trying again.') - print(r.text) - time.sleep(60) - continue - - print('Loaded.') - pr_json = r.json() - # Loop: Each pull request - for pr in pr_json: - pr_num = pr['number'] - print('Looking at PR #{}.'.format(pr_num)) - # Load comments for each pull request - cmt_url = COMMENT_FMT_URL.format(pr_num=pr_num) - print('Loading comments.') - r = requests.get(cmt_url, auth=(BOT_NAME, bot_key)) - if r.status_code != 200: - print('Oops, that didn\'t work. Error below, waiting then trying again.') - print(r.text) - continue - - cmt_json = r.json() - if len(cmt_json) < 1: - print('No comments on PR #{}. Moving on.'.format(pr_num)) - continue - # FUTURE: Loop over comments to make sure PR has been LGTMed - cmt = cmt_json[-1] - cmt_body = cmt['body'].encode('ascii', 'ignore') - # Look for @apache-beam request comments - # FUTURE: Look for @apache-beam reply comments - if not cmt_body.startswith('@apache-beam'): - print('Last comment: {}, not a command. Moving on.'.format(cmt_body)) - continue - cmd_str = cmt_body.split('@apache-beam ', 1)[1] - cmd = cmd_str.split(' ')[0] - if cmd not in CMDS: - # Post back to PR - post_error('Command was {}, not a valid command.'.format(cmd), pr_num) - print('Command was {}, not a valid command.'.format(cmd)) - continue - - if cmd == 'merge': - if cmt['user']['login'] not in AUTHORIZED_USERS: - post_error('Unauthorized users cannot merge: {}'.format(cmt['user']['login'])) - print('Unauthorized user {} attempted to merge PR {}.'.format(cmt['user']['login'], pr_num)) - continue - # Kick off merge workflow - print('Command was merge, merging.') - if merge(pr_num): - post_info('Merge of PR#{} succeeded. Please close this pull request.', pr) - # Clean up + poll_github(bot_key) time.sleep(60) +def poll_github(bot_key): + print('Loading pull requests from Github at {}.'.format(PULLS_URL)) + # Load list of pull requests from Github + r = requests.get(PULLS_URL, auth=(BOT_NAME, bot_key)) + if r.status_code != 200: + print('Oops, that didn\'t work. Error below, waiting then trying again.') + print(r.text) + return + + print('Loaded.') + pr_json = r.json() + # Loop: Each pull request + for pr in pr_json: + search_pr(pr) + + +def search_pr(pr): + pr_num = pr['number'] + print('Looking at PR #{}.'.format(pr_num)) + # Load comments for each pull request + cmt_url = COMMENT_FMT_URL.format(pr_num=pr_num) + print('Loading comments.') + r = requests.get(cmt_url, auth=(BOT_NAME, bot_key)) + if r.status_code != 200: + print('Oops, that didn\'t work. Error below, moving on.') + print(r.text) + return + + cmt_json = r.json() + if len(cmt_json) < 1: + print('No comments on PR #{}. Moving on.'.format(pr_num)) + return + # FUTURE: Loop over comments to make sure PR has been LGTMed + cmt = cmt_json[-1] + cmt_body = cmt['body'].encode('ascii', 'ignore') + # Look for @apache-beam request comments + # FUTURE: Look for @apache-beam reply comments + if not cmt_body.startswith('@apache-beam'): + print('Last comment: {}, not a command. Moving on.'.format(cmt_body)) + return + cmd_str = cmt_body.split('@apache-beam ', 1)[1] + cmd = cmd_str.split(' ')[0] + if cmd not in CMDS: + # Post back to PR + post_error('Command was {}, not a valid command.'.format(cmd), pr_num) + print('Command was {}, not a valid command.'.format(cmd)) + return + + if cmd == 'merge': + if cmt['user']['login'] not in AUTHORIZED_USERS: + post_error('Unauthorized users cannot merge: {}'.format(cmt['user']['login'])) + print('Unauthorized user {} attempted to merge PR {}.'.format(cmt['user']['login'], pr_num)) + return + # Kick off merge workflow + print('Command was merge, merging.') + if merge(pr_num): + post_info('Merge of PR#{} succeeded.', pr) + if not clean_up(): + print("cleanup failed; dying.") + sys.exit(1) + + def merge(pr): if not set_up(): post_error('Error setting up - please try again.', pr) + return False # Make temp directory and cd into. # Clone repository and configure. - clone_success = call(['git', 'clone', '-b', 'master', 'https://github.com/apache/incubator-beam.git', '~/tmp/'], cwd='~/tmp/') - call(['git', 'remote', 'add', 'apache', 'https://git-wip-us.apache.org/repos/asf/incubator-beam.git'], cwd='~/tmp/') - call(['git', 'remote', 'rename', 'origin', 'github'], cwd='~/tmp/') - call(['git', 'config', '--local', '--add', 'remote.github.fetch', '"+refs/pull/*/head:refs/remotes/${SOURCE_REMOTE}/pr/*"'], cwd='~/tmp/') - call(['git', 'fetch', '--all'], cwd='~/tmp/') + print("Starting merge process for #{}.".format(pr)) + clone_success = call(['git', 'clone', '-b', TARGET_BRANCH, 'https://github.com/{}/{}.git'.format(GITHUB_ORG, REPOSITORY), '/usr/local/google/home/jasonkuster/tmp/'], cwd='/usr/local/google/home/jasonkuster/tmp/') + if not clone_success == 0: + post_error('Couldn\'t clone from github/{}/{}. Please try again.'.format(GITHUB_ORG, REPOSITORY), pr) + return False + call(['git', 'remote', 'add', TARGET_REMOTE, 'https://git-wip-us.apache.org/repos/asf/{}.git'.format(REPOSITORY)], cwd='/usr/local/google/home/jasonkuster/tmp/') + call(['git', 'remote', 'rename', 'origin', SOURCE_REMOTE], cwd='/usr/local/google/home/jasonkuster/tmp/') + call('git config --local --add remote.' + SOURCE_REMOTE + '.fetch "+refs/pull/*/head:refs/remotes/{}/pr/*"'.format(SOURCE_REMOTE), shell=True, cwd='/usr/local/google/home/jasonkuster/tmp/') + call(['git', 'fetch', '--all'], cwd='/usr/local/google/home/jasonkuster/tmp/') + print("Initial work complete.") # Clean up fetch - initial_checkout = call(['git', 'checkout', '-b', 'finish-pr-{}'.format(pr), 'github/pr/{}'.format(pr)], cwd='~/tmp/') + initial_checkout = call(['git', 'checkout', '-b', 'finish-pr-{}'.format(pr), 'github/pr/{}'.format(pr)], cwd='/usr/local/google/home/jasonkuster/tmp/') if not initial_checkout == 0: - # aah + post_error("Couldn't checkout code. Please try again.", pr) + return False + print("Checked out.") # Rebase PR onto main. - rebase_success = call(['git', 'rebase', 'apache/master'], cwd='~/tmp/') + rebase_success = call(['git', 'rebase', '{}/{}'.format(TARGET_REMOTE, TARGET_BRANCH)], cwd='/usr/local/google/home/jasonkuster/tmp/') if not rebase_success == 0: + print(rebase_success) post_error('Rebase was not successful. Please rebase against main and try again.', pr) return False + print("Rebased") # Check out target branch to here - checkout_success = call(['git', 'checkout', 'apache/master'], cwd='~/tmp/') - if not checkout_success: + checkout_success = call(['git', 'checkout', '{}/{}'.format(TARGET_REMOTE, TARGET_BRANCH)], cwd='/usr/local/google/home/jasonkuster/tmp/') + if not checkout_success == 0: post_error('Error checking out target branch: master. Please try again.', pr) return False + print("Checked out Apache master.") # Merge - merge_success = call(['git', 'merge', '--no-ff', '-m', 'This closes #{}'.format(pr), 'finish-pr-{}'.format(pr)], cwd='~/tmp/') - if not merge_success: + merge_success = call(['git', 'merge', '--no-ff', '-m', 'This closes #{}'.format(pr), 'finish-pr-{}'.format(pr)], cwd='/usr/local/google/home/jasonkuster/tmp/') + if not merge_success == 0: post_error('Merge was not successful against target branch: master. Please try again.', pr) return False + print("Merged successfully.") + print("Running mvn clean verify.") # mvn clean verify - mvn_success = call(['mvn', 'clean', 'verify'], cwd='~/tmp/') - if not mvn_success: + mvn_success = call(['mvn', 'clean', 'verify'], cwd='/usr/local/google/home/jasonkuster/tmp/') + if not mvn_success == 0: post_error('mvn clean verify against HEAD + PR#{} failed. Not merging.'.format(pr), pr) return False - # git push - push_success = call(['git', 'push', 'apache', 'HEAD:master'], cwd='~/tmp/') - if not push_success: - post_error('Git push failed. Please try again.', pr) - return False + # git push (COMMENTED UNTIL MERGEBOT HAS PERMISSIONS) + #push_success = call(['git', 'push', 'apache', 'HEAD:master'], cwd='/usr/local/google/home/jasonkuster/tmp/') + #if not push_success == 0: + # post_error('Git push failed. Please try again.', pr) + # return False return True + def post_error(content, pr_num): - post("ERROR: {}".format(content), COMMENT_FMT_URL.format(pr_num=pr_num)) + post_pr_comment("Error: {}, #{}.".format(content, pr_num)) + + +def post_info(content, pr_num): + post_pr_comment("Info: {}, #{}.".format(content, pr_num)) + + +def post_pr_comment(content, pr_num): + print(content) + post(content, COMMENT_FMT_URL.format(pr_num=pr_num)) + def post(content, endpoint): payload = {"body": content} requests.post(endpoint, data=payload) + def set_up(): - if not call(['mkdir', '~/tmp']) == 0: - # call clean up? + if not call(['mkdir', '/usr/local/google/home/jasonkuster/tmp']) == 0: return False return True + def clean_up(): - if not call(['rm', '-rf', '~/tmp']) == 0: + if not call(['rm', '-rf', '/usr/local/google/home/jasonkuster/tmp']) == 0: return False return True + if __name__ == "__main__": main() From 66469b47e9c250c8d746d8a725903ba62f35ceae Mon Sep 17 00:00:00 2001 From: Jason Kuster Date: Mon, 25 Apr 2016 18:17:26 -0700 Subject: [PATCH 5/5] fixup! Updates to make mergebot command-line capable. --- testing/mergebot/mergebot.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/testing/mergebot/mergebot.py b/testing/mergebot/mergebot.py index 1c56fc19c0b1..04981a838d68 100644 --- a/testing/mergebot/mergebot.py +++ b/testing/mergebot/mergebot.py @@ -26,6 +26,8 @@ COMMENT_FMT_URL = ISSUES_URL + '/{pr_num}/comments' PULLS_URL = GITHUB_REPO_URL + '/pulls' +bot_key = '' + def main(): print('Starting up.') @@ -35,10 +37,10 @@ def main(): print('Loaded key file.') # Loop: Forever, once per minute. while True: - poll_github(bot_key) + poll_github() time.sleep(60) -def poll_github(bot_key): +def poll_github(): print('Loading pull requests from Github at {}.'.format(PULLS_URL)) # Load list of pull requests from Github r = requests.get(PULLS_URL, auth=(BOT_NAME, bot_key))