Created
May 5, 2025 01:03
-
-
Save lox/05ab04c2d1c7b5efe923434ee066bd25 to your computer and use it in GitHub Desktop.
A shell script for fetching pull request review comments based on current git context
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Script to fetch Pull Request review comments based on the current Git context. | |
set -euo pipefail # Exit on error, unset variable, or pipe failure | |
# --- Configuration --- | |
# Prefer 'origin' remote, fall back to 'upstream' | |
readonly PRIMARY_REMOTE="origin" | |
readonly FALLBACK_REMOTE="upstream" | |
# --- Helper Functions --- | |
# Print error message to stderr and exit | |
error_exit() { | |
echo "Error: $1" >&2 | |
exit 1 | |
} | |
# Check if a command exists in PATH | |
check_command() { | |
local cmd="$1" | |
if ! command -v "$cmd" &> /dev/null; then | |
error_exit "Required command '$cmd' is not installed or not found in PATH." | |
fi | |
} | |
# Get the remote URL, preferring primary, falling back to secondary | |
get_git_remote_url() { | |
local url | |
url=$(git config --get remote."$PRIMARY_REMOTE".url || true) # Allow command to fail without exiting script | |
if [[ -z "$url" ]]; then | |
echo "Warning: Remote '$PRIMARY_REMOTE' not found, trying '$FALLBACK_REMOTE'..." >&2 | |
url=$(git config --get remote."$FALLBACK_REMOTE".url || true) | |
fi | |
if [[ -z "$url" ]]; then | |
error_exit "Could not determine remote URL (checked '$PRIMARY_REMOTE' and '$FALLBACK_REMOTE')." | |
fi | |
echo "$url" | |
} | |
# Parse GitHub organization and repository name from a URL | |
parse_org_repo() { | |
local url="$1" | |
local org_repo | |
# Handles https://, git@, and removes .git suffix | |
org_repo=$(echo "$url" | sed -E 's/^(https?:\/\/|git@)[^:\/]+[:\/]//; s/\.git$//') | |
if [[ ! "$org_repo" == */* ]]; then | |
error_exit "Could not parse organization/repository from remote URL: $url" | |
fi | |
echo "$org_repo" # Returns format "org/repo" | |
} | |
# Get the current Git branch name | |
get_current_branch() { | |
local branch | |
branch=$(git branch --show-current) | |
if [[ -z "$branch" ]]; then | |
error_exit "Could not determine current branch." | |
fi | |
echo "$branch" | |
} | |
# Find the PR number associated with a branch and repo | |
# Tries open PRs first, then the most recent closed/merged PR. | |
find_pr_number() { | |
local branch="$1" | |
local org_repo="$2" | |
local pr_number | |
echo "--> Searching for open PR associated with branch '$branch'..." >&2 | |
pr_number=$(gh pr list --head "$branch" --repo "$org_repo" --state open --json number --limit 1 --jq '.[0].number // empty' || true) | |
if [[ -z "$pr_number" ]]; then | |
echo "--> No open PR found. Searching for most recent closed/merged PR..." >&2 | |
# Use --state all and filter with --search for merged/closed states and the specific head branch | |
# Also sort by updated date descending to get the most recent one. | |
pr_number=$(gh pr list --repo "$org_repo" --state all --search "head:\"$branch\" is:merged,closed sort:updated-desc" --json number --limit 1 --jq '.[0].number // empty' || true) | |
if [[ -z "$pr_number" ]]; then | |
error_exit "No open, closed, or merged Pull Request found for branch '$branch' in $org_repo." | |
else | |
echo "--> Found most recent closed/merged PR: #$pr_number" >&2 | |
fi | |
else | |
echo "--> Found open PR Number: #$pr_number" >&2 | |
fi | |
echo "$pr_number" | |
} | |
# Fetch comments for a given PR number | |
fetch_pr_comments() { | |
local org_repo="$1" | |
local pr_number="$2" | |
local api_path="/repos/$org_repo/pulls/$pr_number/comments" | |
local output | |
echo "--> Fetching comments for PR #$pr_number..." >&2 | |
# Try with cache first | |
output=$(gh api "$api_path" --cache 1h -q '[.[] | {reviewer: .user.login, comment: .body, file: .path, line: (.original_line // .line)}]' 2>/dev/null || true) | |
# Validate output or retry without cache | |
if [[ -z "$output" ]] || (! echo "$output" | jq empty > /dev/null 2>&1 ); then | |
echo "--> Retrying comment fetch without cache..." >&2 | |
output=$(gh api "$api_path" --cache 0 -q '[.[] | {reviewer: .user.login, comment: .body, file: .path, line: (.original_line // .line)}]' 2>/dev/null || true) | |
# Final validation after retry | |
if [[ -z "$output" ]] || (! echo "$output" | jq empty > /dev/null 2>&1 ); then | |
error_exit "Failed to fetch or process comments from GitHub API for PR #$pr_number, even after retry." | |
fi | |
fi | |
echo "$output" | |
} | |
# --- Main Script Logic --- | |
# --- Argument Parsing --- | |
pr_number_arg="" | |
if [[ $# -gt 0 ]] && [[ "$1" =~ ^[0-9]+$ ]]; then | |
pr_number_arg="$1" | |
echo "--> Using provided PR number: #$pr_number_arg" >&2 | |
fi | |
# --- Initial Checks --- | |
check_command "git" | |
check_command "gh" | |
check_command "jq" | |
if ! git rev-parse --is-inside-work-tree &> /dev/null; then | |
error_exit "Not inside a git repository." | |
fi | |
# Detect Context | |
remote_url=$(get_git_remote_url) | |
org_repo=$(parse_org_repo "$remote_url") | |
echo "--> Detected Repository: $org_repo" >&2 | |
# --- Determine PR Number --- | |
if [[ -n "$pr_number_arg" ]]; then | |
pr_number="$pr_number_arg" | |
# Optional: Add a check here to verify the PR exists in the repo? | |
# gh pr view "$pr_number" --repo "$org_repo" > /dev/null || error_exit "PR #$pr_number not found in $org_repo." | |
else | |
# Only detect branch and find PR if number wasn't provided | |
current_branch=$(get_current_branch) | |
echo "--> Detected Branch: $current_branch" >&2 | |
pr_number=$(find_pr_number "$current_branch" "$org_repo") | |
fi | |
# Fetch and Output Comments | |
comments_json=$(fetch_pr_comments "$org_repo" "$pr_number") | |
if [[ "$comments_json" == "[]" ]]; then | |
echo "--> No review comments found for PR #$pr_number." >&2 | |
else | |
# Format the output for better readability (e.g., for LLM input) | |
echo "Review comments for PR #$pr_number in $org_repo:" | |
echo "---" | |
echo "$comments_json" | jq -c '.[]' | while IFS= read -r comment_obj; do | |
file=$(echo "$comment_obj" | jq -r '.file') | |
line=$(echo "$comment_obj" | jq -r '.line') | |
reviewer=$(echo "$comment_obj" | jq -r '.reviewer') | |
comment_body=$(echo "$comment_obj" | jq -r '.comment') | |
echo "File: $file" | |
echo "Line: $line" | |
echo "Reviewer: $reviewer" | |
echo "Comment:" | |
# Indent comment body slightly for clarity | |
# Use parameter expansion or printf instead of sed for simple substitution | |
while IFS= read -r line; do | |
printf ' %s\n' "$line" | |
done <<< "$comment_body" | |
echo "---" | |
done | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment