blob: 9805e2e9749f7e81e0a53600e8c24cebaabdb1fe [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
12from collections import defaultdict
13
14TOP_DIR = os.path.join(os.path.dirname(__file__))
15sys.path.insert(0, os.path.join(TOP_DIR, "scripts"))
16from get_maintainer import Maintainers
17
18def log(s):
19 if args.verbose > 0:
20 print(s, file=sys.stdout)
21
22def 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
49def 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
Anas Nashifd63c2c42022-06-16 11:25:52 -0400173 if maintainer != 'None' and not pr.assignee:
Anas Nashif20483162022-02-25 18:37:47 -0500174 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)
Anas Nashifd63c2c42022-06-16 11:25:52 -0400184 else:
185 log("not setting assignee")
Anas Nashif20483162022-02-25 18:37:47 -0500186
187 time.sleep(1)
188
189def main():
190 parse_args()
191
192 token = os.environ.get('GITHUB_TOKEN', None)
193 if not token:
194 sys.exit('Github token not set in environment, please set the '
195 'GITHUB_TOKEN environment variable and retry.')
196
197 gh = Github(token)
198 maintainer_file = Maintainers(args.maintainer_file)
199
200 if args.pull_request:
201 process_pr(gh, maintainer_file, args.pull_request)
202 else:
203 if args.since:
204 since = args.since
205 else:
206 today = datetime.date.today()
207 since = today - datetime.timedelta(days=1)
208
209 common_prs = f'repo:{args.org}/{args.repo} is:open is:pr base:main -is:draft no:assignee created:>{since}'
210 pulls = gh.search_issues(query=f'{common_prs}')
211
212 for issue in pulls:
213 process_pr(gh, maintainer_file, issue.number)
214
215
216if __name__ == "__main__":
217 main()