@@ -140,6 +140,36 @@ def list_pull_requests(token, state, head, base):
140
140
return results
141
141
142
142
143
+ def get_pull_request_reviews (token , owner , repo , pull_number ):
144
+ """Fetches all reviews for a given pull request."""
145
+ # Note: GitHub API for listing reviews does not support a 'since' parameter directly.
146
+ # Filtering by 'since' must be done client-side after fetching all reviews.
147
+ url = f'{ GITHUB_API_URL } /pulls/{ pull_number } /reviews'
148
+ headers = {'Accept' : 'application/vnd.github.v3+json' , 'Authorization' : f'token { token } ' }
149
+ page = 1
150
+ per_page = 100
151
+ results = []
152
+ keep_going = True
153
+ while keep_going :
154
+ params = {'per_page' : per_page , 'page' : page }
155
+ page = page + 1
156
+ keep_going = False
157
+ try :
158
+ with requests_retry_session ().get (url , headers = headers , params = params ,
159
+ stream = True , timeout = TIMEOUT ) as response :
160
+ logging .info ("get_pull_request_reviews: %s params: %s response: %s" , url , params , response )
161
+ response .raise_for_status ()
162
+ current_page_results = response .json ()
163
+ if not current_page_results :
164
+ break
165
+ results .extend (current_page_results )
166
+ keep_going = (len (current_page_results ) == per_page )
167
+ except requests .exceptions .RequestException as e :
168
+ logging .error (f"Error listing pull request reviews (page { params .get ('page' , 'N/A' )} , params: { params } ) for PR { pull_number } in { owner } /{ repo } : { e } " )
169
+ return None # Indicate error
170
+ return results
171
+
172
+
143
173
def get_current_branch_name ():
144
174
"""Gets the current git branch name."""
145
175
try :
@@ -344,25 +374,98 @@ def parse_repo_url(url_string):
344
374
sys .stderr .write (f"{ error_message } { error_suffix } \n " )
345
375
sys .exit (1 )
346
376
347
- sys .stderr .write (f"Fetching comments for PR #{ pull_request_number } from { OWNER } /{ REPO } ...\n " )
377
+ # Fetch overall reviews first
378
+ sys .stderr .write (f"Fetching overall reviews for PR #{ pull_request_number } from { OWNER } /{ REPO } ...\n " )
379
+ overall_reviews = get_pull_request_reviews (args .token , OWNER , REPO , pull_request_number )
380
+
381
+ if overall_reviews is None :
382
+ sys .stderr .write (f"Error: Failed to fetch overall reviews due to an API or network issue.{ error_suffix } \n Please check logs for details.\n " )
383
+ sys .exit (1 )
384
+
385
+ filtered_overall_reviews = []
386
+ if overall_reviews : # If not None and not empty
387
+ for review in overall_reviews :
388
+ if review .get ("state" ) == "DISMISSED" :
389
+ continue
390
+
391
+ if args .since :
392
+ submitted_at_str = review .get ("submitted_at" )
393
+ if submitted_at_str :
394
+ try :
395
+ # Compatibility for Python < 3.11
396
+ if sys .version_info < (3 , 11 ):
397
+ dt_str_submitted = submitted_at_str .replace ("Z" , "+00:00" )
398
+ else :
399
+ dt_str_submitted = submitted_at_str
400
+ submitted_dt = datetime .datetime .fromisoformat (dt_str_submitted )
401
+
402
+ since_dt_str = args .since
403
+ if sys .version_info < (3 , 11 ) and args .since .endswith ("Z" ):
404
+ since_dt_str = args .since .replace ("Z" , "+00:00" )
405
+ since_dt = datetime .datetime .fromisoformat (since_dt_str )
406
+
407
+ # Ensure 'since_dt' is timezone-aware if 'submitted_dt' is.
408
+ # GitHub timestamps are UTC. fromisoformat on Z or +00:00 makes them aware.
409
+ if submitted_dt .tzinfo and not since_dt .tzinfo :
410
+ since_dt = since_dt .replace (tzinfo = timezone .utc ) # Assume since is UTC if not specified
411
+
412
+ if submitted_dt < since_dt :
413
+ continue
414
+ except ValueError as ve :
415
+ sys .stderr .write (f"Warning: Could not parse review submitted_at timestamp '{ submitted_at_str } ' or --since timestamp '{ args .since } ': { ve } \n " )
416
+ # Decide: skip review or include it if parsing fails? For now, include.
417
+ filtered_overall_reviews .append (review )
418
+
419
+ # Sort by submission time, oldest first
420
+ try :
421
+ filtered_overall_reviews .sort (key = lambda r : r .get ("submitted_at" , "" ))
422
+ except Exception as e : # Broad exception for safety
423
+ sys .stderr .write (f"Warning: Could not sort overall reviews: { e } \n " )
424
+
425
+ if filtered_overall_reviews :
426
+ print ("# Overall Review Summaries\n \n " )
427
+ for review in filtered_overall_reviews :
428
+ user = review .get ("user" , {}).get ("login" , "Unknown user" )
429
+ submitted_at = review .get ("submitted_at" , "N/A" )
430
+ state = review .get ("state" , "N/A" )
431
+ body = review .get ("body" , "" ).strip ()
432
+ html_url = review .get ("html_url" , "N/A" )
433
+ review_id = review .get ("id" , "N/A" )
434
+
435
+ print (f"## Review by: **{ user } ** (ID: `{ review_id } `)\n " )
436
+ print (f"* **Submitted At**: `{ submitted_at } `" )
437
+ print (f"* **State**: `{ state } `" )
438
+ print (f"* **URL**: <{ html_url } >\n " )
439
+
440
+ if body :
441
+ print ("\n ### Summary Comment:" )
442
+ print (body )
443
+ print ("\n ---" )
444
+ # Add an extra newline to separate from line comments if any overall reviews were printed
445
+ print ("\n " )
446
+
447
+
448
+ sys .stderr .write (f"Fetching line comments for PR #{ pull_request_number } from { OWNER } /{ REPO } ...\n " )
348
449
if args .since :
349
- sys .stderr .write (f"Filtering comments updated since: { args .since } \n " )
450
+ sys .stderr .write (f"Filtering line comments updated since: { args .since } \n " ) # Clarify this 'since' is for line comments
350
451
351
452
comments = get_pull_request_review_comments (
352
453
args .token ,
353
454
pull_request_number ,
354
- since = args .since
455
+ since = args .since # This 'since' applies to line comment's 'updated_at'
355
456
)
356
457
357
- if comments is None : # Explicit check for None, indicating an API/network error
358
- sys .stderr .write (f"Error: Failed to fetch review comments due to an API or network issue.{ error_suffix } \n Please check logs for details.\n " )
458
+ if comments is None :
459
+ sys .stderr .write (f"Error: Failed to fetch line comments due to an API or network issue.{ error_suffix } \n Please check logs for details.\n " )
359
460
sys .exit (1 )
360
- elif not comments : # Empty list, meaning no comments found or matching filters
361
- sys .stderr .write (f"No review comments found for PR #{ pull_request_number } (or matching filters).\n " )
362
- return # Graceful exit with 0
461
+ # Note: The decision to exit if only line comments fail vs. if only overall reviews fail could be nuanced.
462
+ # For now, failure to fetch either is treated as a critical error for the script's purpose.
463
+
464
+ # Handling for empty line comments will be just before their processing loop.
465
+ # if not comments: (handled later)
363
466
364
- latest_activity_timestamp_obj = None
365
- processed_comments_count = 0
467
+ latest_activity_timestamp_obj = None # This is for line comments' 'since' suggestion
468
+ processed_comments_count = 0 # This tracks line comments
366
469
print ("# Review Comments\n \n " )
367
470
for comment in comments :
368
471
created_at_str = comment .get ("created_at" )
0 commit comments