| import json | |
| import os | |
| import subprocess | |
| import requests | |
| from typing import Any, Dict | |
| from argparse import ArgumentParser | |
| MERGEBOT_TOKEN = os.environ["MERGEBOT_TOKEN"] | |
| PYTORCHBOT_TOKEN = os.environ["PYTORCHBOT_TOKEN"] | |
| OWNER, REPO = "pytorch", "pytorch" | |
| def git_api( | |
| url: str, params: Dict[str, str], type: str = "get", token: str = MERGEBOT_TOKEN | |
| ) -> Any: | |
| headers = { | |
| "Accept": "application/vnd.github.v3+json", | |
| "Authorization": f"token {token}", | |
| } | |
| if type == "post": | |
| return requests.post( | |
| f"https://api.github.com{url}", | |
| data=json.dumps(params), | |
| headers=headers, | |
| ).json() | |
| elif type == "patch": | |
| return requests.patch( | |
| f"https://api.github.com{url}", | |
| data=json.dumps(params), | |
| headers=headers, | |
| ).json() | |
| else: | |
| return requests.get( | |
| f"https://api.github.com{url}", | |
| params=params, | |
| headers=headers, | |
| ).json() | |
| def parse_args() -> Any: | |
| parser = ArgumentParser("Rebase PR into branch") | |
| parser.add_argument("--repo-name", type=str) | |
| parser.add_argument("--branch", type=str) | |
| return parser.parse_args() | |
| def make_pr(repo_name: str, branch_name: str) -> Any: | |
| params = { | |
| "title": f"[{repo_name} hash update] update the pinned {repo_name} hash", | |
| "head": branch_name, | |
| "base": "master", | |
| "body": "This PR is auto-generated nightly by [this action](https://github.com/pytorch/pytorch/blob/master/" | |
| + f".github/workflows/_update-commit-hash.yml).\nUpdate the pinned {repo_name} hash.", | |
| } | |
| response = git_api(f"/repos/{OWNER}/{REPO}/pulls", params, type="post") | |
| print(f"made pr {response['html_url']}") | |
| return response["number"] | |
| def approve_pr(pr_number: str) -> None: | |
| params = {"event": "APPROVE"} | |
| # use pytorchbot to approve the pr | |
| git_api( | |
| f"/repos/{OWNER}/{REPO}/pulls/{pr_number}/reviews", | |
| params, | |
| type="post", | |
| token=PYTORCHBOT_TOKEN, | |
| ) | |
| def make_comment(pr_number: str, msg: str) -> None: | |
| params = {"body": msg} | |
| # comment with pytorchbot because pytorchmergebot gets ignored | |
| git_api( | |
| f"/repos/{OWNER}/{REPO}/issues/{pr_number}/comments", | |
| params, | |
| type="post", | |
| token=PYTORCHBOT_TOKEN, | |
| ) | |
| def close_pr(pr_number: str) -> None: | |
| params = {"state": "closed"} | |
| git_api( | |
| f"/repos/{OWNER}/{REPO}/pulls/{pr_number}", | |
| params, | |
| type="patch", | |
| ) | |
| def is_newer_hash(new_hash: str, old_hash: str, repo_name: str) -> bool: | |
| def _get_date(hash: str) -> int: | |
| # this git command prints the unix timestamp of the hash | |
| return int( | |
| subprocess.run( | |
| f"git show --no-patch --no-notes --pretty=%ct {hash}".split(), | |
| capture_output=True, | |
| cwd=f"{repo_name}", | |
| ) | |
| .stdout.decode("utf-8") | |
| .strip() | |
| ) | |
| return _get_date(new_hash) > _get_date(old_hash) | |
| def main() -> None: | |
| args = parse_args() | |
| branch_name = os.environ["NEW_BRANCH_NAME"] | |
| pr_num = None | |
| # query to see if a pr already exists | |
| params = { | |
| "q": f"is:pr is:open in:title author:pytorchmergebot repo:{OWNER}/{REPO} {args.repo_name} hash update" | |
| } | |
| response = git_api("/search/issues", params) | |
| if response["total_count"] != 0: | |
| # pr does exist | |
| pr_num = response["items"][0]["number"] | |
| link = response["items"][0]["html_url"] | |
| response = git_api(f"/repos/{OWNER}/{REPO}/pulls/{pr_num}", {}) | |
| branch_name = response["head"]["ref"] | |
| print( | |
| f"pr does exist, number is {pr_num}, branch name is {branch_name}, link is {link}" | |
| ) | |
| hash = ( | |
| subprocess.run( | |
| f"git rev-parse {args.branch}".split(), | |
| capture_output=True, | |
| cwd=f"{args.repo_name}", | |
| ) | |
| .stdout.decode("utf-8") | |
| .strip() | |
| ) | |
| with open(f".github/ci_commit_pins/{args.repo_name}.txt", "r+") as f: | |
| old_hash = f.read().strip() | |
| f.seek(0) | |
| f.truncate() | |
| f.write(f"{hash}\n") | |
| if is_newer_hash(hash, old_hash, args.repo_name): | |
| # if there was an update, push to branch | |
| subprocess.run(f"git checkout -b {branch_name}".split()) | |
| subprocess.run(f"git add .github/ci_commit_pins/{args.repo_name}.txt".split()) | |
| subprocess.run( | |
| "git commit -m".split() + [f"update {args.repo_name} commit hash"] | |
| ) | |
| subprocess.run(f"git push --set-upstream origin {branch_name} -f".split()) | |
| print(f"changes pushed to branch {branch_name}") | |
| if pr_num is None: | |
| # no existing pr, so make a new one and approve it | |
| pr_num = make_pr(args.repo_name, branch_name) | |
| approve_pr(pr_num) | |
| # comment to merge if all checks are green | |
| make_comment(pr_num, "@pytorchbot merge -g") | |
| else: | |
| print( | |
| f"tried to update from old hash: {old_hash} to new hash: {hash} but the old hash seems to be newer, not creating pr" | |
| ) | |
| if pr_num is not None: | |
| make_comment(pr_num, "closing pr as the current hash seems up to date") | |
| close_pr(pr_num) | |
| print(f"closing PR {pr_num}") | |
| if __name__ == "__main__": | |
| main() |