45
45
46
46
class GHError (Exception ):
47
47
"""Custom exception for errors related to 'gh' CLI commands."""
48
+
48
49
pass
49
50
50
51
@@ -75,20 +76,35 @@ def get_manually_requested_reviewers(
75
76
) -> set [str ]:
76
77
"""Fetches a set of reviewers who were manually requested by someone other than the bot."""
77
78
try :
78
- result = run_gh_command ([
79
- "api" , "graphql" ,
80
- "-f" , f"query={ MANUAL_REVIEW_REQUEST_QUERY } " ,
81
- "-F" , f"owner={ owner } " ,
82
- "-F" , f"repo={ repo } " ,
83
- "-F" , f"prNumber={ pr_number } " ,
84
- ])
79
+ result = run_gh_command (
80
+ [
81
+ "api" ,
82
+ "graphql" ,
83
+ "-f" ,
84
+ f"query={ MANUAL_REVIEW_REQUEST_QUERY } " ,
85
+ "-F" ,
86
+ f"owner={ owner } " ,
87
+ "-F" ,
88
+ f"repo={ repo } " ,
89
+ "-F" ,
90
+ f"prNumber={ pr_number } " ,
91
+ ]
92
+ )
85
93
data = json .loads (result .stdout )
86
- nodes = data .get ("data" , {}).get ("repository" , {}).get ("pullRequest" , {}).get ("timelineItems" , {}).get ("nodes" , [])
94
+ nodes = (
95
+ data .get ("data" , {})
96
+ .get ("repository" , {})
97
+ .get ("pullRequest" , {})
98
+ .get ("timelineItems" , {})
99
+ .get ("nodes" , [])
100
+ )
87
101
88
102
manually_requested = {
89
103
node ["requestedReviewer" ]["login" ]
90
104
for node in nodes
91
- if node and node .get ("requestedReviewer" ) and node .get ("actor" , {}).get ("login" ) != bot_user_name
105
+ if node
106
+ and node .get ("requestedReviewer" )
107
+ and node .get ("actor" , {}).get ("login" ) != bot_user_name
92
108
}
93
109
return manually_requested
94
110
except (GHError , json .JSONDecodeError , KeyError ) as e :
@@ -109,15 +125,28 @@ def get_users_from_gh(args: list[str], error_message: str) -> set[str]:
109
125
def get_pending_reviewers (pr_number : int ) -> set [str ]:
110
126
"""Gets the set of currently pending reviewers for a PR."""
111
127
return get_users_from_gh (
112
- ["pr" , "view" , str (pr_number ), "--json" , "reviewRequests" , "--jq" , ".reviewRequests[].login" ],
128
+ [
129
+ "pr" ,
130
+ "view" ,
131
+ str (pr_number ),
132
+ "--json" ,
133
+ "reviewRequests" ,
134
+ "--jq" ,
135
+ ".reviewRequests[].login" ,
136
+ ],
113
137
"Error getting pending reviewers" ,
114
138
)
115
139
116
140
117
141
def get_past_reviewers (owner : str , repo : str , pr_number : int ) -> set [str ]:
118
142
"""Gets the set of users who have already reviewed the PR."""
119
143
return get_users_from_gh (
120
- ["api" , f"repos/{ owner } /{ repo } /pulls/{ pr_number } /reviews" , "--jq" , ".[].user.login" ],
144
+ [
145
+ "api" ,
146
+ f"repos/{ owner } /{ repo } /pulls/{ pr_number } /reviews" ,
147
+ "--jq" ,
148
+ ".[].user.login" ,
149
+ ],
121
150
"Error getting past reviewers" ,
122
151
)
123
152
@@ -128,17 +157,14 @@ def is_collaborator(owner: str, repo: str, username: str) -> bool:
128
157
Handles 404 as a non-collaborator, while other errors are raised.
129
158
"""
130
159
result = run_gh_command (
131
- ["api" , f"repos/{ owner } /{ repo } /collaborators/{ username } " ],
132
- check = False
160
+ ["api" , f"repos/{ owner } /{ repo } /collaborators/{ username } " ], check = False
133
161
)
134
162
135
163
if result .returncode == 0 :
136
164
return True
137
165
138
166
if "HTTP 404" in result .stderr :
139
- logging .error (
140
- "'%s' is not a collaborator in this repository." , username
141
- )
167
+ logging .error ("'%s' is not a collaborator in this repository." , username )
142
168
return False
143
169
else :
144
170
logging .error (
@@ -161,22 +187,32 @@ def update_reviewers(
161
187
if reviewers_to_add :
162
188
logging .info ("Requesting reviews from: %s" , ", " .join (reviewers_to_add ))
163
189
try :
164
- run_gh_command ([
165
- "pr" , "edit" , str (pr_number ),
166
- "--add-reviewer" , "," .join (reviewers_to_add )
167
- ])
190
+ run_gh_command (
191
+ [
192
+ "pr" ,
193
+ "edit" ,
194
+ str (pr_number ),
195
+ "--add-reviewer" ,
196
+ "," .join (reviewers_to_add ),
197
+ ]
198
+ )
168
199
except GHError as e :
169
200
logging .error ("Failed to add reviewers: %s" , e )
170
201
171
202
if reviewers_to_remove and owner and repo :
172
- logging .info ("Removing review requests from: %s" , ", " .join (reviewers_to_remove ))
203
+ logging .info (
204
+ "Removing review requests from: %s" , ", " .join (reviewers_to_remove )
205
+ )
173
206
payload = json .dumps ({"reviewers" : list (reviewers_to_remove )})
174
207
try :
175
208
run_gh_command (
176
209
[
177
- "api" , "--method" , "DELETE" ,
210
+ "api" ,
211
+ "--method" ,
212
+ "DELETE" ,
178
213
f"repos/{ owner } /{ repo } /pulls/{ pr_number } /requested_reviewers" ,
179
- "--input" , "-" ,
214
+ "--input" ,
215
+ "-" ,
180
216
],
181
217
input_data = payload ,
182
218
)
@@ -186,14 +222,28 @@ def update_reviewers(
186
222
187
223
def main () -> None :
188
224
"""Main function to handle command-line arguments and manage reviewers."""
189
- parser = argparse .ArgumentParser (description = "Manage pull request reviewers for Nixvim." )
225
+ parser = argparse .ArgumentParser (
226
+ description = "Manage pull request reviewers for Nixvim."
227
+ )
190
228
parser .add_argument ("--owner" , required = True , help = "Repository owner." )
191
229
parser .add_argument ("--repo" , required = True , help = "Repository name." )
192
- parser .add_argument ("--pr-number" , type = int , required = True , help = "Pull request number." )
230
+ parser .add_argument (
231
+ "--pr-number" , type = int , required = True , help = "Pull request number."
232
+ )
193
233
parser .add_argument ("--pr-author" , required = True , help = "PR author's username." )
194
- parser .add_argument ("--current-maintainers" , default = "" , help = "Space-separated list of current maintainers." )
195
- parser .add_argument ("--changed-files" , default = "" , help = "Newline-separated list of changed files." )
196
- parser .add_argument ("--bot-user-name" , default = "" , help = "Bot user name to distinguish manual vs automated review requests." )
234
+ parser .add_argument (
235
+ "--current-maintainers" ,
236
+ default = "" ,
237
+ help = "Space-separated list of current maintainers." ,
238
+ )
239
+ parser .add_argument (
240
+ "--changed-files" , default = "" , help = "Newline-separated list of changed files."
241
+ )
242
+ parser .add_argument (
243
+ "--bot-user-name" ,
244
+ default = "" ,
245
+ help = "Bot user name to distinguish manual vs automated review requests." ,
246
+ )
197
247
args = parser .parse_args ()
198
248
199
249
no_plugin_files = not args .changed_files .strip ()
@@ -202,12 +252,14 @@ def main() -> None:
202
252
maintainers : set [str ] = set (args .current_maintainers .split ())
203
253
pending_reviewers = get_pending_reviewers (args .pr_number )
204
254
past_reviewers = get_past_reviewers (args .owner , args .repo , args .pr_number )
205
- manually_requested = get_manually_requested_reviewers (args .owner , args .repo , args .pr_number , args .bot_user_name )
255
+ manually_requested = get_manually_requested_reviewers (
256
+ args .owner , args .repo , args .pr_number , args .bot_user_name
257
+ )
206
258
207
- logging .info ("Current Maintainers: %s" , ' ' .join (maintainers ) or "None" )
208
- logging .info ("Pending Reviewers: %s" , ' ' .join (pending_reviewers ) or "None" )
209
- logging .info ("Past Reviewers: %s" , ' ' .join (past_reviewers ) or "None" )
210
- logging .info ("Manually Requested: %s" , ' ' .join (manually_requested ) or "None" )
259
+ logging .info ("Current Maintainers: %s" , " " .join (maintainers ) or "None" )
260
+ logging .info ("Pending Reviewers: %s" , " " .join (pending_reviewers ) or "None" )
261
+ logging .info ("Past Reviewers: %s" , " " .join (past_reviewers ) or "None" )
262
+ logging .info ("Manually Requested: %s" , " " .join (manually_requested ) or "None" )
211
263
212
264
# --- 2. Determine reviewers to remove ---
213
265
reviewers_to_remove : set [str ] = set ()
@@ -224,7 +276,7 @@ def main() -> None:
224
276
args .pr_number ,
225
277
owner = args .owner ,
226
278
repo = args .repo ,
227
- reviewers_to_remove = reviewers_to_remove
279
+ reviewers_to_remove = reviewers_to_remove ,
228
280
)
229
281
else :
230
282
logging .info ("No reviewers to remove." )
@@ -236,12 +288,16 @@ def main() -> None:
236
288
potential_reviewers = maintainers - users_to_exclude
237
289
238
290
reviewers_to_add = {
239
- user for user in potential_reviewers if is_collaborator (args .owner , args .repo , user )
291
+ user
292
+ for user in potential_reviewers
293
+ if is_collaborator (args .owner , args .repo , user )
240
294
}
241
295
242
296
non_collaborators = potential_reviewers - reviewers_to_add
243
297
if non_collaborators :
244
- logging .warning ("Ignoring non-collaborators: %s" , ", " .join (non_collaborators ))
298
+ logging .warning (
299
+ "Ignoring non-collaborators: %s" , ", " .join (non_collaborators )
300
+ )
245
301
246
302
if reviewers_to_add :
247
303
update_reviewers (args .pr_number , reviewers_to_add = reviewers_to_add )
0 commit comments