Script to comment on pull requests that need rebasing

Occasionally we change the status checks in a repo, and then in-flight pull requests need to be rebased in order to pass their checks. This program finds those pull requests and adds a comment so that developers don’t mysteriously have failing checks they can’t fix.

If you need something like this, feel free to use it, but don’t forget to change the details in the middle! @Tim_McCormack and @robrap wrote the original, I tweaked it for use this week:

#!/usr/bin/env python3
# This script records an action we took to leave a comment on (almost)
# every open edx-platform PR to let people know that they needed to
# rebase onto master (or merge it into their branch) to avoid breaking
# the build.
#
# The script requires a GitHub access token and finds all open PRs
# against master on edx-platform which do not have a specific commit
# in their ancestors, then leaves a comment on each one. This should
# be done from a bot account; the bot will then be subscribed to all
# those threads, and should be manually unsubscribed.
#
# The list of PRs this was run against was added to the skip-list in
# case we needed to run it again for some reason; during testing we
# limited it to just run against a few PRs and then added those to the
# skip-list for the next iteration.
#
# Requirements:
# - github3.py
# - click
# - pytz

import datetime
import sys

import click
import github3
import pytz


# The oldest commit that all new branches should have as their
# ancestor in order to contain all the changes to the quality checks.
WORKING_QUALITY_COMMIT = '1e0f0e344b41c0001cdc0b800ea154da9aeac611'

# The earliest creation date we will comment on.  Older pull requests
# are either abandoned, or will need signification renovations anyway,
# so don't bother commenting on them.
CREATED_SINCE = pytz.UTC.localize(datetime.datetime(2022, 8, 30))

# Pull request numbers that shouldn't get a comment because we've already
# commented on them.
ALREADY_HANDLED = []

def process_pr(gh, repo, pr):
    # Skip any PRs that have the commit as an ancestor
    comparison = repo.compare_commits(WORKING_QUALITY_COMMIT, pr.head.sha)
    if comparison.behind_by == 0:
        return
    # Skip PRs that are too old
    if pr.created_at < CREATED_SINCE:
        return
    # PRs we already handled during testing
    if pr.number in ALREADY_HANDLED:
        return
    
    print(f"PR #{pr.number} (branch {pr.head.ref}) is behind by {comparison.behind_by}")

    message = f"""
📣 💥 Heads-up: You must either **rebase onto master** or **merge master into your branch** to keep passing required checks.

We added a new required check, "Tests Successful," that this PR does not yet run.  Rebasing will get it started.

If you have any questions, please reach out to the Architecture team (either [#architecture](https://openedx.slack.com/archives/C024JPE27PV) on Open edX Slack or [#external-architecture](https://edx-internal.slack.com/archives/C024JPE27PV) on edX internal Slack).
"""

    pr.create_comment(message)


@click.command()
@click.option(
    '--github-username',
    help="Username corresponding to the GitHub access token"
)
@click.option(
    '--github-token',
    help="GitHub token with repo and org read scope on applicable orgs."
)
@click.option(
    '--github-repo',
    help="GitHub repo in org/short-name format"
)
def main(github_username, github_token, github_repo):
    gh = github3.login(github_username, github_token)
    (repo_owner, repo_name) = github_repo.split('/', 2)
    repo = gh.repository(repo_owner, repo_name)

    prs = list(repo.pull_requests(state='open', base='master'))
    print(f"Found {len(prs)} open PRs against master")

    for pr in prs:
        process_pr(gh, repo, pr)


if __name__ == '__main__':
    main(auto_envvar_prefix='MSG_PRS')
1 Like

Nice. Thanks for sharing,