Skip to content

Commit a56d14d

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

File tree

3 files changed

+208
-0
lines changed

3 files changed

+208
-0
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Maintainer file check
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
- collab-*
8+
- v*-branch
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
assignment:
15+
name: Pull Request Assignment
16+
runs-on: ubuntu-24.04
17+
18+
steps:
19+
- name: Check out source code
20+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
21+
22+
- name: Set up Python
23+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
24+
with:
25+
python-version: 3.12
26+
cache: pip
27+
cache-dependency-path: scripts/requirements-actions.txt
28+
29+
- name: Install Python packages
30+
run: |
31+
pip install -r scripts/requirements-actions.txt --require-hashes
32+
33+
- name: Fetch MAINTAINERS.yml from mainline
34+
run: |
35+
git fetch origin main
36+
git show origin/main:MAINTAINERS.yml > mainline_MAINTAINERS.yml
37+
38+
- name: Check maintainer file changes
39+
env:
40+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41+
run: |
42+
python ./scripts/check_maintainer_changes.py --repo zephyrproject-rtos/zephyr-testing mainline_MAINTAINERS.yml MAINTAINERS.yml

MAINTAINERS.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ BeagleBoard Platforms:
329329
- con-pax
330330
- vaishnavachath
331331
- glneo
332+
- dcpleung
332333
files:
333334
- boards/beagle/
334335
labels:
@@ -5871,6 +5872,7 @@ zbus:
58715872
collaborators:
58725873
- lyakh
58735874
- pillo79
5875+
- nashif
58745876
files:
58755877
- cmake/llext-edk.cmake
58765878
- samples/subsys/llext/

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)