Skip to content

Commit de8577b

Browse files
nashifstephanosio
authored andcommitted
scripts: introduce script to set labels, assignees and reviewers
Use MAINTAINERS.yml file to set lables, assignees and reviewers for specific PRs or all unassigned PRs since a given date. Signed-off-by: Anas Nashif <[email protected]> Signed-off-by: Stephanos Ioannidis <[email protected]>
1 parent 868b180 commit de8577b

File tree

1 file changed

+215
-0
lines changed

1 file changed

+215
-0
lines changed

scripts/set_assignees.py

+215
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
#!/usr/bin/env python3
2+
3+
# Copyright (c) 2022 Intel Corp.
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
import argparse
7+
import sys
8+
import os
9+
import time
10+
import datetime
11+
from github import Github, GithubException
12+
from collections import defaultdict
13+
14+
TOP_DIR = os.path.join(os.path.dirname(__file__))
15+
sys.path.insert(0, os.path.join(TOP_DIR, "scripts"))
16+
from get_maintainer import Maintainers
17+
18+
def log(s):
19+
if args.verbose > 0:
20+
print(s, file=sys.stdout)
21+
22+
def parse_args():
23+
global args
24+
parser = argparse.ArgumentParser(
25+
description=__doc__,
26+
formatter_class=argparse.RawDescriptionHelpFormatter)
27+
28+
parser.add_argument("-M", "--maintainer-file", required=False, default="MAINTAINERS.yml",
29+
help="Maintainer file to be used.")
30+
parser.add_argument("-P", "--pull_request", required=False, default=None, type=int,
31+
help="Operate on one pull-request only.")
32+
parser.add_argument("-s", "--since", required=False,
33+
help="Process pull-requests since date.")
34+
35+
parser.add_argument("-y", "--dry-run", action="store_true", default=False,
36+
help="Dry run only.")
37+
38+
parser.add_argument("-o", "--org", default="zephyrproject-rtos",
39+
help="Github organisation")
40+
41+
parser.add_argument("-r", "--repo", default="zephyr",
42+
help="Github repository")
43+
44+
parser.add_argument("-v", "--verbose", action="count", default=0,
45+
help="Verbose Output")
46+
47+
args = parser.parse_args()
48+
49+
def process_pr(gh, maintainer_file, number):
50+
51+
gh_repo = gh.get_repo(f"{args.org}/{args.repo}")
52+
pr = gh_repo.get_pull(number)
53+
54+
log(f"working on https://github.com/{args.org}/{args.repo}/pull/{pr.number} : {pr.title}")
55+
56+
labels = set()
57+
collab = set()
58+
area_counter = defaultdict(int)
59+
maint = defaultdict(int)
60+
61+
num_files = 0
62+
all_areas = set()
63+
fn = list(pr.get_files())
64+
if len(fn) > 500:
65+
log(f"Too many files changed ({len(fn)}), skipping....")
66+
return
67+
for f in pr.get_files():
68+
num_files += 1
69+
log(f"file: {f.filename}")
70+
areas = maintainer_file.path2areas(f.filename)
71+
72+
if areas:
73+
all_areas.update(areas)
74+
for a in areas:
75+
area_counter[a.name] += 1
76+
labels.update(a.labels)
77+
collab.update(a.collaborators)
78+
collab.update(a.maintainers)
79+
for p in a.maintainers:
80+
maint[p] += 1
81+
82+
ac = dict(sorted(area_counter.items(), key=lambda item: item[1], reverse=True))
83+
log(f"Area matches: {ac}")
84+
log(f"labels: {labels}")
85+
log(f"collab: {collab}")
86+
if len(labels) > 10:
87+
log(f"Too many labels to be applied")
88+
return
89+
90+
sm = dict(sorted(maint.items(), key=lambda item: item[1], reverse=True))
91+
92+
log(f"Submitted by: {pr.user.login}")
93+
log(f"candidate maintainers: {sm}")
94+
95+
prop = 0
96+
if sm:
97+
maintainer = list(sm.keys())[0]
98+
99+
if len(ac) > 1 and list(ac.values())[0] == list(ac.values())[1]:
100+
log("++ Platform/Drivers takes precedence over subsystem...")
101+
for aa in ac:
102+
if 'Documentation' in aa:
103+
log("++ With multiple areas of same weight including docs, take something else other than Documentation as the maintainer")
104+
for a in all_areas:
105+
if a.name == aa and a.maintainers[0] == maintainer:
106+
maintainer = list(sm.keys())[1]
107+
elif 'Platform' in aa:
108+
log(f"Set maintainer of area {aa}")
109+
for a in all_areas:
110+
if a.name == aa:
111+
if a.maintainers:
112+
maintainer = a.maintainers[0]
113+
break
114+
115+
116+
# if the submitter is the same as the maintainer, check if we have
117+
# multiple maintainers
118+
if pr.user.login == maintainer:
119+
log("Submitter is same as Assignee, trying to find another assignee...")
120+
aff = list(ac.keys())[0]
121+
for a in all_areas:
122+
if a.name == aff:
123+
if len(a.maintainers) > 1:
124+
maintainer = a.maintainers[1]
125+
else:
126+
log(f"This area has only one maintainer, keeping assignee as {maintainer}")
127+
128+
prop = (maint[maintainer] / num_files) * 100
129+
if prop < 20:
130+
maintainer = "None"
131+
else:
132+
maintainer = "None"
133+
log(f"Picked maintainer: {maintainer} ({prop:.2f}% ownership)")
134+
log("+++++++++++++++++++++++++")
135+
136+
# Set labels
137+
if labels and len(labels) < 10:
138+
for l in labels:
139+
log(f"adding label {l}...")
140+
if not args.dry_run:
141+
pr.add_to_labels(l)
142+
143+
if collab:
144+
reviewers = []
145+
existing_reviewers = set()
146+
147+
revs = pr.get_reviews()
148+
for review in revs:
149+
existing_reviewers.add(review.user)
150+
151+
rl = pr.get_review_requests()
152+
page = 0
153+
for r in rl:
154+
existing_reviewers |= set(r.get_page(page))
155+
page += 1
156+
157+
for c in collab:
158+
u = gh.get_user(c)
159+
if pr.user != u and gh_repo.has_in_collaborators(u):
160+
if u not in existing_reviewers:
161+
reviewers.append(c)
162+
163+
if reviewers:
164+
try:
165+
log(f"adding reviewers {reviewers}...")
166+
if not args.dry_run:
167+
pr.create_review_request(reviewers=reviewers)
168+
except GithubException:
169+
log("cant add reviewer")
170+
171+
ms = []
172+
# assignees
173+
if maintainer != 'None':
174+
try:
175+
u = gh.get_user(maintainer)
176+
ms.append(u)
177+
except GithubException:
178+
log(f"Error: Unknown user")
179+
180+
for mm in ms:
181+
log(f"Adding assignee {mm}...")
182+
if not args.dry_run:
183+
pr.add_to_assignees(mm)
184+
185+
time.sleep(1)
186+
187+
def main():
188+
parse_args()
189+
190+
token = os.environ.get('GITHUB_TOKEN', None)
191+
if not token:
192+
sys.exit('Github token not set in environment, please set the '
193+
'GITHUB_TOKEN environment variable and retry.')
194+
195+
gh = Github(token)
196+
maintainer_file = Maintainers(args.maintainer_file)
197+
198+
if args.pull_request:
199+
process_pr(gh, maintainer_file, args.pull_request)
200+
else:
201+
if args.since:
202+
since = args.since
203+
else:
204+
today = datetime.date.today()
205+
since = today - datetime.timedelta(days=1)
206+
207+
common_prs = f'repo:{args.org}/{args.repo} is:open is:pr base:main -is:draft no:assignee created:>{since}'
208+
pulls = gh.search_issues(query=f'{common_prs}')
209+
210+
for issue in pulls:
211+
process_pr(gh, maintainer_file, issue.number)
212+
213+
214+
if __name__ == "__main__":
215+
main()

0 commit comments

Comments
 (0)