-
Notifications
You must be signed in to change notification settings - Fork 31.7k
Github action for auto-assigning reviewers #35846
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
Changes from all commits
90a1b1a
38e0b26
469c0f9
00bddf3
51df024
fff0d86
996cbd8
77aede4
befc35b
6e04a1e
0f4b893
385aef8
afb28ad
8e58115
4ea22e1
e6a1085
caa88bb
319ad2b
3c2bd13
ccb865b
ca82e75
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| # coding=utf-8 | ||
| # Copyright 2025 the HuggingFace Inc. team. All rights reserved. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| import os | ||
| import github | ||
| import json | ||
| from github import Github | ||
| from fnmatch import fnmatch | ||
| from collections import Counter | ||
| from pathlib import Path | ||
|
|
||
| def get_file_owners(file_path, codeowners_lines): | ||
| # Process lines in reverse (last matching pattern takes precedence) | ||
| for line in reversed(codeowners_lines): | ||
| # Skip comments and empty lines, strip inline comments | ||
| line = line.split('#')[0].strip() | ||
| if not line: | ||
| continue | ||
|
|
||
| # Split into pattern and owners | ||
| parts = line.split() | ||
| pattern = parts[0] | ||
| # Can be empty, e.g. for dummy files with explicitly no owner! | ||
| owners = [owner.removeprefix("@") for owner in parts[1:]] | ||
|
|
||
| # Check if file matches pattern | ||
| if fnmatch(file_path, pattern): | ||
| return owners # Remember, can still be empty! | ||
| return [] # Should never happen, but just in case | ||
|
|
||
| def main(): | ||
| g = Github(os.environ['GITHUB_TOKEN']) | ||
| repo = g.get_repo("huggingface/transformers") | ||
| with open(os.environ['GITHUB_EVENT_PATH']) as f: | ||
| event = json.load(f) | ||
| script_dir = Path(__file__).parent.absolute() | ||
| with open(script_dir / "codeowners_for_review_action") as f: | ||
| codeowners_lines = f.readlines() | ||
|
|
||
| # The PR number is available in the event payload | ||
| pr_number = event['pull_request']['number'] | ||
| pr = repo.get_pull(pr_number) | ||
| pr_author = pr.user.login | ||
|
|
||
| existing_reviews = list(pr.get_reviews()) | ||
| if existing_reviews: | ||
| print(f"Already has reviews: {[r.user.login for r in existing_reviews]}") | ||
| return | ||
|
|
||
| users_requested, teams_requested = pr.get_review_requests() | ||
| users_requested = list(users_requested) | ||
| if users_requested: | ||
| print(f"Reviewers already requested: {users_requested}") | ||
| return | ||
|
|
||
| locs_per_owner = Counter() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My bad, I somehow missed this until now! LOCs = lines of code
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe i am just not familiar with common abbreviation here. But if you think it would help in general / in the future, let's add a comment about the meaning. |
||
| for file in pr.get_files(): | ||
| owners = get_file_owners(file.filename, codeowners_lines) | ||
| for owner in owners: | ||
| locs_per_owner[owner] += file.changes | ||
|
|
||
| # Assign the top 2 based on locs changed as reviewers, but skip the owner if present | ||
| locs_per_owner.pop(pr_author, None) | ||
| top_owners = locs_per_owner.most_common(2) | ||
| print("Top owners", top_owners) | ||
| top_owners = [owner[0] for owner in top_owners] | ||
| try: | ||
| pr.create_review_request(top_owners) | ||
| except github.GithubException as e: | ||
| print(f"Failed to request review for {top_owners}: {e}") | ||
|
|
||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing licence
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and potentially doc helper