aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Spickett <david.spickett@linaro.org>2024-02-13 14:52:02 +0000
committerGitHub <noreply@github.com>2024-02-13 14:52:02 +0000
commit38c706e30f5f339bfb0bfb26fd7b5c2d5086064a (patch)
tree15d136793d366cdc95f962beca640c1573b62a64
parent5e5e51e9062895bed9fcf0dbb157d868be0adf8d (diff)
downloadllvm-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.yml39
-rwxr-xr-xllvm/utils/git/github-automation.py65
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,