[llvm] [GitHub][workflows] Ask reviewers to merge PRs when author can not (PR #81142)

David Spickett via llvm-commits llvm-commits at lists.llvm.org
Thu Feb 8 06:53:01 PST 2024


https://github.com/DavidSpickett updated https://github.com/llvm/llvm-project/pull/81142

>From 1ec430a500cda99a408693f643fa452b72dad79e Mon Sep 17 00:00:00 2001
From: David Spickett <david.spickett at linaro.org>
Date: Fri, 2 Feb 2024 16:15:48 +0000
Subject: [PATCH 1/2] [GitHub][workflows] Ask reviewers to merge PRs when
 author can not

This is based on GitHub's examples:
https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#running-a-workflow-when-a-pull-request-is-approved
https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2022-11-28#get-repository-permissions-for-a-user

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 (using a REST API call).

The comment doesn't ask the reviewers to merge it right away, just in
case the author still had things to do. As we don't have a norm of merging as
soon as there is an approval, so doing that without asking might be surprising.

It also notes that if we need multiple approvals to wait for those.
Though in that situation I don't think GitHub will enable the merge
button until they've all approved anyway.

GitHub does have limits for the REST API:
https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28

And I've made some rough assumptions based on there being 37900 commits in the tree
Jan 2023 to Jan 2024. If we assumed every one of those was a PR (they weren't) that would be
roughly 4 per hour.

I'm not sure if llvm would be using the personal rate:
"All of these requests count towards your personal rate limit of 5,000 requests per hour."
Or the higher enterprise rate:
"Requests made on your behalf by a GitHub App that is owned by a GitHub Enterprise Cloud organization have a higher rate limit of 15,000 requests per hour."

If we assume the lower limit, that's 5000 approvals per hour. Assuming ~2 approval events per PR that's 2500 per hour we
can run this job on.

There are secondary limits too.

"No more than 100 concurrent requests are allowed."

Seems unlikely we would hit this given that we'd have to have 100 approval
events that managed to get scheduled at the exact same time on the runners.

"No more than 900 points per minute are allowed for REST API endpoints"

The request here is 1 point, so we'd have to have 900 approval events in one minute, not likely.

"In general, no more than 80 content-generating requests per minute and no more than 500 content-generating requests per hour are allowed."

We are only reading permissions, so this isn't an issue. Leaving the comment, the majority of PRs won't need a comment anyway.

In case of issues with the API I have written the check to assume the author has permission
should anything go wrong. This means we default to not leaving any comments.
---
 .github/workflows/approved-prs.yml  | 38 +++++++++++++
 llvm/utils/git/github-automation.py | 84 +++++++++++++++++++++++++++++
 2 files changed, 122 insertions(+)
 create mode 100644 .github/workflows/approved-prs.yml

diff --git a/.github/workflows/approved-prs.yml b/.github/workflows/approved-prs.yml
new file mode 100644
index 00000000000000..6a037f7aead0e9
--- /dev/null
+++ b/.github/workflows/approved-prs.yml
@@ -0,0 +1,38 @@
+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 at 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 }}"
diff --git a/llvm/utils/git/github-automation.py b/llvm/utils/git/github-automation.py
index 04698cacbff929..50475916144c63 100755
--- a/llvm/utils/git/github-automation.py
+++ b/llvm/utils/git/github-automation.py
@@ -11,6 +11,7 @@
 import argparse
 from git import Repo  # type: ignore
 import html
+import json
 import github
 import os
 import re
@@ -298,6 +299,76 @@ def run(self) -> bool:
         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):
+        repo = github.Github(token).get_repo(repo)
+        self.pr = repo.get_issue(pr_number).as_pull_request()
+        self.author = author
+        self.repo = repo
+        self.token = token
+
+    def author_has_push_permission(self):
+        # https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2022-11-28#get-repository-permissions-for-a-user
+        response = requests.get(
+            # Where repo is "owner/repo-name".
+            f"https://api.github.com/repos/{self.repo}/collaborators/{self.author}/permission",
+            headers={
+                "Accept": "application/vnd.github+json",
+                "Authorization": f"Bearer {self.token}",
+                "X-GitHub-Api-Version": "2022-11-28",
+            },
+        )
+
+        # 404 means this user is not a collaborator.
+        if response.status_code == 404:
+            # Does not have push permission if not a collaborator.
+            return False
+        # User is a collaborator.
+        elif response.status_code == 200:
+            user_details = json.loads(response.text)
+            user = user_details["user"]
+
+            # We may have a list of permissions.
+            if permissions := user.get("permissions"):
+                return permissions["pull"]
+            else:
+                # Otherwise we can always fall back to the permission
+                # on the top level object. The other permissions "read" and
+                # "none" cannot push changes.
+                return user_details["permisson"] in ["admin", "write"]
+        else:
+            # Something went wrong, log and carry on.
+            print("Unexpected response code", response.status_code)
+            # Assume they do have push permissions, so that we don't spam
+            # PRs with comments if there are API problems.
+            return True
+
+    def run(self) -> bool:
+        # A review can be approved more than once, only comment the first time.
+        # Doing this check first as I'm assuming we get the comment data "free" in
+        # terms of API cost.
+        for comment in self.pr.as_issue().get_comments():
+            if self.COMMENT_TAG in comment.body:
+                return
+
+        # Now check whether the author has permissions needed to merge, which
+        # uses a REST API call.
+        if self.author_has_push_permission():
+            return
+
+        # This text is using Markdown formatting.
+        comment = f"""\
+{self.COMMENT_TAG}
+@{self.author}, you do not have permissions to merge your own PRs yet. Please let us know when you are happy for this to be merged, and one of the reviewers can merge it on your behalf.
+
+(if many approvals are required, please wait until everyone has approved before merging)
+"""
+        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
@@ -647,6 +718,14 @@ def execute_command(self) -> bool:
 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)
+
 release_workflow_parser = subparsers.add_parser("release-workflow")
 release_workflow_parser.add_argument(
     "--llvm-project-dir",
@@ -700,6 +779,11 @@ def execute_command(self) -> bool:
         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
+    )
+    pr_merge_on_behalf_information.run()
 elif args.command == "release-workflow":
     release_workflow = ReleaseWorkflow(
         args.token,

>From 63019af882555e668c17bac1136bbdedcca316c5 Mon Sep 17 00:00:00 2001
From: David Spickett <david.spickett at linaro.org>
Date: Thu, 8 Feb 2024 14:52:44 +0000
Subject: [PATCH 2/2] grammar

---
 llvm/utils/git/github-automation.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/utils/git/github-automation.py b/llvm/utils/git/github-automation.py
index 50475916144c63..a79e048df73066 100755
--- a/llvm/utils/git/github-automation.py
+++ b/llvm/utils/git/github-automation.py
@@ -361,7 +361,7 @@ def run(self) -> bool:
         # This text is using Markdown formatting.
         comment = f"""\
 {self.COMMENT_TAG}
-@{self.author}, you do not have permissions to merge your own PRs yet. Please let us know when you are happy for this to be merged, and one of the reviewers can merge it on your behalf.
+@{self.author} you do not have permissions to merge your own PRs yet. Please let us know when you are happy for this to be merged, and one of the reviewers can merge it on your behalf.
 
 (if many approvals are required, please wait until everyone has approved before merging)
 """



More information about the llvm-commits mailing list