Skip to content

[GitHub] Add workflow to check author's commit access on new PRs #123593

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/new-prs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,31 @@ jobs:
# workaround for https://github.com/actions/labeler/issues/112
sync-labels: ''
repo-token: ${{ secrets.ISSUE_SUBSCRIBER_TOKEN }}

check-commit-access:
runs-on: ubuntu-latest
permissions:
pull-requests: write
if: >-
(github.repository == 'llvm/llvm-project') &&
(github.event.action == 'opened')
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 --require-hashes -r requirements.txt

- name: Check Commit Access
working-directory: ./llvm/utils/git/
run: |
python3 ./github-automation.py \
--token '${{ secrets.GITHUB_TOKEN }}' \
check-commit-access \
--issue-number "${{ github.event.pull_request.number }}" \
--author "${{ github.event.pull_request.user.login }}"
35 changes: 35 additions & 0 deletions llvm/utils/git/github-automation.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,32 @@ def run(self) -> bool:
return True


class CheckCommitAccess:
def __init__(self, token: str, repo: str, pr_number: int, author: str):
self.repo = github.Github(token).get_repo(repo)
self.pr = self.repo.get_issue(pr_number).as_pull_request()
self.author = author

def can_merge(self, user: str) -> bool:
try:
return self.repo.get_collaborator_permission(user) in ["admin", "write"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably plenty sufficient, but there's also the collaborators endpoint that enumerates explicit permissions like push: https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2022-11-28#list-repository-collaborators

It does however require paginating through the list so this is probably kinder to the rate limits

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not know about this, however I don't think we can use it here.https://pygithub.readthedocs.io/en/stable/github_objects/Repository.html?highlight=get_collaborator_permission#github.Repository.Repository.get_collaborators returns a list of all collaborators in the form of https://pygithub.readthedocs.io/en/stable/github_objects/NamedUser.html#github.NamedUser.NamedUser, which doesn't have a way to get to the permissions.

I see existing code on GitHub mainly using get_collaborators to get a list of usernames, then passing those one by one to get_collaborator_permission. I presume in workflows that cover the whole project not just one PR, here we just have them to check.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting that pygithub truncates so much of the response 🤔

I would say the current solution is good to go then.

# 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:
if not self.can_merge(self.author):
self.pr.as_issue().add_to_labels("no-commit-access")

return True


def setup_llvmbot_git(git_dir="."):
"""
Configure the git repo in `git_dir` with the llvmbot account so
Expand Down Expand Up @@ -680,6 +706,10 @@ def request_release_note(token: str, repo_name: str, pr_number: int):
pr_buildbot_information_parser.add_argument("--issue-number", type=int, required=True)
pr_buildbot_information_parser.add_argument("--author", type=str, required=True)

check_commit_access_parser = subparsers.add_parser("check-commit-access")
check_commit_access_parser.add_argument("--issue-number", type=int, required=True)
check_commit_access_parser.add_argument("--author", type=str, required=True)

release_workflow_parser = subparsers.add_parser("release-workflow")
release_workflow_parser.add_argument(
"--llvm-project-dir",
Expand Down Expand Up @@ -751,6 +781,11 @@ def request_release_note(token: str, repo_name: str, pr_number: int):
args.token, args.repo, args.issue_number, args.author
)
pr_buildbot_information.run()
elif args.command == "check-commit-access":
check_commit_access = CheckCommitAccess(
args.token, args.repo, args.issue_number, args.author
)
check_commit_access.run()
elif args.command == "release-workflow":
release_workflow = ReleaseWorkflow(
args.token,
Expand Down
Loading