1
1
#!/usr/bin/env python3
2
- # Copyright 2024 Google LLC
2
+ # Copyright 2025 Google LLC
3
3
#
4
4
# Licensed under the Apache License, Version 2.0 (the "License");
5
5
# you may not use this file except in compliance with the License.
26
26
# Set verbosity for absl logging if you want to see logs from firebase_github
27
27
# absl_logging.set_verbosity(absl_logging.INFO)
28
28
except ImportError :
29
- # If absl is not used, standard logging can be configured if needed
30
- # import logging as std_logging
31
- # std_logging.basicConfig(level=std_logging.INFO)
32
29
pass # firebase_github.py uses absl.logging.info, so this won't redirect.
33
30
34
31
32
+ def print_contextual_diff_hunk (diff_hunk , comment_position , context_lines_count ):
33
+ if not diff_hunk or not diff_hunk .strip (): # Handle empty or whitespace-only diff_hunk
34
+ print ("(No diff hunk available or content is empty)" )
35
+ return
36
+
37
+ hunk_lines = diff_hunk .split ('\n ' )
38
+
39
+ # comment_position is 1-indexed from GitHub API. If None, or context is 0, print full hunk.
40
+ if context_lines_count == 0 or comment_position is None or comment_position < 1 or comment_position > len (hunk_lines ):
41
+ print (diff_hunk )
42
+ return
43
+
44
+ comment_line_index = comment_position - 1 # Convert to 0-indexed for list access
45
+
46
+ start_index = max (0 , comment_line_index - context_lines_count )
47
+ end_index = min (len (hunk_lines ), comment_line_index + context_lines_count + 1 )
48
+
49
+ # Ensure start_index is not greater than comment_line_index, in case of small hunks
50
+ # This also means that if comment_line_index is valid, start_index will be <= comment_line_index
51
+ start_index = min (start_index , comment_line_index if comment_line_index >= 0 else 0 )
52
+
53
+
54
+ for i in range (start_index , end_index ):
55
+ # Basic safety for i, though start/end logic should make this robust
56
+ if i >= 0 and i < len (hunk_lines ):
57
+ prefix = "> " if i == comment_line_index else " "
58
+ print (f"{ prefix } { hunk_lines [i ]} " )
59
+ # else: # This case should ideally not be reached with correct boundary conditions
60
+ # print(f" Error: Skipped line index {i} in hunk processing due to boundary issue.")
61
+
62
+
35
63
def main ():
36
- # Default owner and repo from firebase_github, ensuring it's loaded.
37
64
default_owner = firebase_github .OWNER
38
65
default_repo = firebase_github .REPO
39
66
40
67
parser = argparse .ArgumentParser (
41
68
description = "Fetch review comments from a GitHub PR and format for use with Jules." ,
42
- formatter_class = argparse .RawTextHelpFormatter # To preserve formatting in help text
69
+ formatter_class = argparse .RawTextHelpFormatter
43
70
)
44
71
parser .add_argument (
45
72
"--pull_number" ,
@@ -51,73 +78,100 @@ def main():
51
78
"--owner" ,
52
79
type = str ,
53
80
default = default_owner ,
54
- help = f"Repository owner. Defaults to '{ default_owner } ' (from firebase_github.py) ."
81
+ help = f"Repository owner. Defaults to '{ default_owner } '."
55
82
)
56
83
parser .add_argument (
57
84
"--repo" ,
58
85
type = str ,
59
86
default = default_repo ,
60
- help = f"Repository name. Defaults to '{ default_repo } ' (from firebase_github.py) ."
87
+ help = f"Repository name. Defaults to '{ default_repo } '."
61
88
)
62
89
parser .add_argument (
63
90
"--token" ,
64
91
type = str ,
65
92
default = os .environ .get ("GITHUB_TOKEN" ),
66
- help = "GitHub token. Can also be set via GITHUB_TOKEN environment variable."
93
+ help = "GitHub token. Can also be set via GITHUB_TOKEN env var."
94
+ )
95
+ parser .add_argument (
96
+ "--context-lines" ,
97
+ type = int ,
98
+ default = 10 ,
99
+ help = "Number of context lines around the commented line from the diff hunk. Use 0 for the full hunk. Default: 10."
100
+ )
101
+ parser .add_argument (
102
+ "--since" ,
103
+ type = str ,
104
+ default = None ,
105
+ help = "Only show comments created at or after this ISO 8601 timestamp (e.g., YYYY-MM-DDTHH:MM:SSZ)."
67
106
)
68
107
69
108
args = parser .parse_args ()
70
109
71
110
if not args .token :
72
- sys .stderr .write ("Error: GitHub token not provided. Set GITHUB_TOKEN environment variable or use --token argument .\n " )
111
+ sys .stderr .write ("Error: GitHub token not provided. Set GITHUB_TOKEN or use --token.\n " )
73
112
sys .exit (1 )
74
113
75
- # Update the repository details in firebase_github module if different from default
76
114
if args .owner != firebase_github .OWNER or args .repo != firebase_github .REPO :
77
115
repo_url = f"https://github.com/{ args .owner } /{ args .repo } "
78
116
if not firebase_github .set_repo_url (repo_url ):
79
- sys .stderr .write (f"Error: Invalid repository URL format for { args .owner } /{ args .repo } . Expected format: https://github.com/owner/repo\n " )
117
+ sys .stderr .write (f"Error: Invalid repo URL: { args .owner } /{ args .repo } . Expected https://github.com/owner/repo\n " )
80
118
sys .exit (1 )
81
- # Using print to stderr for info, as absl logging might not be configured here for this script's own messages.
82
119
print (f"Targeting repository: { firebase_github .OWNER } /{ firebase_github .REPO } " , file = sys .stderr )
83
120
121
+ print (f"Fetching comments for PR #{ args .pull_number } from { firebase_github .OWNER } /{ firebase_github .REPO } ..." , file = sys .stderr )
122
+ if args .since :
123
+ print (f"Filtering comments created since: { args .since } " , file = sys .stderr )
84
124
85
- print (f"Fetching review comments for PR #{ args .pull_number } from { firebase_github .OWNER } /{ firebase_github .REPO } ..." , file = sys .stderr )
86
-
87
- comments = firebase_github .get_pull_request_review_comments (args .token , args .pull_number )
125
+ comments = firebase_github .get_pull_request_review_comments (
126
+ args .token ,
127
+ args .pull_number ,
128
+ since = args .since # Pass the 'since' argument
129
+ )
88
130
89
- if not comments : # This will be true if list is empty (no comments or error in fetching first page)
90
- print (f"No review comments found for PR #{ args .pull_number } , or an error occurred during fetching." , file = sys .stderr )
91
- # If firebase_github.py's get_pull_request_review_comments logs errors, those might provide more details.
92
- return # Exit gracefully if no comments
131
+ if not comments :
132
+ print (f"No review comments found for PR #{ args .pull_number } (or matching filters), or an error occurred." , file = sys .stderr )
133
+ return
93
134
94
- # Output actual data to stdout
95
135
print ("\n --- Review Comments ---" )
96
136
for comment in comments :
97
137
user = comment .get ("user" , {}).get ("login" , "Unknown user" )
98
138
path = comment .get ("path" , "N/A" )
99
- line = comment .get ("line" , "N/A" )
100
- body = comment .get ("body" , "" ).strip () # Strip whitespace from comment body
101
- diff_hunk = comment .get ("diff_hunk" , "N/A" )
139
+ file_line = comment .get ("line" , "N/A" )
140
+ hunk_position = comment .get ("position" ) # This is the 1-indexed position in the hunk
141
+
142
+ body = comment .get ("body" , "" ).strip ()
143
+ diff_hunk = comment .get ("diff_hunk" ) # Can be None or empty
102
144
html_url = comment .get ("html_url" , "N/A" )
103
145
104
- # Only print comments that have a body
146
+ comment_id = comment .get ("id" )
147
+ in_reply_to_id = comment .get ("in_reply_to_id" )
148
+ created_at = comment .get ("created_at" )
149
+
105
150
if not body :
106
151
continue
107
152
108
- print (f"Comment by: { user } " )
153
+ print (f"Comment by: { user } (ID: { comment_id } ){ f' (In Reply To: { in_reply_to_id } )' if in_reply_to_id else '' } " )
154
+ if created_at :
155
+ print (f"Timestamp: { created_at } " )
156
+
157
+ if diff_hunk : # Only show status if it's a diff-related comment
158
+ status_text = "[OUTDATED]" if hunk_position is None else "[CURRENT]"
159
+ print (f"Status: { status_text } " )
160
+
109
161
print (f"File: { path } " )
110
- # The 'line' field in GitHub's API for PR review comments refers to the line number in the diff.
111
- # 'original_line' refers to the line number in the file at the time the comment was made.
112
- # 'start_line' and 'original_start_line' for multi-line comments.
113
- # For simplicity, we use 'line'.
114
- print (f"Line in diff: { line } " )
162
+ print (f"Line in File Diff: { file_line } " )
115
163
print (f"URL: { html_url } " )
116
- print ("--- Diff Hunk ---" )
117
- print (diff_hunk )
164
+
165
+ print ("--- Diff Hunk Context ---" )
166
+ if diff_hunk :
167
+ print_contextual_diff_hunk (diff_hunk , hunk_position , args .context_lines )
168
+ else :
169
+ print ("(Comment not associated with a specific diff hunk)" )
170
+
118
171
print ("--- Comment ---" )
119
172
print (body )
120
- print ("----------------------------------------\n " )
173
+ print ("----------------------------------------\n " ) # Use
174
+ for newline
121
175
122
176
if __name__ == "__main__" :
123
177
main ()
0 commit comments