blob: 09571ab020f1b1c2a82d1e1011c50c83f182b5cf [file] [log] [blame]
Anas Nashif20483162022-02-25 18:37:47 -05001#!/usr/bin/env python3
2
3# Copyright (c) 2022 Intel Corp.
4# SPDX-License-Identifier: Apache-2.0
5
6import argparse
7import sys
8import os
9import time
10import datetime
11from github import Github, GithubException
Anas Nashif60271522022-07-18 19:37:31 -040012from github.GithubException import UnknownObjectException
Anas Nashif20483162022-02-25 18:37:47 -050013from collections import defaultdict
14
15TOP_DIR = os.path.join(os.path.dirname(__file__))
16sys.path.insert(0, os.path.join(TOP_DIR, "scripts"))
17from get_maintainer import Maintainers
18
19def log(s):
20 if args.verbose > 0:
21 print(s, file=sys.stdout)
22
23def parse_args():
24 global args
25 parser = argparse.ArgumentParser(
26 description=__doc__,
Jamie McCraeec704442023-01-04 16:08:36 +000027 formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False)
Anas Nashif20483162022-02-25 18:37:47 -050028
29 parser.add_argument("-M", "--maintainer-file", required=False, default="MAINTAINERS.yml",
30 help="Maintainer file to be used.")
31 parser.add_argument("-P", "--pull_request", required=False, default=None, type=int,
32 help="Operate on one pull-request only.")
33 parser.add_argument("-s", "--since", required=False,
34 help="Process pull-requests since date.")
35
36 parser.add_argument("-y", "--dry-run", action="store_true", default=False,
37 help="Dry run only.")
38
39 parser.add_argument("-o", "--org", default="zephyrproject-rtos",
40 help="Github organisation")
41
42 parser.add_argument("-r", "--repo", default="zephyr",
43 help="Github repository")
44
45 parser.add_argument("-v", "--verbose", action="count", default=0,
46 help="Verbose Output")
47
48 args = parser.parse_args()
49
50def process_pr(gh, maintainer_file, number):
51
52 gh_repo = gh.get_repo(f"{args.org}/{args.repo}")
53 pr = gh_repo.get_pull(number)
54
55 log(f"working on https://github.com/{args.org}/{args.repo}/pull/{pr.number} : {pr.title}")
56
57 labels = set()
Anas Nashif20483162022-02-25 18:37:47 -050058 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)
Anas Nashif20483162022-02-25 18:37:47 -050077 for p in a.maintainers:
78 maint[p] += 1
79
80 ac = dict(sorted(area_counter.items(), key=lambda item: item[1], reverse=True))
81 log(f"Area matches: {ac}")
82 log(f"labels: {labels}")
Anas Nashif20483162022-02-25 18:37:47 -050083
Stephanos Ioannidisfaf42082022-10-20 21:51:03 +090084 # Create a list of collaborators ordered by the area match
85 collab = list()
86 for a in ac:
87 collab += maintainer_file.areas[a].maintainers
88 collab += maintainer_file.areas[a].collaborators
89 collab = list(dict.fromkeys(collab))
90 log(f"collab: {collab}")
91
Anas Nashif20483162022-02-25 18:37:47 -050092 sm = dict(sorted(maint.items(), key=lambda item: item[1], reverse=True))
93
94 log(f"Submitted by: {pr.user.login}")
95 log(f"candidate maintainers: {sm}")
96
Fabio Baltierid06450b2022-10-03 14:51:40 +000097 maintainer = "None"
98 maintainers = list(sm.keys())
99
Anas Nashif20483162022-02-25 18:37:47 -0500100 prop = 0
Fabio Baltierid06450b2022-10-03 14:51:40 +0000101 if maintainers:
102 maintainer = maintainers[0]
Anas Nashif20483162022-02-25 18:37:47 -0500103
104 if len(ac) > 1 and list(ac.values())[0] == list(ac.values())[1]:
Anas Nashif20483162022-02-25 18:37:47 -0500105 for aa in ac:
106 if 'Documentation' in aa:
107 log("++ With multiple areas of same weight including docs, take something else other than Documentation as the maintainer")
108 for a in all_areas:
Fabio Baltierid06450b2022-10-03 14:51:40 +0000109 if (a.name == aa and
110 a.maintainers and a.maintainers[0] == maintainer and
111 len(maintainers) > 1):
112 maintainer = maintainers[1]
Anas Nashif20483162022-02-25 18:37:47 -0500113 elif 'Platform' in aa:
Fabio Baltieri279ab432022-10-03 15:00:10 +0000114 log("++ Platform takes precedence over subsystem...")
Anas Nashif20483162022-02-25 18:37:47 -0500115 log(f"Set maintainer of area {aa}")
116 for a in all_areas:
117 if a.name == aa:
118 if a.maintainers:
119 maintainer = a.maintainers[0]
120 break
121
122
123 # if the submitter is the same as the maintainer, check if we have
124 # multiple maintainers
125 if pr.user.login == maintainer:
126 log("Submitter is same as Assignee, trying to find another assignee...")
127 aff = list(ac.keys())[0]
128 for a in all_areas:
129 if a.name == aff:
130 if len(a.maintainers) > 1:
131 maintainer = a.maintainers[1]
132 else:
133 log(f"This area has only one maintainer, keeping assignee as {maintainer}")
134
135 prop = (maint[maintainer] / num_files) * 100
136 if prop < 20:
137 maintainer = "None"
Fabio Baltierid06450b2022-10-03 14:51:40 +0000138
Anas Nashif20483162022-02-25 18:37:47 -0500139 log(f"Picked maintainer: {maintainer} ({prop:.2f}% ownership)")
140 log("+++++++++++++++++++++++++")
141
142 # Set labels
Fabio Baltieri16d723e2023-01-26 17:03:13 +0000143 if labels:
144 if len(labels) < 10:
145 for l in labels:
146 log(f"adding label {l}...")
147 if not args.dry_run:
148 pr.add_to_labels(l)
149 else:
150 log(f"Too many labels to be applied")
Anas Nashif20483162022-02-25 18:37:47 -0500151
152 if collab:
153 reviewers = []
154 existing_reviewers = set()
155
156 revs = pr.get_reviews()
157 for review in revs:
158 existing_reviewers.add(review.user)
159
160 rl = pr.get_review_requests()
161 page = 0
162 for r in rl:
163 existing_reviewers |= set(r.get_page(page))
164 page += 1
165
166 for c in collab:
Anas Nashif60271522022-07-18 19:37:31 -0400167 try:
168 u = gh.get_user(c)
169 if pr.user != u and gh_repo.has_in_collaborators(u):
170 if u not in existing_reviewers:
171 reviewers.append(c)
172 except UnknownObjectException as e:
173 log(f"Can't get user '{c}', account does not exist anymore? ({e})")
Anas Nashif20483162022-02-25 18:37:47 -0500174
Stephanos Ioannidisfaf42082022-10-20 21:51:03 +0900175 if len(existing_reviewers) < 15:
176 reviewer_vacancy = 15 - len(existing_reviewers)
177 reviewers = reviewers[:reviewer_vacancy]
178
179 if reviewers:
180 try:
181 log(f"adding reviewers {reviewers}...")
182 if not args.dry_run:
183 pr.create_review_request(reviewers=reviewers)
184 except GithubException:
185 log("cant add reviewer")
186 else:
187 log("not adding reviewers because the existing reviewer count is greater than or "
188 "equal to 15")
Anas Nashif20483162022-02-25 18:37:47 -0500189
190 ms = []
191 # assignees
Anas Nashifd63c2c42022-06-16 11:25:52 -0400192 if maintainer != 'None' and not pr.assignee:
Anas Nashif20483162022-02-25 18:37:47 -0500193 try:
194 u = gh.get_user(maintainer)
195 ms.append(u)
196 except GithubException:
197 log(f"Error: Unknown user")
198
199 for mm in ms:
200 log(f"Adding assignee {mm}...")
201 if not args.dry_run:
202 pr.add_to_assignees(mm)
Anas Nashifd63c2c42022-06-16 11:25:52 -0400203 else:
204 log("not setting assignee")
Anas Nashif20483162022-02-25 18:37:47 -0500205
206 time.sleep(1)
207
208def main():
209 parse_args()
210
211 token = os.environ.get('GITHUB_TOKEN', None)
212 if not token:
213 sys.exit('Github token not set in environment, please set the '
214 'GITHUB_TOKEN environment variable and retry.')
215
216 gh = Github(token)
217 maintainer_file = Maintainers(args.maintainer_file)
218
219 if args.pull_request:
220 process_pr(gh, maintainer_file, args.pull_request)
221 else:
222 if args.since:
223 since = args.since
224 else:
225 today = datetime.date.today()
226 since = today - datetime.timedelta(days=1)
227
228 common_prs = f'repo:{args.org}/{args.repo} is:open is:pr base:main -is:draft no:assignee created:>{since}'
229 pulls = gh.search_issues(query=f'{common_prs}')
230
231 for issue in pulls:
232 process_pr(gh, maintainer_file, issue.number)
233
234
235if __name__ == "__main__":
236 main()