def __init__(self, admin_ghtoken, puller_ghtoken_pool ,repo_list, freeze_context, freeze_desc, unfreeze_desc): self.__admin_ghtoken = admin_ghtoken self.__puller_ghtoken_pool = puller_ghtoken_pool self.__puller_ghtoken_pool = puller_ghtoken_pool.split() self.__repo_list = repo_list self.__freeze_context = freeze_context self.__freeze_desc = freeze_desc self.__unfreeze_desc = unfreeze_desc # a pygithub private class method self.__admin_requester = Requester.Requester(self.__admin_ghtoken,None,"https://api.github.com", 10, None, None,'PyGithub/Python',30,False)
APPROVAL_COLUMNS = { 'de': 654919, 'en': 390130, 'fr': 398417, 'pt': 654918, 'zh': 654920, } # The GitHub Projects API is still in development # - https://developer.github.com/v3/projects # - It's not supported by the python lib yet # - We need to provide a special "Accept" header # So, we hack in our own support ; it's dirty but it works. # Be warned : it may break at any moment -_- GITHUB_ACCEPT = "application/vnd.github.inertia-preview+json" rq = Requester.Requester(GITHUB_API_KEY, None, "https://api.github.com", 10, None, None, 'PyGithub/Python', 30, False) # MAIN ######################################################################## if __name__ == "__main__": argparser = argparse.ArgumentParser(parents=[youtube_argparser], add_help=False, description=""" Updates the issues's card by moving them to the last column if the related caption has been published and subsequently downloaded. """, epilog=""" © WTFPL 2017 - YOU ARE FREE TO DO WHAT THE FORK YOU WANT """)
def publish(token: str, event: dict, repo_name: str, commit_sha: str, stats: Dict[Any, Any], cases: Dict[str, Dict[str, List[Dict[Any, Any]]]], check_name: str, comment_title: str, hide_comment_mode: str, report_individual_runs: bool): from github import Github, PullRequest, Requester, MainClass from githubext import Repository, Commit, IssueComment # to prevent githubext import to be auto-removed if getattr(Repository, 'create_check_run') is None: raise RuntimeError('patching github Repository failed') if getattr(Commit, 'get_check_runs') is None: raise RuntimeError('patching github Commit failed') if getattr(IssueComment, 'node_id') is None: raise RuntimeError('patching github IssueComment failed') gh = Github(token) repo = gh.get_repo(repo_name) req = Requester.Requester(token, password=None, jwt=None, base_url=MainClass.DEFAULT_BASE_URL, timeout=MainClass.DEFAULT_TIMEOUT, client_id=None, client_secret=None, user_agent="PyGithub/Python", per_page=MainClass.DEFAULT_PER_PAGE, verify=True, retry=None) def get_pull(commit: str) -> PullRequest: issues = gh.search_issues('type:pr {}'.format(commit)) logger.debug('found {} pull requests for commit {}'.format(issues.totalCount, commit)) if issues.totalCount == 0: return None logger.debug('running in repo {}'.format(repo_name)) for issue in issues: pr = issue.as_pull_request() logger.debug(pr) logger.debug(pr.raw_data) logger.debug('PR {}: {} -> {}'.format(pr.html_url, pr.head.repo.full_name, pr.base.repo.full_name)) # we can only publish the comment to PRs that are in the same repository as this action is executed in # so pr.base.repo.full_name must be same as GITHUB_REPOSITORY # we won't have permission otherwise pulls = list([pr for issue in issues for pr in [issue.as_pull_request()] if pr.base.repo.full_name == repo_name]) if len(pulls) == 0: logger.debug('found no pull requests in repo {} for commit {}'.format(repo_name, commit)) return None if len(pulls) > 1: logger.error('found multiple pull requests for commit {}'.format(commit)) return None pull = pulls[0] logger.debug('found pull request #{} for commit {}'.format(pull.number, commit)) return pull def get_stats_from_commit(commit_sha: str) -> Optional[Dict[Any, Any]]: if commit_sha is None or commit_sha == '0000000000000000000000000000000000000000': return None commit = repo.get_commit(commit_sha) if commit is None: logger.error('could not find commit {}'.format(commit_sha)) return None runs = commit.get_check_runs() logger.debug('found {} check runs for commit {}'.format(runs.totalCount, commit_sha)) runs = list([run for run in runs if run.name == check_name]) logger.debug('found {} check runs for commit {} with title {}'.format(len(runs), commit_sha, check_name)) if len(runs) != 1: return None summary = runs[0].output.get('summary') if summary is None: return None for line in summary.split('\n'): logger.debug('summary: {}'.format(line)) pos = summary.index(digest_prefix) if digest_prefix in summary else None if pos: digest = summary[pos + len(digest_prefix):] logger.debug('digest: {}'.format(digest)) stats = get_stats_from_digest(digest) logger.debug('stats: {}'.format(stats)) return stats def publish_check(stats: Dict[Any, Any], cases: Dict[str, Dict[str, List[Dict[Any, Any]]]]) -> None: # get stats from earlier commits before_commit_sha = event.get('before') logger.debug('comparing against before={}'.format(before_commit_sha)) before_stats = get_stats_from_commit(before_commit_sha) stats_with_delta = get_stats_with_delta(stats, before_stats, 'ancestor') if before_stats is not None else stats logger.debug('stats with delta: {}'.format(stats_with_delta)) all_annotations = get_annotations(cases, report_individual_runs) # only works when run by GitHub Actions GitHub App if os.environ.get('GITHUB_ACTIONS') is None: logger.warning('action not running on GitHub, skipping publishing the check') return # we can send only 50 annotations at once, so we split them into chunks of 50 all_annotations = [all_annotations[x:x+50] for x in range(0, len(all_annotations), 50)] or [[]] for annotations in all_annotations: output = dict( title=get_short_summary(stats, check_name), summary=get_long_summary_with_digest_md(stats_with_delta, stats), annotations=annotations ) logger.info('creating check') repo.create_check_run(name=check_name, head_sha=commit_sha, status='completed', conclusion='success', output=output) def publish_comment(title: str, stats: Dict[Any, Any], pull) -> None: # compare them with earlier stats base_commit_sha = pull.base.sha if pull else None logger.debug('comparing against base={}'.format(base_commit_sha)) base_stats = get_stats_from_commit(base_commit_sha) stats_with_delta = get_stats_with_delta(stats, base_stats, 'base') if base_stats is not None else stats logger.debug('stats with delta: {}'.format(stats_with_delta)) # we don't want to actually do this when not run by GitHub Actions GitHub App if os.environ.get('GITHUB_ACTIONS') is None: logger.warning('action not running on GitHub, skipping creating comment') return pull logger.info('creating comment') pull.create_issue_comment('## {}\n{}'.format(title, get_long_summary_md(stats_with_delta))) return pull def get_pull_request_comments(pull: PullRequest) -> List[Dict[str, Any]]: query = dict( query=r'query ListComments {' r' repository(owner:"' + repo.owner.login + r'", name:"' + repo.name + r'") {' r' pullRequest(number:' + str(pull.number) + r') {' r' comments(last: 100) {' r' nodes {' r' id, author { login }, body, isMinimized' r' }' r' }' r' }' r' }' r'}' ) headers, data = req.requestJsonAndCheck( "POST", 'https://api.github.com/graphql', input=query ) return data \ .get('data', {}) \ .get('repository', {}) \ .get('pullRequest', {}) \ .get('comments', {}) \ .get('nodes') def hide_comment(comment_node_id) -> bool: input = dict( query=r'mutation MinimizeComment {' r' minimizeComment(input: { subjectId: "' + comment_node_id + r'", classifier: OUTDATED } ) {' r' minimizedComment { isMinimized, minimizedReason }' r' }' r'}' ) headers, data = req.requestJsonAndCheck( "POST", 'https://api.github.com/graphql', input=input ) return data.get('data').get('minimizeComment').get('minimizedComment').get('isMinimized') def hide_orphaned_commit_comments(pull: PullRequest) -> None: # rewriting history of branch removes commits # we do not want to show test results for those commits anymore # get commits of this pull request commit_shas = set([commit.sha for commit in pull.get_commits()]) # get comments of this pull request comments = get_pull_request_comments(pull) # get all comments that come from this action and are not hidden comments = list([comment for comment in comments if comment.get('author', {}).get('login') == 'github-actions' and comment.get('isMinimized') is False and comment.get('body', '').startswith('## {}\n'.format(comment_title)) and '\nresults for commit ' in comment.get('body')]) # get comment node ids and their commit sha (possibly abbreviated) matches = [(comment.get('id'), re.search(r'^results for commit ([0-9a-f]{8,40})(?:\s.*)?$', comment.get('body'), re.MULTILINE)) for comment in comments] comment_commits = [(node_id, match.group(1)) for node_id, match in matches if match is not None] # get those comment node ids whose commit is not part of this pull request any more comment_ids = [(node_id, comment_commit_sha) for (node_id, comment_commit_sha) in comment_commits if not any([sha for sha in commit_shas if sha.startswith(comment_commit_sha)])] # we don't want to actually do this when not run by GitHub Actions GitHub App if os.environ.get('GITHUB_ACTIONS') is None: logger.warning('action not running on GitHub, skipping hiding comment') for node_id, comment_commit_sha in comment_ids: logger.info('commend for commit {} should be hidden'.format(comment_commit_sha)) return # hide all those comments for node_id, comment_commit_sha in comment_ids: logger.info('hiding unit test result comment for commit {}'.format(comment_commit_sha)) hide_comment(node_id) def hide_all_but_latest_comments(pull: PullRequest) -> None: # we want to reduce the number of shown comments to a minimum # get comments of this pull request comments = get_pull_request_comments(pull) # get all comments that come from this action and are not hidden comments = list([comment for comment in comments if comment.get('author', {}).get('login') == 'github-actions' and comment.get('isMinimized') is False and comment.get('body', '').startswith('## {}\n'.format(comment_title)) and '\nresults for commit ' in comment.get('body')]) # take all but the last comment comment_ids = [comment.get('id') for comment in comments[:-1]] # we don't want to actually do this when not run by GitHub Actions GitHub App if os.environ.get('GITHUB_ACTIONS') is None: logger.warning('action not running on GitHub, skipping hiding comment') for node_id in comment_ids: logger.info('commend {} should be hidden'.format(node_id)) return # hide all those comments for node_id in comment_ids: logger.info('hiding unit test result comment {}'.format(node_id)) hide_comment(node_id) logger.info('publishing results for commit {}'.format(commit_sha)) publish_check(stats, cases) pull = get_pull(commit_sha) if pull is not None: publish_comment(comment_title, stats, pull) if hide_comment_mode == hide_comments_mode_orphaned: hide_orphaned_commit_comments(pull) elif hide_comment_mode == hide_comments_mode_all_but_latest: hide_all_but_latest_comments(pull) else: logger.info('there is no pull request for commit {}'.format(commit_sha))
def hide_comments(pull: PullRequest) -> None: # rewriting history of branch removes commits # we do not want to show test results for those commits anymore req = Requester.Requester(token, password=None, jwt=None, base_url=MainClass.DEFAULT_BASE_URL, timeout=MainClass.DEFAULT_TIMEOUT, client_id=None, client_secret=None, user_agent="PyGithub/Python", per_page=MainClass.DEFAULT_PER_PAGE, verify=True, retry=None) def get_pull_request_comments(pull: PullRequest) -> List[Dict[str, Any]]: query = dict( query=r'query ListComments {' r' repository(owner:"' + repo.owner.login + r'", name:"' + repo.name + r'") {' r' pullRequest(number:' + str(pull.number) + r') {' r' comments(last: 100) {' r' nodes {' r' id, author { login }, body, isMinimized' r' }' r' }' r' }' r' }' r'}' ) headers, data = req.requestJsonAndCheck( "POST", 'https://api.github.com/graphql', input=query ) return data \ .get('data', {}) \ .get('repository', {}) \ .get('pullRequest', {}) \ .get('comments', {}) \ .get('nodes') def hide_comment(comment_node_id) -> bool: input = dict( query=r'mutation MinimizeComment {' r' minimizeComment(input: { subjectId: "' + comment_node_id + r'", classifier: OUTDATED } ) {' r' minimizedComment { isMinimized, minimizedReason }' r' }' r'}' ) headers, data = req.requestJsonAndCheck( "POST", 'https://api.github.com/graphql', input=input ) return data.get('data').get('minimizeComment').get('minimizedComment').get('isMinimized') # get commits of this pull request commit_shas = set([commit.sha for commit in pull.get_commits()]) # get commits of this pull request comments = get_pull_request_comments(pull) # get all comments that come from this action and are not hidden comments = list([comment for comment in comments if comment.get('author', {}).get('login') == 'github-actions' and comment.get('isMinimized') is False and comment.get('body', '').startswith('## Unit Test Results\n') and '\nresults for commit ' in comment.get('body')]) # get comment node ids and their commit sha (possibly abbreviated) matches = [(comment.get('id'), re.search(r'^results for commit ([0-9a-f]{8,40})(?:\s.*)?$', comment.get('body'), re.MULTILINE)) for comment in comments] comment_commits = [(node_id, match.group(1)) for node_id, match in matches if match is not None] # get those comment node ids whose commit is not part of this pull request any more comment_ids = [(node_id, comment_commit_sha) for (node_id, comment_commit_sha) in comment_commits if not any([sha for sha in commit_shas if sha.startswith(comment_commit_sha)])] # we don't want to actually do this when not run by GitHub Actions GitHub App if os.environ.get('GITHUB_ACTIONS') is None: logger.warning('action not running on GitHub, skipping hiding comment') for node_id, comment_commit_sha in comment_ids: logger.info('commend for commit {} should be hidden'.format(comment_commit_sha)) return # hide all those comments that do not have a commit for node_id, comment_commit_sha in comment_ids: logger.info('hiding unit test result comment for commit {}'.format(comment_commit_sha)) hide_comment(node_id)