Created
July 4, 2022 20:10
-
-
Save janosh/5625739fe3d6c411750ac506099906bf to your computer and use it in GitHub Desktop.
Auto-merge bot PRs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import argparse | |
import subprocess | |
from typing import Literal | |
__author__ = "Janosh Riebesell" | |
__date__ = "2022-07-04" | |
description = """ | |
Batch merge bot-created PRs. Uses the GitHub CLI (`brew install gh`) which must be | |
authenticated, i.e. `gh auth status` must exit 0. By default asks for confirmation | |
before merging each PR. Pass --yes to skip confirmation. | |
MIT licensed. Free to reuse. | |
""" | |
def main( | |
bot_name: str, | |
owner: str, | |
yes: bool = False, | |
ci: Literal["success", "any"] = "success", | |
) -> int: | |
# make sure gh auth status returns 0 which means user is logged in and hopefully | |
# authorized to merge PRs | |
if subprocess.run("gh auth status".split(), check=True).returncode != 0: | |
raise PermissionError("Please run `gh auth login` and then `gh auth token`") | |
search_prs_cmd = f"gh search prs --state=open --app={bot_name} --owner={owner}" | |
if ci == "success": | |
search_prs_cmd += " --checks=success" | |
pr_list = ( | |
subprocess.run(search_prs_cmd.split(), capture_output=True) | |
.stdout.decode("utf-8") | |
.split("\n") | |
) | |
pr_list = list(filter(bool, pr_list)) | |
if len(pr_list) == 0: | |
print("No PRs found") | |
for idx, pr_header in enumerate(pr_list, 1): | |
try: | |
repo_handle, pr_number, *_ = pr_header.split("\t") | |
counter = f"{idx}/{len(pr_list)}" | |
if not repo_handle.startswith(f"{owner}/"): | |
raise ValueError(f"{repo_handle=} does not start with {owner=}") | |
if not pr_number.isdigit(): | |
raise ValueError(f"{pr_number=} is not a number") | |
pr_url = f"https://github.com/{repo_handle}/pull/{pr_number}" | |
if yes: | |
print(f"{counter} Merging {pr_url}") | |
answer = "yes" if yes else "" | |
while answer not in ("y", "n", "yes", "no"): | |
answer = input(f"{counter} Merge {pr_url}? [y/n] ").lower() | |
if answer in ("y", "yes"): | |
merge_pr_cmd = ( | |
f"gh pr merge {pr_number} --repo {repo_handle} --squash --delete-branch" | |
).split() | |
subprocess.run(merge_pr_cmd, capture_output=True, check=True) | |
print(f"\u2713 {repo_handle}#{pr_number} merged!") | |
except ValueError: | |
print(f"{pr_header=}") | |
raise | |
return 0 | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser(description=description) | |
parser = argparse.ArgumentParser( | |
description="Batch merge PRs from pre-commit-ci bot." | |
) | |
parser.add_argument( | |
"--bot-name", default="pre-commit-ci", help="Name of the bot to merge PRs from" | |
) | |
parser.add_argument( | |
"--owner", | |
help="GitHub user handle of the repos' owner. Can be an org handle but you " | |
"must be authorized to merge the org's PRs.", | |
) | |
parser.add_argument( | |
"-y", | |
"--yes", | |
action="store_true", | |
help="Skip confirmation prompt for each PR and automatically merge all " | |
"matching PRs.", | |
) | |
parser.add_argument( | |
"--ci", | |
choices=("success", "any"), | |
default="success", | |
help="Only merge PRs that have this status. 'success' will only merge green " | |
"PRs. 'any' also includes 'failure' and 'pending'.", | |
) | |
args = parser.parse_args() | |
ret_code = main(**vars(args)) | |
raise SystemExit(ret_code) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment