19
19
import os
20
20
import sys
21
21
import firebase_github # Assumes firebase_github.py is in the same directory or python path
22
+ import datetime
23
+ from datetime import timezone , timedelta
24
+
22
25
23
26
# Attempt to configure logging for firebase_github if absl is available
24
27
try :
@@ -33,7 +36,7 @@ def main():
33
36
default_repo = firebase_github .REPO
34
37
35
38
parser = argparse .ArgumentParser (
36
- description = "Fetch review comments from a GitHub PR and format for use with Jules ." ,
39
+ description = "Fetch review comments from a GitHub PR and format into a simple text output ." ,
37
40
formatter_class = argparse .RawTextHelpFormatter
38
41
)
39
42
parser .add_argument (
@@ -63,7 +66,7 @@ def main():
63
66
parser .add_argument (
64
67
"--context-lines" ,
65
68
type = int ,
66
- default = 10 , # Default to 10 lines, 0 means full hunk.
69
+ default = 10 ,
67
70
help = "Number of context lines from the diff hunk. Use 0 for the full hunk. "
68
71
"If > 0, shows the last N lines of the hunk. Default: 10."
69
72
)
@@ -76,7 +79,7 @@ def main():
76
79
parser .add_argument (
77
80
"--skip-outdated" ,
78
81
action = "store_true" ,
79
- help = "If set, outdated comments will not be printed."
82
+ help = "If set, comments marked [OUTDATED] or [FULLY_OUTDATED] will not be printed."
80
83
)
81
84
82
85
args = parser .parse_args ()
@@ -90,13 +93,13 @@ def main():
90
93
if not firebase_github .set_repo_url (repo_url ):
91
94
sys .stderr .write (f"Error: Invalid repo URL: { args .owner } /{ args .repo } . Expected https://github.com/owner/repo\n " )
92
95
sys .exit (1 )
93
- print (f"Targeting repository: { firebase_github .OWNER } /{ firebase_github .REPO } " , file = sys . stderr )
96
+ sys . stderr . write (f"Targeting repository: { firebase_github .OWNER } /{ firebase_github .REPO } \n " )
94
97
95
- print (f"Fetching comments for PR #{ args .pull_number } from { firebase_github .OWNER } /{ firebase_github .REPO } ..." , file = sys . stderr )
98
+ sys . stderr . write (f"Fetching comments for PR #{ args .pull_number } from { firebase_github .OWNER } /{ firebase_github .REPO } ...\n " )
96
99
if args .since :
97
- print (f"Filtering comments created since: { args .since } " , file = sys . stderr )
100
+ sys . stderr . write (f"Filtering comments created since: { args .since } \n " )
98
101
if args .skip_outdated :
99
- print ("Skipping outdated comments." , file = sys . stderr )
102
+ sys . stderr . write ("Skipping outdated comments based on status. \n " )
100
103
101
104
102
105
comments = firebase_github .get_pull_request_review_comments (
@@ -106,48 +109,79 @@ def main():
106
109
)
107
110
108
111
if not comments :
109
- print (f"No review comments found for PR #{ args .pull_number } (or matching filters), or an error occurred." , file = sys . stderr )
112
+ sys . stderr . write (f"No review comments found for PR #{ args .pull_number } (or matching filters), or an error occurred.\n " )
110
113
return
111
114
115
+ latest_created_at_obj = None
112
116
print ("\n --- Review Comments ---" )
113
117
for comment in comments :
114
- # Determine outdated status and effective line for display
115
- is_outdated = comment .get ("position" ) is None
116
-
117
- if args .skip_outdated and is_outdated :
118
+ created_at_str = comment .get ("created_at" )
119
+
120
+ current_pos = comment .get ("position" )
121
+ current_line = comment .get ("line" )
122
+ original_line = comment .get ("original_line" )
123
+
124
+ status_text = ""
125
+ line_to_display = None
126
+ is_effectively_outdated = False
127
+
128
+ if current_pos is None : # Comment's specific diff context is gone
129
+ status_text = "[FULLY_OUTDATED]"
130
+ line_to_display = original_line # Show original line if available
131
+ is_effectively_outdated = True
132
+ elif original_line is not None and current_line != original_line : # Comment on a line that changed
133
+ status_text = "[OUTDATED]"
134
+ line_to_display = current_line # Show where the comment is now in the diff
135
+ is_effectively_outdated = True
136
+ else : # Comment is current or a file-level comment (original_line is None but current_pos exists)
137
+ status_text = "[CURRENT]"
138
+ line_to_display = current_line # For line comments, or None for file comments (handled by fallback)
139
+ is_effectively_outdated = False
140
+
141
+ if line_to_display is None :
142
+ line_to_display = "N/A"
143
+
144
+ if args .skip_outdated and is_effectively_outdated :
118
145
continue
119
146
120
- line_to_display = comment .get ("original_line" ) if is_outdated else comment .get ("line" )
121
- # Ensure line_to_display has a fallback if None from both
122
- if line_to_display is None : line_to_display = "N/A"
123
-
147
+ # Update latest timestamp (only for comments that will be printed)
148
+ if created_at_str :
149
+ try :
150
+ # GitHub ISO format "YYYY-MM-DDTHH:MM:SSZ"
151
+ # Python <3.11 fromisoformat needs "+00:00" not "Z"
152
+ if sys .version_info < (3 , 11 ):
153
+ dt_str = created_at_str .replace ("Z" , "+00:00" )
154
+ else :
155
+ dt_str = created_at_str
156
+ current_comment_dt = datetime .datetime .fromisoformat (dt_str )
157
+ if latest_created_at_obj is None or current_comment_dt > latest_created_at_obj :
158
+ latest_created_at_obj = current_comment_dt
159
+ except ValueError :
160
+ sys .stderr .write (f"Warning: Could not parse timestamp: { created_at_str } \n " )
124
161
125
162
user = comment .get ("user" , {}).get ("login" , "Unknown user" )
126
163
path = comment .get ("path" , "N/A" )
127
-
128
164
body = comment .get ("body" , "" ).strip ()
129
- if not body : # Skip comments with no actual text body
165
+
166
+ if not body :
130
167
continue
131
168
132
169
diff_hunk = comment .get ("diff_hunk" )
133
170
html_url = comment .get ("html_url" , "N/A" )
134
171
comment_id = comment .get ("id" )
135
172
in_reply_to_id = comment .get ("in_reply_to_id" )
136
- created_at = comment .get ("created_at" )
137
-
138
- status_text = "[OUTDATED]" if is_outdated else "[CURRENT]"
139
173
140
- # Start printing comment details
141
174
print (f"Comment by: { user } (ID: { comment_id } ){ f' (In Reply To: { in_reply_to_id } )' if in_reply_to_id else '' } " )
142
- if created_at :
143
- print (f"Timestamp: { created_at } " )
175
+ if created_at_str :
176
+ print (f"Timestamp: { created_at_str } " )
144
177
145
178
print (f"Status: { status_text } " )
146
179
print (f"File: { path } " )
147
180
print (f"Line in File Diff: { line_to_display } " )
148
181
print (f"URL: { html_url } " )
149
182
150
183
print ("--- Diff Hunk Context ---" )
184
+ print ("```" ) # Start of Markdown code block
151
185
if diff_hunk and diff_hunk .strip ():
152
186
hunk_lines = diff_hunk .split ('\n ' )
153
187
if args .context_lines == 0 : # User wants the full hunk
@@ -157,14 +191,36 @@ def main():
157
191
actual_lines_to_print = hunk_lines [- lines_to_print_count :]
158
192
for line_content in actual_lines_to_print :
159
193
print (line_content )
160
- # If context_lines < 0, argparse should ideally prevent this or it's handled by default type int.
161
- # No explicit handling here means it might behave unexpectedly or error if not positive/zero.
162
194
else :
163
195
print ("(No diff hunk available for this comment)" )
196
+ print ("```" ) # End of Markdown code block
164
197
165
198
print ("--- Comment ---" )
166
199
print (body )
167
200
print ("----------------------------------------\n " )
168
201
202
+ if latest_created_at_obj :
203
+ try :
204
+ # Ensure it's UTC before adding timedelta, then format
205
+ next_since_dt = latest_created_at_obj .astimezone (timezone .utc ) + timedelta (seconds = 1 )
206
+ next_since_str = next_since_dt .strftime ('%Y-%m-%dT%H:%M:%SZ' )
207
+
208
+ new_cmd_args = [sys .argv [0 ]]
209
+ skip_next_arg = False
210
+ for i in range (1 , len (sys .argv )):
211
+ if skip_next_arg :
212
+ skip_next_arg = False
213
+ continue
214
+ if sys .argv [i ] == "--since" :
215
+ skip_next_arg = True
216
+ continue
217
+ new_cmd_args .append (sys .argv [i ])
218
+
219
+ new_cmd_args .extend (["--since" , next_since_str ])
220
+ suggested_cmd = " " .join (new_cmd_args )
221
+ sys .stderr .write (f"\n To get comments created after the last one in this batch, try:\n { suggested_cmd } \n " )
222
+ except Exception as e :
223
+ sys .stderr .write (f"\n Warning: Could not generate next command suggestion: { e } \n " )
224
+
169
225
if __name__ == "__main__" :
170
226
main ()
0 commit comments