Skip to content

Commit 5ad0984

Browse files
committed
ci: add member check
Signed-off-by: Anas Nashif <anas.nashif@intel.com>
1 parent 34e47f4 commit 5ad0984

File tree

2 files changed

+171
-0
lines changed

2 files changed

+171
-0
lines changed

.github/workflows/assigner.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,10 @@ jobs:
6262
fi
6363
6464
python3 scripts/set_assignees.py $FLAGS
65+
66+
- name: Check maintainer file changes
67+
if: github.event_name == 'pull_request_target'
68+
run: |
69+
c = ${{ github.event.pull_request.base.sha }}
70+
git show ${c}:MAINTAINERS.yml > old_MAINTAINER.yml
71+
python ./scripts/check_maintainer_changes.py --repo zephyrproject-rtos/zephyr old_MAINTAINER.yml MAINTAINERS.yml

scripts/check_maintainer_changes.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
import os
5+
import yaml
6+
import argparse
7+
from github import Github
8+
9+
def load_areas(filename: str):
10+
with open(filename, "r") as f:
11+
doc = yaml.safe_load(f)
12+
return {k: v for k, v in doc.items() if isinstance(v, dict) and ("files" in v or "files-regex" in v)}
13+
14+
def set_or_empty(d, key):
15+
return set(d.get(key, []) or [])
16+
17+
def check_github_access(usernames, repo_fullname, token):
18+
"""Check if each username has at least Triage access to the repo."""
19+
gh = Github(token)
20+
repo = gh.get_repo(repo_fullname)
21+
missing_access = set()
22+
for username in usernames:
23+
try:
24+
collab = repo.get_collaborator_permission(username)
25+
# Permissions: admin, maintain, write, triage, read
26+
if collab not in ("admin", "maintain", "write", "triage"):
27+
missing_access.add(username)
28+
except Exception:
29+
missing_access.add(username)
30+
return missing_access
31+
32+
def compare_areas(old, new, repo_fullname=None, token=None):
33+
old_areas = set(old.keys())
34+
new_areas = set(new.keys())
35+
36+
added_areas = new_areas - old_areas
37+
removed_areas = old_areas - new_areas
38+
common_areas = old_areas & new_areas
39+
40+
all_added_maintainers = set()
41+
all_added_collaborators = set()
42+
43+
print("=== Areas Added ===")
44+
for area in sorted(added_areas):
45+
print(f"+ {area}")
46+
entry = new[area]
47+
all_added_maintainers.update(set_or_empty(entry, "maintainers"))
48+
all_added_collaborators.update(set_or_empty(entry, "collaborators"))
49+
50+
print("\n=== Areas Removed ===")
51+
for area in sorted(removed_areas):
52+
print(f"- {area}")
53+
54+
print("\n=== Area Changes ===")
55+
for area in sorted(common_areas):
56+
changes = []
57+
old_entry = old[area]
58+
new_entry = new[area]
59+
60+
# Compare maintainers
61+
old_maint = set_or_empty(old_entry, "maintainers")
62+
new_maint = set_or_empty(new_entry, "maintainers")
63+
added_maint = new_maint - old_maint
64+
removed_maint = old_maint - new_maint
65+
if added_maint:
66+
changes.append(f" Maintainers added: {', '.join(sorted(added_maint))}")
67+
all_added_maintainers.update(added_maint)
68+
if removed_maint:
69+
changes.append(f" Maintainers removed: {', '.join(sorted(removed_maint))}")
70+
71+
# Compare collaborators
72+
old_collab = set_or_empty(old_entry, "collaborators")
73+
new_collab = set_or_empty(new_entry, "collaborators")
74+
added_collab = new_collab - old_collab
75+
removed_collab = old_collab - new_collab
76+
if added_collab:
77+
changes.append(f" Collaborators added: {', '.join(sorted(added_collab))}")
78+
all_added_collaborators.update(added_collab)
79+
if removed_collab:
80+
changes.append(f" Collaborators removed: {', '.join(sorted(removed_collab))}")
81+
82+
# Compare status
83+
old_status = old_entry.get("status")
84+
new_status = new_entry.get("status")
85+
if old_status != new_status:
86+
changes.append(f" Status changed: {old_status} -> {new_status}")
87+
88+
# Compare labels
89+
old_labels = set_or_empty(old_entry, "labels")
90+
new_labels = set_or_empty(new_entry, "labels")
91+
added_labels = new_labels - old_labels
92+
removed_labels = old_labels - new_labels
93+
if added_labels:
94+
changes.append(f" Labels added: {', '.join(sorted(added_labels))}")
95+
if removed_labels:
96+
changes.append(f" Labels removed: {', '.join(sorted(removed_labels))}")
97+
98+
# Compare files
99+
old_files = set_or_empty(old_entry, "files")
100+
new_files = set_or_empty(new_entry, "files")
101+
added_files = new_files - old_files
102+
removed_files = old_files - new_files
103+
if added_files:
104+
changes.append(f" Files added: {', '.join(sorted(added_files))}")
105+
if removed_files:
106+
changes.append(f" Files removed: {', '.join(sorted(removed_files))}")
107+
108+
# Compare files-regex
109+
old_regex = set_or_empty(old_entry, "files-regex")
110+
new_regex = set_or_empty(new_entry, "files-regex")
111+
added_regex = new_regex - old_regex
112+
removed_regex = old_regex - new_regex
113+
if added_regex:
114+
changes.append(f" files-regex added: {', '.join(sorted(added_regex))}")
115+
if removed_regex:
116+
changes.append(f" files-regex removed: {', '.join(sorted(removed_regex))}")
117+
118+
if changes:
119+
print(f"* {area}")
120+
for c in changes:
121+
print(c)
122+
123+
print("\n=== Summary ===")
124+
print(f"Total areas added: {len(added_areas)}")
125+
print(f"Total maintainers added: {len(all_added_maintainers)}")
126+
if all_added_maintainers:
127+
print(" Added maintainers: " + ", ".join(sorted(all_added_maintainers)))
128+
print(f"Total collaborators added: {len(all_added_collaborators)}")
129+
if all_added_collaborators:
130+
print(" Added collaborators: " + ", ".join(sorted(all_added_collaborators)))
131+
132+
# Check GitHub access if repo and token are provided
133+
if repo_fullname and token:
134+
print("\n=== GitHub Access Check ===")
135+
missing_maint = check_github_access(all_added_maintainers, repo_fullname, token)
136+
missing_collab = check_github_access(all_added_collaborators, repo_fullname, token)
137+
if missing_maint:
138+
print("Maintainers without at least triage access:")
139+
for u in sorted(missing_maint):
140+
print(f" - {u}")
141+
if missing_collab:
142+
print("Collaborators without at least triage access:")
143+
for u in sorted(missing_collab):
144+
print(f" - {u}")
145+
if not missing_maint and not missing_collab:
146+
print("All added maintainers and collaborators have at least triage access.")
147+
148+
def main():
149+
parser = argparse.ArgumentParser(
150+
description="Compare two MAINTAINERS.yml files and show changes in areas, maintainers, collaborators, etc."
151+
)
152+
parser.add_argument("old", help="Old MAINTAINERS.yml file")
153+
parser.add_argument("new", help="New MAINTAINERS.yml file")
154+
parser.add_argument("--repo", help="GitHub repository in org/repo format for access check")
155+
parser.add_argument("--token", help="GitHub token for API access (required for access check)")
156+
args = parser.parse_args()
157+
158+
old_areas = load_areas(args.old)
159+
new_areas = load_areas(args.new)
160+
token = os.environ.get("GITHUB_TOKEN") or args.token
161+
compare_areas(old_areas, new_areas, repo_fullname=args.repo, token=token)
162+
163+
if __name__ == "__main__":
164+
main()

0 commit comments

Comments
 (0)