import argparse import os import re import time from github import Github GITHUB_TOKEN = os.getenv('GITHUB_TOKEN') def get_mergeable_state(pr_number): """Get mergeable state of the PR https://docs.github.com/en/graphql/reference/enums#mergestatestatus""" github_api = Github(GITHUB_TOKEN) db_repo = github_api.get_repo("theorchard/database") # github calculates mergeable state when the api request occurs, so might need to retry a fetch if stale attempt = 1 while (attempt < 10): pr = db_repo.get_pull(pr_number) mergeable_state = pr.mergeable_state if mergeable_state != 'unknown': return mergeable_state print('PR {} status is currently unknown, sleeping and retrying.'.format( pr_number)) time.sleep(5) attempt = attempt + 1 raise Exception('PR {} has a mergeable_state of unknown. Please give ' 'GitHub a minute to finish calculating this state.'.format(pr_number)) def is_pr_mergeable(pr_number, raise_exception): pr_mergeable_state = get_mergeable_state(pr_number) print('The status of PR {} is {}'.format(pr_number, pr_mergeable_state)) if pr_mergeable_state != 'clean': message = 'PR {} is not mergeable, blocking deploy'.format(pr_number) if raise_exception: raise Exception(message) print(message) return False print('This PR is mergeable') return True def get_pr_state_merged(pr_number): github_api = Github(GITHUB_TOKEN) db_repo = github_api.get_repo("theorchard/database") pr = db_repo.get_pull(pr_number) return (pr.state, pr.merged) def get_pr_from_sha1(sha1): github_api = Github(GITHUB_TOKEN) db_repo = github_api.get_repo("theorchard/database") commit = db_repo.get_commit(sha1) pulls = commit.get_pulls() return pulls[0].number def get_pr_from_pr_string(pr_string): """pr_string in format of 'refs/remotes/origin/pr//head' """ pattern = re.compile("refs/remotes/origin/pr/([0-9]+)/head") match = pattern.fullmatch(pr_string) if match: return int(match[1]) else: raise Exception('PR String not in expected format: ' 'refs/remotes/origin/pr//head') def main(*args): parser = argparse.ArgumentParser() parser.add_argument( '--pr-number', type=int, help='Pull Request to check if mergeable') parser.add_argument( '--pr-string', help='refs/remotes/origin/pr//head') parser.add_argument( '--sha1', help='sha1 whose Pull Request to check if mergeable') parser.add_argument( '--raise-exception', action='store_true', help='Raise exception if PR is not mergeable') args = parser.parse_args() pr_number = args.pr_number if (args.pr_string): pr_number = get_pr_from_pr_string(args.pr_string) if (args.sha1): pr_number = get_pr_from_sha1(args.sha1) # check if PR is already merged (otherwise mergeable state is always unknown) (state, merged) = get_pr_state_merged(pr_number) if merged: print('PR {} is already merged and can be deployed'.format(pr_number)) return True if state == 'closed': message = 'PR {} is closed and not merged so canNOT be deployed'.format(pr_number) if args.raise_exception: raise Exception(message) print(message) return False is_pr_mergeable(pr_number, args.raise_exception) if __name__ == '__main__': main()