Exemplo n.º 1
0
 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)
Exemplo n.º 2
0
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)