Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
da2efa9
feat: Add script to fetch PR review comments
google-labs-jules[bot] Jun 7, 2025
0678049
feat: Enhance PR comment script with context and filters
google-labs-jules[bot] Jun 7, 2025
e84b02d
fix: Correct IndentationError in get_pr_review_comments.py
google-labs-jules[bot] Jun 7, 2025
5948b96
fix: Correct --context-lines behavior for non-line-specific comments
google-labs-jules[bot] Jun 7, 2025
565eed2
feat: Simplify diff hunk display and add comment filters
google-labs-jules[bot] Jun 7, 2025
24a03ea
refactor: Update script description and format diff hunks
google-labs-jules[bot] Jun 7, 2025
7e182aa
fix: Adjust 'next command' timestamp increment to 2 seconds
google-labs-jules[bot] Jun 7, 2025
599845b
docs: Minor textual cleanups in PR comments script
google-labs-jules[bot] Jun 7, 2025
77d1ed2
feat: Format output as Markdown for improved readability
google-labs-jules[bot] Jun 7, 2025
9cb8d42
style: Adjust Markdown headings for structure and conciseness
google-labs-jules[bot] Jun 7, 2025
203e88f
style: Adjust default context lines and Markdown spacing
google-labs-jules[bot] Jun 7, 2025
b900c7f
feat: Refactor comment filtering with new status terms and flags
google-labs-jules[bot] Jun 7, 2025
5a4010f
feat: Improve context display and suggested command robustness
google-labs-jules[bot] Jun 7, 2025
94417e7
style: Refactor hunk printing to use join for conciseness
google-labs-jules[bot] Jun 7, 2025
9312a0c
fix: Align 'since' filter and next command with observed API behavior…
google-labs-jules[bot] Jun 7, 2025
07d06bb
style: Condense printing of trailing hunk lines
google-labs-jules[bot] Jun 7, 2025
7c7a269
chore: Remove specific stale developer comments
google-labs-jules[bot] Jun 9, 2025
91bfae6
fix: Ensure removal of specific stale developer comments
google-labs-jules[bot] Jun 9, 2025
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions scripts/gha/firebase_github.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,49 @@ def get_reviews(token, pull_number):
return results


def get_pull_request_review_comments(token, pull_number, since=None): # Added since=None
"""https://docs.github.com/en/rest/pulls/comments#list-review-comments-on-a-pull-request"""
url = f'{GITHUB_API_URL}/pulls/{pull_number}/comments'
headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'}

page = 1
per_page = 100
results = []

# Base parameters for the API request
base_params = {'per_page': per_page}
if since:
base_params['since'] = since

while True: # Loop indefinitely until explicitly broken
current_page_params = base_params.copy()
current_page_params['page'] = page

try:
with requests_retry_session().get(url, headers=headers, params=current_page_params,
stream=True, timeout=TIMEOUT) as response:
response.raise_for_status()
# Log which page and if 'since' was used for clarity
logging.info("get_pull_request_review_comments: %s params %s response: %s", url, current_page_params, response)

current_page_results = response.json()
if not current_page_results: # No more results on this page
break # Exit loop, no more comments to fetch

results.extend(current_page_results)

# If fewer results than per_page were returned, it's the last page
if len(current_page_results) < per_page:
break # Exit loop, this was the last page

page += 1 # Increment page for the next iteration

except requests.exceptions.RequestException as e:
logging.error(f"Error fetching review comments (page {page}, params: {current_page_params}) for PR {pull_number}: {e}")
break # Stop trying if there's an error
return results


def create_workflow_dispatch(token, workflow_id, ref, inputs):
"""https://docs.github.com/en/rest/reference/actions#create-a-workflow-dispatch-event"""
url = f'{GITHUB_API_URL}/actions/workflows/{workflow_id}/dispatches'
Expand Down
170 changes: 170 additions & 0 deletions scripts/gha/get_pr_review_comments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#!/usr/bin/env python3
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Fetches and formats review comments from a GitHub Pull Request."""

import argparse
import os
import sys
import firebase_github # Assumes firebase_github.py is in the same directory or python path

# Attempt to configure logging for firebase_github if absl is available
try:
from absl import logging as absl_logging
# Set verbosity for absl logging if you want to see logs from firebase_github
# absl_logging.set_verbosity(absl_logging.INFO)
except ImportError:
pass # firebase_github.py uses absl.logging.info, so this won't redirect.

def main():
default_owner = firebase_github.OWNER
default_repo = firebase_github.REPO

parser = argparse.ArgumentParser(
description="Fetch review comments from a GitHub PR and format for use with Jules.",
formatter_class=argparse.RawTextHelpFormatter
)
parser.add_argument(
"--pull_number",
type=int,
required=True,
help="Pull request number."
)
parser.add_argument(
"--owner",
type=str,
default=default_owner,
help=f"Repository owner. Defaults to '{default_owner}'."
)
parser.add_argument(
"--repo",
type=str,
default=default_repo,
help=f"Repository name. Defaults to '{default_repo}'."
)
parser.add_argument(
"--token",
type=str,
default=os.environ.get("GITHUB_TOKEN"),
help="GitHub token. Can also be set via GITHUB_TOKEN env var."
)
parser.add_argument(
"--context-lines",
type=int,
default=10, # Default to 10 lines, 0 means full hunk.
help="Number of context lines from the diff hunk. Use 0 for the full hunk. "
"If > 0, shows the last N lines of the hunk. Default: 10."
)
parser.add_argument(
"--since",
type=str,
default=None,
help="Only show comments created at or after this ISO 8601 timestamp (e.g., YYYY-MM-DDTHH:MM:SSZ)."
)
parser.add_argument(
"--skip-outdated",
action="store_true",
help="If set, outdated comments will not be printed."
)

args = parser.parse_args()

if not args.token:
sys.stderr.write("Error: GitHub token not provided. Set GITHUB_TOKEN or use --token.\n")
sys.exit(1)

if args.owner != firebase_github.OWNER or args.repo != firebase_github.REPO:
repo_url = f"https://github.com/{args.owner}/{args.repo}"
if not firebase_github.set_repo_url(repo_url):
sys.stderr.write(f"Error: Invalid repo URL: {args.owner}/{args.repo}. Expected https://github.com/owner/repo\n")
sys.exit(1)
print(f"Targeting repository: {firebase_github.OWNER}/{firebase_github.REPO}", file=sys.stderr)

print(f"Fetching comments for PR #{args.pull_number} from {firebase_github.OWNER}/{firebase_github.REPO}...", file=sys.stderr)
if args.since:
print(f"Filtering comments created since: {args.since}", file=sys.stderr)
if args.skip_outdated:
print("Skipping outdated comments.", file=sys.stderr)


comments = firebase_github.get_pull_request_review_comments(
args.token,
args.pull_number,
since=args.since
)

if not comments:
print(f"No review comments found for PR #{args.pull_number} (or matching filters), or an error occurred.", file=sys.stderr)
return

print("\n--- Review Comments ---")
for comment in comments:
# Determine outdated status and effective line for display
is_outdated = comment.get("position") is None

if args.skip_outdated and is_outdated:
continue

line_to_display = comment.get("original_line") if is_outdated else comment.get("line")
# Ensure line_to_display has a fallback if None from both
if line_to_display is None: line_to_display = "N/A"


user = comment.get("user", {}).get("login", "Unknown user")
path = comment.get("path", "N/A")

body = comment.get("body", "").strip()
if not body: # Skip comments with no actual text body
continue

diff_hunk = comment.get("diff_hunk")
html_url = comment.get("html_url", "N/A")
comment_id = comment.get("id")
in_reply_to_id = comment.get("in_reply_to_id")
created_at = comment.get("created_at")

status_text = "[OUTDATED]" if is_outdated else "[CURRENT]"

# Start printing comment details
print(f"Comment by: {user} (ID: {comment_id}){f' (In Reply To: {in_reply_to_id})' if in_reply_to_id else ''}")
if created_at:
print(f"Timestamp: {created_at}")

print(f"Status: {status_text}")
print(f"File: {path}")
print(f"Line in File Diff: {line_to_display}")
print(f"URL: {html_url}")

print("--- Diff Hunk Context ---")
if diff_hunk and diff_hunk.strip():
hunk_lines = diff_hunk.split('\n')
if args.context_lines == 0: # User wants the full hunk
print(diff_hunk)
elif args.context_lines > 0: # User wants N lines of context (last N lines)
lines_to_print_count = args.context_lines
actual_lines_to_print = hunk_lines[-lines_to_print_count:]
for line_content in actual_lines_to_print:
print(line_content)
# If context_lines < 0, argparse should ideally prevent this or it's handled by default type int.
# No explicit handling here means it might behave unexpectedly or error if not positive/zero.
else:
print("(No diff hunk available for this comment)")

print("--- Comment ---")
print(body)
print("----------------------------------------\n")

if __name__ == "__main__":
main()
Loading