Skip to content

Commit 01eaf8a

Browse files
Fix: Ensure 'next command' suggestion considers overall reviews timestamps
- Corrects the logic for generating the 'next command' suggestion to ensure it appears even if only overall PR reviews (and no line comments) are present. - The script now reliably uses the latest timestamp from either overall review `submitted_at` or line comment `updated_at` fields. - Removed a premature `return` that bypassed the suggestion logic when line comments were empty but overall reviews might have had timestamps. This also includes a pass for final extraneous comment removal.
1 parent 2876592 commit 01eaf8a

File tree

1 file changed

+34
-73
lines changed

1 file changed

+34
-73
lines changed

scripts/print_github_reviews.py

Lines changed: 34 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,18 @@
2626
import subprocess
2727
from requests.adapters import HTTPAdapter
2828
from requests.packages.urllib3.util.retry import Retry
29-
# from absl import logging # Removed
30-
3129
# Constants for GitHub API interaction
3230
RETRIES = 3
3331
BACKOFF = 5
3432
RETRY_STATUS = (403, 500, 502, 504) # HTTP status codes to retry on
3533
TIMEOUT = 5 # Default timeout for requests in seconds
3634
TIMEOUT_LONG = 20 # Longer timeout, currently not used by functions in this script
3735

38-
# Global variables for the target repository.
39-
# These are populated by set_repo_url_standalone() after determining
40-
# the owner and repo from arguments or git configuration.
36+
# Global variables for the target repository, populated by set_repo_url_standalone()
4137
OWNER = ''
4238
REPO = ''
43-
BASE_URL = 'https://api.github.com' # Base URL for GitHub API
44-
GITHUB_API_URL = '' # Dynamically constructed API URL for the specific repository
45-
46-
# logging.set_verbosity(logging.WARNING) # Removed
39+
BASE_URL = 'https://api.github.com'
40+
GITHUB_API_URL = ''
4741

4842

4943
def set_repo_url_standalone(owner_name, repo_name):
@@ -80,7 +74,7 @@ def get_pull_request_review_comments(token, pull_number, since=None):
8074

8175
base_params = {'per_page': per_page}
8276
if since:
83-
base_params['since'] = since # Filter comments by timestamp
77+
base_params['since'] = since
8478

8579
while True:
8680
current_page_params = base_params.copy()
@@ -90,22 +84,21 @@ def get_pull_request_review_comments(token, pull_number, since=None):
9084
with requests_retry_session().get(url, headers=headers, params=current_page_params,
9185
stream=True, timeout=TIMEOUT) as response:
9286
response.raise_for_status()
93-
# logging.info("get_pull_request_review_comments: %s params %s response: %s", url, current_page_params, response) # Removed
9487

9588
current_page_results = response.json()
96-
if not current_page_results: # No more data on this page
89+
if not current_page_results: # No more data
9790
break
9891

9992
results.extend(current_page_results)
10093

101-
if len(current_page_results) < per_page: # Last page
94+
if len(current_page_results) < per_page: # Reached last page
10295
break
10396

10497
page += 1
10598

10699
except requests.exceptions.RequestException as e:
107100
sys.stderr.write(f"Error: Failed to fetch review comments (page {page}, params: {current_page_params}) for PR {pull_number}: {e}\n")
108-
return None # Indicate error
101+
return None
109102
return results
110103

111104

@@ -127,7 +120,6 @@ def list_pull_requests(token, state, head, base):
127120
try:
128121
with requests_retry_session().get(url, headers=headers, params=params,
129122
stream=True, timeout=TIMEOUT) as response:
130-
# logging.info("list_pull_requests: %s params: %s response: %s", url, params, response) # Removed
131123
response.raise_for_status()
132124
current_page_results = response.json()
133125
if not current_page_results:
@@ -136,7 +128,7 @@ def list_pull_requests(token, state, head, base):
136128
keep_going = (len(current_page_results) == per_page)
137129
except requests.exceptions.RequestException as e:
138130
sys.stderr.write(f"Error: Failed to list pull requests (page {params.get('page', 'N/A')}, params: {params}) for {OWNER}/{REPO}: {e}\n")
139-
return None # Indicate error
131+
return None
140132
return results
141133

142134

@@ -157,7 +149,6 @@ def get_pull_request_reviews(token, owner, repo, pull_number):
157149
try:
158150
with requests_retry_session().get(url, headers=headers, params=params,
159151
stream=True, timeout=TIMEOUT) as response:
160-
# logging.info("get_pull_request_reviews: %s params: %s response: %s", url, params, response) # Removed
161152
response.raise_for_status()
162153
current_page_results = response.json()
163154
if not current_page_results:
@@ -166,7 +157,7 @@ def get_pull_request_reviews(token, owner, repo, pull_number):
166157
keep_going = (len(current_page_results) == per_page)
167158
except requests.exceptions.RequestException as e:
168159
sys.stderr.write(f"Error: Failed to list pull request reviews (page {params.get('page', 'N/A')}, params: {params}) for PR {pull_number} in {owner}/{repo}: {e}\n")
169-
return None # Indicate error
160+
return None
170161
return results
171162

172163

@@ -220,12 +211,11 @@ def main():
220211
sys.stderr.write(f"Determined repository: {determined_owner}/{determined_repo} from git remote.\n")
221212
except (subprocess.CalledProcessError, FileNotFoundError, UnicodeDecodeError) as e:
222213
sys.stderr.write(f"Could not automatically determine repository from git remote: {e}\n")
223-
except Exception as e: # Catch any other unexpected error during git processing, though less likely.
214+
except Exception as e: # Catch any other unexpected error.
224215
sys.stderr.write(f"An unexpected error occurred while determining repository: {e}\n")
225216

226217
def parse_repo_url(url_string):
227218
"""Parses owner and repository name from various GitHub URL formats."""
228-
# Handles https://github.com/owner/repo.git, [email protected]:owner/repo.git, and URLs without .git suffix.
229219
url_match = re.search(r"(?:(?:https?://github\.com/)|(?:git@github\.com:))([^/]+)/([^/.]+?)(?:\.git)?/?$", url_string)
230220
if url_match:
231221
return url_match.group(1), url_match.group(2)
@@ -326,30 +316,27 @@ def parse_repo_url(url_string):
326316
final_owner = args.owner
327317
final_repo = args.repo
328318
sys.stderr.write(f"Using repository from --owner/--repo args: {final_owner}/{final_repo}\n")
329-
else: # User set one but not the other (and the other wasn't available from a default)
319+
else:
330320
sys.stderr.write(f"Error: Both --owner and --repo must be specified if one is provided explicitly (and --url is not used).{error_suffix}\n")
331321
sys.exit(1)
332322
elif args.owner and args.repo: # Both args have values, from successful auto-detection
333323
final_owner = args.owner
334324
final_repo = args.repo
335-
elif args.owner or args.repo: # Only one has a value (e.g. auto-detect only found one)
325+
elif args.owner or args.repo: # Only one has a value from auto-detection (e.g. git remote parsing failed partially)
336326
sys.stderr.write(f"Error: Both --owner and --repo are required if not using --url, and auto-detection was incomplete.{error_suffix}\n")
337327
sys.exit(1)
338-
# If final_owner/repo are still None here, it means auto-detection failed and user provided nothing.
328+
# If final_owner/repo are still None here, it means auto-detection failed AND user provided nothing.
339329

340330
if not final_owner or not final_repo:
341331
sys.stderr.write(f"Error: Could not determine repository. Please specify --url, OR both --owner and --repo, OR ensure git remote 'origin' is configured correctly.{error_suffix}\n")
342332
sys.exit(1)
343333

344-
# Set global repository variables for API calls
345334
if not set_repo_url_standalone(final_owner, final_repo):
346-
# This path should ideally not be reached if previous checks are robust.
347335
sys.stderr.write(f"Error: Could not set repository to {final_owner}/{final_repo}. Ensure owner/repo are correct.{error_suffix}\n")
348336
sys.exit(1)
349337

350-
# Determine Pull Request number
351338
pull_request_number = args.pull_number
352-
current_branch_for_pr_check = None
339+
current_branch_for_pr_check = None # Store branch name if auto-detecting PR
353340
if not pull_request_number:
354341
sys.stderr.write("Pull number not specified, attempting to find PR for current branch...\n")
355342
current_branch_for_pr_check = get_current_branch_name()
@@ -359,22 +346,19 @@ def parse_repo_url(url_string):
359346
if pull_request_number:
360347
sys.stderr.write(f"Found PR #{pull_request_number} for branch {current_branch_for_pr_check}.\n")
361348
else:
362-
# Informational, actual error handled by `if not pull_request_number:` below
363-
sys.stderr.write(f"No open PR found for branch {current_branch_for_pr_check} in {OWNER}/{REPO}.\n")
349+
sys.stderr.write(f"No open PR found for branch {current_branch_for_pr_check} in {OWNER}/{REPO}.\n") # Informational
364350
else:
365351
sys.stderr.write(f"Error: Could not determine current git branch. Cannot find PR automatically.{error_suffix}\n")
366352
sys.exit(1)
367353

368-
if not pull_request_number:
354+
if not pull_request_number: # Final check for PR number
369355
error_message = "Error: Pull request number is required."
370-
if not args.pull_number and current_branch_for_pr_check:
356+
if not args.pull_number and current_branch_for_pr_check: # Auto-detect branch ok, but no PR found
371357
error_message = f"Error: Pull request number not specified and no open PR found for branch {current_branch_for_pr_check}."
372-
elif not args.pull_number and not current_branch_for_pr_check: # Should have been caught and exited above.
373-
error_message = "Error: Pull request number not specified and could not determine current git branch."
358+
# The case where current_branch_for_pr_check is None (git branch fail) is caught and exited above.
374359
sys.stderr.write(f"{error_message}{error_suffix}\n")
375360
sys.exit(1)
376361

377-
# Fetch overall reviews first
378362
sys.stderr.write(f"Fetching overall reviews for PR #{pull_request_number} from {OWNER}/{REPO}...\n")
379363
overall_reviews = get_pull_request_reviews(args.token, OWNER, REPO, pull_request_number)
380364

@@ -490,47 +474,24 @@ def parse_repo_url(url_string):
490474
# Note: The decision to exit if only line comments fail vs. if only overall reviews fail could be nuanced.
491475
# For now, failure to fetch either is treated as a critical error for the script's purpose.
492476

493-
# Handling for empty line comments will be just before their processing loop.
494-
# if not comments: (handled later)
495-
496-
# Initialize timestamps for 'next command' suggestion
497-
latest_overall_review_activity_dt = None
498-
latest_line_comment_activity_dt = None
499-
processed_comments_count = 0
500-
501-
# Only print line comments header if there are comments to process
502-
# The 'comments' list here has already been checked for None (API error)
503-
# and for being empty (no comments found, in which case script would have exited).
504-
# However, all comments could be filtered out by status or content.
505-
# So, we'll print the header, and if nothing follows, it's acceptable.
506-
# A more robust check would be to see if any comment *will* be printed.
507-
# For now, let's check if the list is non-empty before printing the header.
508-
# The user's request was "if there are no review comments to display".
509-
# This means after all filtering. The current loop structure processes then prints.
510-
# A simple way is to print header only if `comments` list is not empty,
511-
# and then if the loop results in `processed_comments_count == 0`, the section will be empty.
512-
# Or, delay printing header until first comment is processed.
513-
514-
# Let's try: print header only if comments list is not empty *before* the loop.
515-
# If all get filtered out, an empty section is fine.
516-
# The existing "No review comments found..." handles the case of an initially empty list.
517-
# The current plan asks for "processed_comments_count > 0". This requires a look-ahead or restructuring.
518-
519-
# Simpler approach: If the `comments` list (from API) is not empty, print header.
520-
# If all get filtered out inside the loop, the section will be empty.
521-
# The earlier check `elif not comments:` handles the case of truly no comments from API.
522-
# So, if we reach here, `comments` is a non-empty list.
523-
# The condition should be: if any comments *survive* the loop's internal filters.
524-
# This is best done by checking `processed_comments_count` *after* the loop,
525-
# but the header needs to be printed *before*.
526-
# So, we print the header if `comments` is not empty, and accept an empty section if all are filtered.
527-
# The user's request can be interpreted as "don't print the header if the `comments` list is empty
528-
# *after fetching and initial checks*".
529-
530-
if comments: # If the list from API (after None check) is not empty
477+
# Initialize tracking variables early - MOVED TO TOP OF MAIN
478+
# latest_overall_review_activity_dt = None
479+
# latest_line_comment_activity_dt = None
480+
# processed_comments_count = 0 # This is specifically for line comments
481+
482+
# Handling for line comments
483+
if not comments: # comments is an empty list here (None case handled above)
484+
sys.stderr.write(f"No line comments found for PR #{pull_request_number} (or matching filters).\n")
485+
# If there were also no overall reviews, and no line comments, then nothing to show.
486+
# The 'next command' suggestion logic below will still run if overall_reviews had content.
487+
if not filtered_overall_reviews : # and not comments (implicitly true here)
488+
# Only return (and skip 'next command' suggestion) if NO content at all was printed.
489+
# If overall_reviews were printed, we still want the 'next command' suggestion.
490+
pass # Let it fall through to the 'next command' suggestion logic
491+
else:
531492
print("# Review Comments\n\n")
532493

533-
for comment in comments: # `comments` is guaranteed to be a list here
494+
for comment in comments: # if comments is empty, this loop is skipped.
534495
created_at_str = comment.get("created_at")
535496

536497
current_pos = comment.get("position")

0 commit comments

Comments
 (0)