2 # ===-- commit-access-review.py --------------------------------------------===#
4 # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5 # See https://llvm.org/LICENSE.txt for license information.
6 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8 # ===------------------------------------------------------------------------===#
10 # ===------------------------------------------------------------------------===#
24 def __init__(self
, name
, triage_list
):
29 self
.triage_list
= triage_list
31 def add_authored(self
, val
=1):
33 if self
.meets_threshold():
34 print(self
.name
, "meets the threshold with authored commits")
35 del self
.triage_list
[self
.name
]
37 def set_authored(self
, val
):
39 self
.add_authored(val
)
41 def add_merged(self
, val
=1):
43 if self
.meets_threshold():
44 print(self
.name
, "meets the threshold with merged commits")
45 del self
.triage_list
[self
.name
]
47 def add_reviewed(self
, val
=1):
49 if self
.meets_threshold():
50 print(self
.name
, "meets the threshold with reviewed commits")
51 del self
.triage_list
[self
.name
]
54 return self
.authored
+ self
.merged
+ self
.reviewed
56 def meets_threshold(self
):
57 return self
.get_total() >= self
.THRESHOLD
60 return "{} : a: {} m: {} r: {}".format(
61 self
.name
, self
.authored
, self
.merged
, self
.reviewed
65 def check_manual_requests(
66 gh
: github
.Github
, start_date
: datetime
.datetime
69 Return a list of users who have been asked since ``start_date`` if they
70 want to keep their commit access.
73 query ($query: String!) {
74 search(query: $query, type: ISSUE, first: 100) {
78 comments (first: 100) {
90 formatted_start_date
= start_date
.strftime("%Y-%m-%dT%H:%M:%S")
92 "query": f
"type:issue created:>{formatted_start_date} org:llvm repo:llvm-project label:infra:commit-access"
95 res_header
, res_data
= gh
._Github
__requester
.graphql_query(
96 query
=query
, variables
=variables
98 data
= res_data
["data"]
100 for issue
in data
["search"]["nodes"]:
101 users
.extend([user
[1:] for user
in re
.findall("@[^ ,\n]+", issue
["body"])])
106 def get_num_commits(gh
: github
.Github
, user
: str, start_date
: datetime
.datetime
) -> int:
108 Get number of commits that ``user`` has been made since ``start_date`.
113 "start_date": start_date
.strftime("%Y-%m-%dT%H:%M:%S"),
117 query ($user: String!) {
124 res_header
, res_data
= gh
._Github
__requester
.graphql_query(
125 query
=user_query
, variables
=variables
127 data
= res_data
["data"]
128 variables
["user_id"] = data
["user"]["id"]
131 query ($owner: String!, $user_id: ID!, $start_date: GitTimestamp!){
132 organization(login: $owner) {
133 teams(query: "llvm-committers" first:1) {
137 ref(qualifiedName: "main") {
140 history(since: $start_date, author: {id: $user_id }) {
154 res_header
, res_data
= gh
._Github
__requester
.graphql_query(
155 query
=query
, variables
=variables
157 data
= res_data
["data"]
158 for repo
in data
["organization"]["teams"]["nodes"][0]["repositories"]["nodes"]:
159 count
+= int(repo
["ref"]["target"]["history"]["totalCount"])
160 if count
>= User
.THRESHOLD
:
165 def is_new_committer_query_repo(
166 gh
: github
.Github
, user
: str, start_date
: datetime
.datetime
169 Determine if ``user`` is a new committer. A new committer can keep their
170 commit access even if they don't meet the criteria.
177 query ($user: String!) {
184 res_header
, res_data
= gh
._Github
__requester
.graphql_query(
185 query
=user_query
, variables
=variables
187 data
= res_data
["data"]
188 variables
["owner"] = "llvm"
189 variables
["user_id"] = data
["user"]["id"]
190 variables
["start_date"] = start_date
.strftime("%Y-%m-%dT%H:%M:%S")
193 query ($owner: String!, $user_id: ID!){
194 organization(login: $owner) {
195 repository(name: "llvm-project") {
196 ref(qualifiedName: "main") {
199 history(author: {id: $user_id }, first: 5) {
212 res_header
, res_data
= gh
._Github
__requester
.graphql_query(
213 query
=query
, variables
=variables
215 data
= res_data
["data"]
216 repo
= data
["organization"]["repository"]
217 commits
= repo
["ref"]["target"]["history"]["nodes"]
218 if len(commits
) == 0:
220 committed_date
= commits
[-1]["committedDate"]
221 if datetime
.datetime
.strptime(committed_date
, "%Y-%m-%dT%H:%M:%SZ") < start_date
:
226 def is_new_committer(
227 gh
: github
.Github
, user
: str, start_date
: datetime
.datetime
230 Wrapper around is_new_commiter_query_repo to handle exceptions.
233 return is_new_committer_query_repo(gh
, user
, start_date
)
239 def get_review_count(
240 gh
: github
.Github
, user
: str, start_date
: datetime
.datetime
243 Return the number of reviews that ``user`` has done since ``start_date``.
246 query ($query: String!) {
247 search(query: $query, type: ISSUE, first: 5) {
252 formatted_start_date
= start_date
.strftime("%Y-%m-%dT%H:%M:%S")
255 "repo": "llvm-project",
257 "query": f
"type:pr commenter:{user} -author:{user} merged:>{formatted_start_date} org:llvm",
260 res_header
, res_data
= gh
._Github
__requester
.graphql_query(
261 query
=query
, variables
=variables
263 data
= res_data
["data"]
264 return int(data
["search"]["issueCount"])
267 def count_prs(gh
: github
.Github
, triage_list
: dict, start_date
: datetime
.datetime
):
269 Fetch all the merged PRs for the project since ``start_date`` and update
270 ``triage_list`` with the number of PRs merged for each user.
274 query ($query: String!, $after: String) {
275 search(query: $query, type: ISSUE, first: 100, after: $after) {
294 date_begin
= start_date
296 while date_begin
< datetime
.datetime
.now():
297 date_end
= date_begin
+ datetime
.timedelta(days
=7)
298 formatted_date_begin
= date_begin
.strftime("%Y-%m-%dT%H:%M:%S")
299 formatted_date_end
= date_end
.strftime("%Y-%m-%dT%H:%M:%S")
301 "query": f
"type:pr is:merged merged:{formatted_date_begin}..{formatted_date_end} org:llvm",
306 res_header
, res_data
= gh
._Github
__requester
.graphql_query(
307 query
=query
, variables
=variables
309 data
= res_data
["data"]
310 for pr
in data
["search"]["nodes"]:
311 # Users can be None if the user has been deleted.
314 author
= pr
["author"]["login"]
315 if author
in triage_list
:
316 triage_list
[author
].add_authored()
318 if not pr
["mergedBy"]:
320 merger
= pr
["mergedBy"]["login"]
323 if merger
not in triage_list
:
325 triage_list
[merger
].add_merged()
327 has_next_page
= data
["search"]["pageInfo"]["hasNextPage"]
329 variables
["after"] = data
["search"]["pageInfo"]["endCursor"]
330 date_begin
= date_end
335 gh
= github
.Github(login_or_token
=token
)
336 org
= gh
.get_organization("llvm")
337 repo
= org
.get_repo("llvm-project")
338 one_year_ago
= datetime
.datetime
.now() - datetime
.timedelta(days
=365)
340 for collaborator
in repo
.get_collaborators(permission
="push"):
341 triage_list
[collaborator
.login
] = User(collaborator
.login
, triage_list
)
343 print("Start:", len(triage_list
), "triagers")
344 # Step 0 Check if users have requested commit access in the last year.
345 for user
in check_manual_requests(gh
, one_year_ago
):
346 if user
in triage_list
:
347 print(user
, "requested commit access in the last year.")
348 del triage_list
[user
]
349 print("After Request Check:", len(triage_list
), "triagers")
351 # Step 1 count all PRs authored or merged
352 count_prs(gh
, triage_list
, one_year_ago
)
354 print("After PRs:", len(triage_list
), "triagers")
356 if len(triage_list
) == 0:
359 # Step 2 check for reviews
360 for user
in list(triage_list
.keys()):
361 review_count
= get_review_count(gh
, user
, one_year_ago
)
362 triage_list
[user
].add_reviewed(review_count
)
364 print("After Reviews:", len(triage_list
), "triagers")
366 if len(triage_list
) == 0:
369 # Step 3 check for number of commits
370 for user
in list(triage_list
.keys()):
371 num_commits
= get_num_commits(gh
, user
, one_year_ago
)
372 # Override the total number of commits to not double count commits and
374 triage_list
[user
].set_authored(num_commits
)
376 print("After Commits:", len(triage_list
), "triagers")
378 # Step 4 check for new committers
379 for user
in list(triage_list
.keys()):
380 print("Checking", user
)
381 if is_new_committer(gh
, user
, one_year_ago
):
382 print("Removing new committer: ", user
)
383 del triage_list
[user
]
385 print("Complete:", len(triage_list
), "triagers")
387 with
open("triagers.log", "w") as triagers_log
:
388 for user
in triage_list
:
389 print(triage_list
[user
].__repr
__())
390 triagers_log
.write(user
+ "\n")
393 if __name__
== "__main__":