|
| 1 | +#!/usr/bin/env bash |
| 2 | +# |
| 3 | +# Helper script to run code review using Gemini |
| 4 | +# |
| 5 | +# Usage: |
| 6 | +# ./hack/review.sh # Review current branch vs origin/main |
| 7 | +# ./hack/review.sh --pr 1234 # Review specific PR |
| 8 | +# ./hack/review.sh --files file1.go file2.go # Review specific files |
| 9 | + |
| 10 | +# set -euo pipefail |
| 11 | + |
| 12 | +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 13 | +REPO_ROOT="${SCRIPT_DIR}" |
| 14 | + |
| 15 | +# Default values |
| 16 | +PR_NUMBER="" |
| 17 | +PR_REMOTE="${PR_REMOTE:-origin}" # Can be overridden via env var (e.g., PR_REMOTE=upstream) |
| 18 | + |
| 19 | +# Determine the default branch from the remote |
| 20 | +DEFAULT_BRANCH=$(git remote show "${PR_REMOTE}" 2>/dev/null | grep 'HEAD branch' | cut -d' ' -f5) |
| 21 | +if [[ -z "$DEFAULT_BRANCH" ]]; then |
| 22 | + # Fallback to main if remote is not configured or branch not found |
| 23 | + DEFAULT_BRANCH="main" |
| 24 | +fi |
| 25 | +DIFF_RANGE="${PR_REMOTE}/${DEFAULT_BRANCH}...HEAD" |
| 26 | + |
| 27 | +FILES=() |
| 28 | + |
| 29 | +# Pre-parse arguments to handle --files |
| 30 | +for arg in "$@"; do |
| 31 | + case "$arg" in |
| 32 | + -f|--files) |
| 33 | + # When reviewing specific files, |
| 34 | + # default to showing local changes against HEAD instead of a branch diff. |
| 35 | + DIFF_RANGE="HEAD" |
| 36 | + break |
| 37 | + ;; |
| 38 | + esac |
| 39 | +done |
| 40 | + |
| 41 | + |
| 42 | +function usage() { |
| 43 | + cat <<EOF |
| 44 | +Usage: $0 [OPTIONS] |
| 45 | +
|
| 46 | +Run automated code review using Gemini agent. |
| 47 | +
|
| 48 | +Options: |
| 49 | + -p, --pr NUMBER Review a specific pull request number |
| 50 | + -f, --files FILE... Review only specific files |
| 51 | + -h, --help Show this help message |
| 52 | +
|
| 53 | +Environment Variables: |
| 54 | + PR_REMOTE Git remote to use for fetching PRs (default: origin) |
| 55 | + Set to 'upstream' if working from a fork |
| 56 | +
|
| 57 | +Examples: |
| 58 | + $0 # Review current changes vs main |
| 59 | + $0 --pr 1234 # Review PR #1234 (uses gh CLI if available) |
| 60 | + PR_REMOTE=upstream $0 --pr 1234 # Review PR from upstream remote |
| 61 | + $0 --files pkg/cvo/cvo.go # Review specific file |
| 62 | +
|
| 63 | +Notes: |
| 64 | + - PR reviews work best with 'gh' CLI installed: https://cli.github.com/ |
| 65 | + - Without 'gh', falls back to git fetch (requires proper remote configuration) |
| 66 | + - When working from a fork, use PR_REMOTE=upstream to fetch from main repository |
| 67 | +EOF |
| 68 | +} |
| 69 | + |
| 70 | +# Parse arguments |
| 71 | +while [[ $# -gt 0 ]]; do |
| 72 | + case $1 in |
| 73 | + -p|--pr) |
| 74 | + PR_NUMBER="$2" |
| 75 | + shift 2 |
| 76 | + ;; |
| 77 | + -f|--files) |
| 78 | + shift |
| 79 | + # stip options until next flag or end of args |
| 80 | + while [[ $# -gt 0 ]] && [[ ! $1 =~ ^- ]]; do |
| 81 | + if [[ ! $1 =~ ^vendor/ ]]; then |
| 82 | + FILES+=("$1") |
| 83 | + fi |
| 84 | + shift |
| 85 | + done |
| 86 | + ;; |
| 87 | + -h|--help) |
| 88 | + usage |
| 89 | + exit 0 |
| 90 | + ;; |
| 91 | + *) |
| 92 | + echo "Unknown option: $1" |
| 93 | + usage |
| 94 | + exit 1 |
| 95 | + ;; |
| 96 | + esac |
| 97 | +done |
| 98 | + |
| 99 | +cd "$REPO_ROOT" || exit 0 |
| 100 | + |
| 101 | +# Get diff based on input |
| 102 | +if [[ -n "$PR_NUMBER" ]]; then |
| 103 | + PR_REF="pull/${PR_NUMBER}/head" |
| 104 | + FETCH_SUCCESS="false" |
| 105 | + |
| 106 | + # Find the correct remote that has the PR ref by trying all of them. |
| 107 | + for remote in $(git remote); do |
| 108 | + echo "Attempting to fetch PR #${PR_NUMBER} from remote '$remote'..." |
| 109 | + if git fetch "$remote" "$PR_REF" 2>/dev/null; then |
| 110 | + echo "Successfully fetched PR from remote '$remote'." |
| 111 | + PR_REMOTE=$remote # Set the successful remote for the diff operation below |
| 112 | + FETCH_SUCCESS="true" |
| 113 | + break |
| 114 | + fi |
| 115 | + done |
| 116 | + |
| 117 | + if [[ "$FETCH_SUCCESS" == "false" ]]; then |
| 118 | + echo "" |
| 119 | + echo "Error: Could not fetch PR #${PR_NUMBER} from any of your configured remotes." |
| 120 | + echo "Attempted remotes: $(git remote | tr '\n' ' ')" |
| 121 | + echo "" |
| 122 | + echo "This can happen if:" |
| 123 | + echo " - You are working on a fork and have not configured a remote for the upstream repository." |
| 124 | + echo " - The PR doesn't exist or is closed." |
| 125 | + echo " - You don't have network access to the repository." |
| 126 | + echo "" |
| 127 | + echo "Possible solutions:" |
| 128 | + echo " 1. Add the main repository as a remote. e.g.:" |
| 129 | + echo " git remote add upstream https://github.com/openshift/cluster-version-operator.git" |
| 130 | + echo " 2. Manually fetch the PR into a local branch." |
| 131 | + exit 1 |
| 132 | + fi |
| 133 | + |
| 134 | + # Determine the base branch for the diff |
| 135 | + echo "Determining base branch for comparison..." |
| 136 | + # First, update the remote's HEAD branch information locally |
| 137 | + git remote set-head "$PR_REMOTE" --auto &>/dev/null |
| 138 | + |
| 139 | + # Now, get the HEAD branch name from the updated remote info |
| 140 | + BASE_BRANCH=$(git remote show "$PR_REMOTE" 2>/dev/null | grep 'HEAD branch' | cut -d' ' -f5) |
| 141 | + # Fallback to master for this specific repository if lookup fails |
| 142 | + BASE_BRANCH=${BASE_BRANCH:-master} |
| 143 | + echo "Base branch identified as: ${PR_REMOTE}/${BASE_BRANCH}" |
| 144 | + |
| 145 | + DIFF=$(git diff "${PR_REMOTE}/${BASE_BRANCH}...FETCH_HEAD" -- . ':(exclude)vendor/*') |
| 146 | + FILES_CHANGED=$(git diff --name-only "${PR_REMOTE}/${BASE_BRANCH}...FETCH_HEAD" -- . ':(exclude)vendor/*') |
| 147 | + |
| 148 | + echo "Successfully generated diff for PR #${PR_NUMBER}." |
| 149 | + |
| 150 | +elif [[ ${#FILES[@]} -gt 0 ]]; then |
| 151 | + echo "Reviewing specified files: ${FILES[*]}" |
| 152 | + DIFF=$(git diff "$DIFF_RANGE" -- "${FILES[@]}") |
| 153 | + FILES_CHANGED=$(printf '%s |
| 154 | +' "${FILES[@]}") |
| 155 | +else |
| 156 | + echo "Reviewing changes in range: $DIFF_RANGE" |
| 157 | + DIFF=$(git diff "$DIFF_RANGE" -- . ':(exclude)vendor/*') |
| 158 | + FILES_CHANGED=$(git diff --name-only "$DIFF_RANGE" -- . ':(exclude)vendor/*') |
| 159 | +fi |
| 160 | + |
| 161 | +if [[ -z "$DIFF" ]]; then |
| 162 | + echo "No changes found to review." |
| 163 | + exit 0 |
| 164 | +fi |
| 165 | + |
| 166 | +echo "Files changed:" |
| 167 | +echo "$FILES_CHANGED" |
| 168 | +echo "" |
| 169 | +echo "Generating code review..." |
| 170 | +echo "" |
| 171 | + |
| 172 | +# Create prompt for the agent |
| 173 | +REPO_NAME=$(git remote get-url origin | sed -e 's/.*[:/]\([^/]*\/[^/]*\)\.git$/\1/' -e 's/.*[:/]\([^/]*\/[^/]*\)$/\1/') |
| 174 | + |
| 175 | +REVIEW_PROMPT=$(cat <<REVIEW_PROMPT_EOF |
| 176 | +Please perform a code review for the following changes in the ${REPO_NAME} repository. |
| 177 | +
|
| 178 | +As a reviewer, you must follow these instructions: |
| 179 | +1. **Analyze the provided git diff.** The user has provided the diff of the changes. |
| 180 | +2. **Apply Go best practices.** Refer to "Effective Go" for guidance on idiomatic Go, including formatting, naming conventions, and. |
| 181 | +3. **Consider oc-specific conventions.** Since 'oc' is built on 'kubectl', ensure that kubectl compatibility is maintained, OpenShift-specific commands follow existing patterns, and CLI output is consistent. |
| 182 | +4. **Check for code quality issues.** Look for potential race conditions, resource leaks, and proper context propagation. |
| 183 | +5. **Verify documentation.** Ensure that exported functions and types have doc comments, and complex logic is explained. |
| 184 | +6. **Provide a structured summary of your findings.** Organize your feedback clearly. |
| 185 | +
|
| 186 | +Files changed: |
| 187 | +${FILES_CHANGED} |
| 188 | +
|
| 189 | +Git diff: |
| 190 | +${DIFF} |
| 191 | +
|
| 192 | +Provide your code review based on the instructions above. |
| 193 | +REVIEW_PROMPT_EOF |
| 194 | +) |
| 195 | + |
| 196 | +# Save prompt to temp file for gemini CLI |
| 197 | +PROMPT_FILE=$(mktemp) |
| 198 | +echo "$REVIEW_PROMPT" > "$PROMPT_FILE" |
| 199 | + |
| 200 | +echo "=== CODE REVIEW RESULTS ===" |
| 201 | +echo "" |
| 202 | + |
| 203 | +# Check if gemini CLI is available |
| 204 | +if command -v gemini &> /dev/null; then |
| 205 | + # Use Gemini CLI if available |
| 206 | + gemini -y "$(cat "$PROMPT_FILE")" |
| 207 | +else |
| 208 | + # Fallback: Print instructions for manual review |
| 209 | + echo "gemini CLI not found." |
| 210 | + echo "" |
| 211 | + echo "To perform the review, copy the following prompt to Gemini:" |
| 212 | + echo "---------------------------------------------------------------" |
| 213 | + cat "$PROMPT_FILE" |
| 214 | + echo "---------------------------------------------------------------" |
| 215 | +fi |
| 216 | + |
| 217 | +rm -f "$PROMPT_FILE" |
0 commit comments