예제 #1
0
def process_pull_requests(repository, installation,
                          warn_seconds=None,
                          close_seconds=None):

    now = time.time()

    # Find app name
    bot_name = get_app_name()

    # Get issues labeled as 'Close?'
    repo = RepoHandler(repository, 'master', installation)
    pull_requests = repo.open_pull_requests()

    for n in pull_requests:

        print(f'Checking {n}')

        pr = PullRequestHandler(repository, n, installation)
        if 'keep-open' in pr.labels:
            print('-> PROTECTED by label, skipping')
            continue

        commit_time = pr.last_commit_date
        time_since_last_commit = now - commit_time

        # Note: if warning time is before commit time, it's as if the warning
        # didn't exist since it's no longer relevant.
        warning_time = pr.last_comment_date(f'{bot_name}[bot]', filter_keep=is_close_warning)
        if warning_time is None or warning_time < commit_time:
            time_since_last_warning = -1.
        else:
            # We use max() here to make sure that the value is positive
            time_since_last_warning = max(0, now - warning_time)

        # We only close pull requests if there has been a warning before, and
        # the time since the warning exceeds the threshold specified by
        # close_seconds.

        if time_since_last_warning > close_seconds:
            comment_ids = pr.find_comments(f'{bot_name}[bot]', filter_keep=is_close_epilogue)
            if len(comment_ids) == 0:
                print(f'-> CLOSING pull request {n}')
                pr.set_labels(['closed-by-bot'])
                pr.submit_comment(PULL_REQUESTS_CLOSE_EPILOGUE)
                pr.close()
            else:
                print(f'-> Skipping pull request {n} (already closed)')
        elif time_since_last_commit > warn_seconds:
            # A negative time_since_last_warning means no warning since last commit.
            if time_since_last_warning < 0.:
                print(f'-> WARNING pull request {n}')
                pr.submit_comment(PULL_REQUESTS_CLOSE_WARNING.format(pasttime=naturaldelta(time_since_last_commit),
                                                                     futuretime=naturaldelta(close_seconds)))
            else:
                print(f'-> Skipping pull request {n} (already warned)')
        else:
            print(f'-> OK pull request {n}')

    print('Finished checking for stale pull requests')
예제 #2
0
def process_pull_request(repository, number, installation):

    # TODO: cache handlers and invalidate the internal cache of the handlers on
    # certain events.
    pr_handler = PullRequestHandler(repository, number, installation)

    pr_config = pr_handler.get_config_value("pull_requests", {})
    post_comment = pr_config.get("post_pr_comment", False)
    pull_request_substring = pr_config.get('pull_request_substring', '')

    # Disable if the config is not present
    if pr_config is None:
        return

    # Don't comment on closed PR
    if pr_handler.is_closed:
        return "Pull request already closed, no need to check"

    repo_handler = RepoHandler(pr_handler.head_repo_name,
                               pr_handler.head_branch, installation)

    def is_previous_comment(message):
        if len(pull_request_substring) > 0:
            return pull_request_substring in message
        else:
            return True

    # Find previous comments by this app
    comment_ids = pr_handler.find_comments(f'{current_app.bot_username}[bot]',
                                           filter_keep=is_previous_comment)

    if len(comment_ids) == 0:
        comment_id = None
    else:
        comment_id = comment_ids[-1]

    # First check whether there are labels that indicate the checks should be
    # skipped

    skip_labels = pr_config.get("skip_labels", [])
    skip_fails = pr_config.get("skip_fails", True)

    for label in pr_handler.labels:
        if label in skip_labels:
            skip_message = pr_config.get(
                "skip_message", "Pull request checks have "
                "been skipped as this pull request has been "
                f"labelled as **{label}**")
            skip_message = skip_message.format(pr_handler=pr_handler,
                                               repo_handler=repo_handler)
            pr_handler.submit_comment(skip_message, comment_id=comment_id)
            if skip_fails:
                pr_handler.set_status(
                    'failure',
                    "Skipping checks due to {0} label".format(label),
                    current_app.bot_username)
            return

    results = {}
    for function in PULL_REQUEST_CHECKS:
        result = function(pr_handler, repo_handler)
        results.update(result)

    failures = [
        details['description'] for details in results.values()
        if details['state'] in ('error', 'failure')
    ]

    if post_comment:

        # Post all failures in a comment, and have a single status check

        if failures:

            pull_request_prologue = pr_config.get('fail_prologue', '')
            pull_request_epilogue = pr_config.get('fail_epilogue', '')

            fail_status = pr_config.get('fail_status', 'Failed some checks')

            message = pull_request_prologue.format(pr_handler=pr_handler,
                                                   repo_handler=repo_handler)
            for failure in failures:
                message += f'* {failure}\n'
            message += pull_request_epilogue.format(pr_handler=pr_handler,
                                                    repo_handler=repo_handler)
            comment_url = pr_handler.submit_comment(message,
                                                    comment_id=comment_id,
                                                    return_url=True)

            pr_handler.set_status('failure',
                                  fail_status,
                                  current_app.bot_username,
                                  target_url=comment_url)

        else:

            pass_status = pr_config.get('pass_status', 'Passed all checks')

            all_passed_message = pr_config.get('all_passed_message', '')
            all_passed_message = all_passed_message.format(
                pr_handler=pr_handler, repo_handler=repo_handler)

            if all_passed_message:
                pr_handler.submit_comment(all_passed_message,
                                          comment_id=comment_id)

            pr_handler.set_status('success', pass_status,
                                  current_app.bot_username)

    else:

        # Post each failure as a status

        existing_statuses = pr_handler.list_statuses()

        for context, details in sorted(results.items()):

            full_context = current_app.bot_username + ':' + context

            # Don't post again if status hasn't changed
            if full_context in existing_statuses:
                existing_details = existing_statuses[full_context]
                if (details['state'] == existing_details['state']
                        and details['description']
                        == existing_details['description']
                        and details.get('target_url')
                        == existing_details.get('target_url')):
                    continue

            pr_handler.set_status(details['state'],
                                  details['description'],
                                  full_context,
                                  target_url=details.get('target_url'))

        # For statuses that have been skipped this time but existed before, set
        # status to pass and set message to say skipped

        for full_context in existing_statuses:
            if full_context.startswith(current_app.bot_username + ':'):
                context = full_context[len(current_app.bot_username) + 1:]
                if context not in results:
                    pr_handler.set_status(
                        'success', 'This check has been skipped',
                        current_app.bot_username + ':' + context)

    return 'Finished pull requests checks'