blob: 15564f06fd11a04fce5463cebb72738c4b5f42c8 [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__,
27 formatter_class=argparse.RawDescriptionHelpFormatter)
28
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 if len(labels) > 10:
84 log(f"Too many labels to be applied")
85 return
86
Stephanos Ioannidisfaf42082022-10-20 21:51:03 +090087 # Create a list of collaborators ordered by the area match
88 collab = list()
89 for a in ac:
90 collab += maintainer_file.areas[a].maintainers
91 collab += maintainer_file.areas[a].collaborators
92 collab = list(dict.fromkeys(collab))
93 log(f"collab: {collab}")
94
Anas Nashif20483162022-02-25 18:37:47 -050095 sm = dict(sorted(maint.items(), key=lambda item: item[1], reverse=True))
96
97 log(f"Submitted by: {pr.user.login}")
98 log(f"candidate maintainers: {sm}")
99
Fabio Baltierid06450b2022-10-03 14:51:40 +0000100 maintainer = "None"
101 maintainers = list(sm.keys())
102
Anas Nashif20483162022-02-25 18:37:47 -0500103 prop = 0
Fabio Baltierid06450b2022-10-03 14:51:40 +0000104 if maintainers:
105 maintainer = maintainers[0]
Anas Nashif20483162022-02-25 18:37:47 -0500106
107 if len(ac) > 1 and list(ac.values())[0] == list(ac.values())[1]:
Anas Nashif20483162022-02-25 18:37:47 -0500108 for aa in ac:
109 if 'Documentation' in aa:
110 log("++ With multiple areas of same weight including docs, take something else other than Documentation as the maintainer")
111 for a in all_areas:
Fabio Baltierid06450b2022-10-03 14:51:40 +0000112 if (a.name == aa and
113 a.maintainers and a.maintainers[0] == maintainer and
114 len(maintainers) > 1):
115 maintainer = maintainers[1]
Anas Nashif20483162022-02-25 18:37:47 -0500116 elif 'Platform' in aa:
Fabio Baltieri279ab432022-10-03 15:00:10 +0000117 log("++ Platform takes precedence over subsystem...")
Anas Nashif20483162022-02-25 18:37:47 -0500118 log(f"Set maintainer of area {aa}")
119 for a in all_areas:
120 if a.name == aa:
121 if a.maintainers:
122 maintainer = a.maintainers[0]
123 break
124
125
126 # if the submitter is the same as the maintainer, check if we have
127 # multiple maintainers
128 if pr.user.login == maintainer:
129 log("Submitter is same as Assignee, trying to find another assignee...")
130 aff = list(ac.keys())[0]
131 for a in all_areas:
132 if a.name == aff:
133 if len(a.maintainers) > 1:
134 maintainer = a.maintainers[1]
135 else:
136 log(f"This area has only one maintainer, keeping assignee as {maintainer}")
137
138 prop = (maint[maintainer] / num_files) * 100
139 if prop < 20:
140 maintainer = "None"
Fabio Baltierid06450b2022-10-03 14:51:40 +0000141
Anas Nashif20483162022-02-25 18:37:47 -0500142 log(f"Picked maintainer: {maintainer} ({prop:.2f}% ownership)")
143 log("+++++++++++++++++++++++++")
144
145 # Set labels
146 if labels and len(labels) < 10:
147 for l in labels:
148 log(f"adding label {l}...")
149 if not args.dry_run:
150 pr.add_to_labels(l)
151
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()