diff options
author | David Spickett <david.spickett@linaro.org> | 2024-02-13 14:52:02 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-13 14:52:02 +0000 |
commit | 38c706e30f5f339bfb0bfb26fd7b5c2d5086064a (patch) | |
tree | 15d136793d366cdc95f962beca640c1573b62a64 | |
parent | 5e5e51e9062895bed9fcf0dbb157d868be0adf8d (diff) | |
download | llvm-38c706e30f5f339bfb0bfb26fd7b5c2d5086064a.zip llvm-38c706e30f5f339bfb0bfb26fd7b5c2d5086064a.tar.gz llvm-38c706e30f5f339bfb0bfb26fd7b5c2d5086064a.tar.bz2 |
[GitHub][workflows] Ask reviewers to merge PRs when author cannot (#81142)
This uses
https://pygithub.readthedocs.io/en/stable/github_objects/Repository.html?highlight=get_collaborator_permission#github.Repository.Repository.get_collaborator_permission.
Which does
https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2022-11-28#get-repository-permissions-for-a-user
and returns the top level "permission" key.
This is less detailed than the user/permissions key but should be fine
for this
use case.
When a review is submitted we check:
* If it's an approval.
* Whether we have already left a merge on behalf comment (by looking for
a hidden HTML comment).
* Whether the author has permissions to merge their own PR.
* Whether the reviewer has permissions to merge.
If needed we leave a comment tagging the reviewer. If the reviewer also
doesn't have merge permission, then it asks them to find someone else
who does.
-rw-r--r-- | .github/workflows/approved-prs.yml | 39 | ||||
-rwxr-xr-x | llvm/utils/git/github-automation.py | 65 |
2 files changed, 104 insertions, 0 deletions
diff --git a/.github/workflows/approved-prs.yml b/.github/workflows/approved-prs.yml new file mode 100644 index 0000000..309a921 --- /dev/null +++ b/.github/workflows/approved-prs.yml @@ -0,0 +1,39 @@ +name: "Prompt reviewers to merge PRs on behalf of authors" + +permissions: + contents: read + +on: + pull_request_review: + types: + - submitted + +jobs: + merge-on-behalf-information-comment: + runs-on: ubuntu-latest + permissions: + pull-requests: write + if: >- + (github.repository == 'llvm/llvm-project') && + (github.event.review.state == 'APPROVED') + steps: + - name: Checkout Automation Script + uses: actions/checkout@v4 + with: + sparse-checkout: llvm/utils/git/ + ref: main + + - name: Setup Automation Script + working-directory: ./llvm/utils/git/ + run: | + pip install -r requirements.txt + + - name: Add Merge On Behalf Comment + working-directory: ./llvm/utils/git/ + run: | + python3 ./github-automation.py \ + --token '${{ secrets.GITHUB_TOKEN }}' \ + pr-merge-on-behalf-information \ + --issue-number "${{ github.event.pull_request.number }}" \ + --author "${{ github.event.pull_request.user.login }}" \ + --reviewer "${{ github.event.review.user.login }}" diff --git a/llvm/utils/git/github-automation.py b/llvm/utils/git/github-automation.py index b475eff..ccef274 100755 --- a/llvm/utils/git/github-automation.py +++ b/llvm/utils/git/github-automation.py @@ -298,6 +298,55 @@ If you don't get any reports, no action is required from you. Your changes are w return True +class PRMergeOnBehalfInformation: + COMMENT_TAG = "<!--LLVM MERGE ON BEHALF INFORMATION COMMENT-->\n" + + def __init__( + self, token: str, repo: str, pr_number: int, author: str, reviewer: str + ): + self.repo = github.Github(token).get_repo(repo) + self.pr = self.repo.get_issue(pr_number).as_pull_request() + self.author = author + self.reviewer = reviewer + + def can_merge(self, user: str) -> bool: + try: + return self.repo.get_collaborator_permission(user) in ["admin", "write"] + # There is a UnknownObjectException for this scenario, but this method + # does not use it. + except github.GithubException as e: + # 404 means the author was not found in the collaborator list, so we + # know they don't have push permissions. Anything else is a real API + # issue, raise it so it is visible. + if e.status != 404: + raise e + return False + + def run(self) -> bool: + # Check this first because it only costs 1 API point. + if self.can_merge(self.author): + return + + # A review can be approved more than once, only comment the first time. + for comment in self.pr.as_issue().get_comments(): + if self.COMMENT_TAG in comment.body: + return + + # This text is using Markdown formatting. + if self.can_merge(self.reviewer): + comment = f"""\ +{self.COMMENT_TAG} +@{self.reviewer} the PR author does not have permission to merge their own PRs yet. Please merge on their behalf.""" + else: + comment = f"""\ +{self.COMMENT_TAG} +@{self.reviewer} the author of this PR does not have permission to merge and neither do you. +Please find someone who has merge permissions who can merge it on the author's behalf. This could be one of the other reviewers or you can ask on [Discord](https://discord.com/invite/xS7Z362).""" + + self.pr.as_issue().create_comment(comment) + return True + + def setup_llvmbot_git(git_dir="."): """ Configure the git repo in `git_dir` with the llvmbot account so @@ -665,6 +714,17 @@ pr_buildbot_information_parser = subparsers.add_parser("pr-buildbot-information" pr_buildbot_information_parser.add_argument("--issue-number", type=int, required=True) pr_buildbot_information_parser.add_argument("--author", type=str, required=True) +pr_merge_on_behalf_information_parser = subparsers.add_parser( + "pr-merge-on-behalf-information" +) +pr_merge_on_behalf_information_parser.add_argument( + "--issue-number", type=int, required=True +) +pr_merge_on_behalf_information_parser.add_argument("--author", type=str, required=True) +pr_merge_on_behalf_information_parser.add_argument( + "--reviewer", type=str, required=True +) + release_workflow_parser = subparsers.add_parser("release-workflow") release_workflow_parser.add_argument( "--llvm-project-dir", @@ -724,6 +784,11 @@ elif args.command == "pr-buildbot-information": args.token, args.repo, args.issue_number, args.author ) pr_buildbot_information.run() +elif args.command == "pr-merge-on-behalf-information": + pr_merge_on_behalf_information = PRMergeOnBehalfInformation( + args.token, args.repo, args.issue_number, args.author, args.reviewer + ) + pr_merge_on_behalf_information.run() elif args.command == "release-workflow": release_workflow = ReleaseWorkflow( args.token, |