blob: 4f912000909b95ebc50fb0ee712c7959e01f1dd5 [file] [log] [blame] [edit]
#!/bin/bash
# Copyright 2025 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#### CATEGORY=Code submission and review
### Perform a Gemini review on the current commit
## usage: fx g-review
##
## Performs a Gemini review of the current commit
## Runs in a new headless instance to preserve any gemini-cli context
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
source "${SCRIPT_DIR}/../lib/vars.sh" || exit $?
source "${SCRIPT_DIR}/../lib/metrics.sh" || exit $?
fx-config-read
G_REVIEW_CONFIG_DIR="${HOME}/.fuchsia"
G_REVIEW_CONFIG="${G_REVIEW_CONFIG_DIR}/g-review"
if [[ -f "${G_REVIEW_CONFIG}" ]]; then
source "${G_REVIEW_CONFIG}"
else
mkdir -p "${G_REVIEW_CONFIG_DIR}"
SALT="$(fx-uuid)"
echo "SALT=\"${SALT}\"" > "${G_REVIEW_CONFIG}"
source "${G_REVIEW_CONFIG}"
fi
set -euo pipefail
pre_agree=false
while (( "$#" )); do
case "$1" in
-y)
pre_agree=true
shift
;;
*)
shift
;;
esac
done
echo -e >&2 "Creating a Gemini Code Review of the current commit at HEAD"
echo -e >&2 "Staged and uncommited changes are ignored."
if ! commit_hash="$(git rev-parse --short --verify HEAD)"; then
fx-error "No commit found. Cannot review."
exit 1
fi
PROMPT="${FUCHSIA_DIR}/.gemini/g-review_prompt.md"
if [[ ! -f "${PROMPT}" ]]; then
fx-error "Prompt template not found at ${PROMPT}"
exit 1
fi
echo -e >&2 "Reviewing changes in: ${commit_hash}"
git show --pretty="format:" --name-status HEAD >&2
use_tui="$(fx-get-ui-mode "fx-g-review")"
gerrit_change_id="$(git show --no-patch --format=%B | grep "Change-Id:" | awk '{print $2}')"
if [[ -z "${gerrit_change_id}" ]]; then
echo >&2 "Warning: Could not find Change-Id in commit message. Creating a random ID for analytics"
gerrit_change_id="$(fx-uuid)"
fi
md5_output="$(printf "%s" "${gerrit_change_id}${SALT}" | md5sum)"
private_identifier=${md5_output%% *}
if ! command -v gemini >/dev/null 2>&1; then
echo "Error: 'gemini' command not found in path. Please install Gemini CLI." >&2
exit 1
fi
# Call gemini with the prompt from stdin
json_response="$( (cat "${PROMPT}"; echo; git show HEAD) | gemini -p )" || {
fx-error "Gemini command failed."
exit 1
}
# So far Gemini always respects the prompt to create a JSON object for return.
# The response seems to always come in a markdown block: strip the fences.
# Following https://github.com/google-gemini/gemini-cli/issues/5021#issue-3268410106
#
# Some gemini-cli clients return an additional json header with gemini-cli metadata.
# We need to find the expected json block to parse, to be robust to those clients.
json_data="$(echo "${json_response}" | awk '/^```json/ {p=1; sub(/^```json/, ""); print; next} p' | sed 's/```[[:space:]]*$//')"
if ! echo "${json_data}" | fx jq -e . > /dev/null; then
fx-error "Gemini response was not valid JSON"
echo -e >&2 "RAW RESPONSE:"
echo "${json_response}" >&2
exit 1
fi
text="$(echo "${json_data}" | fx jq -r '.response_text')"
diff="$(echo "${json_data}" | fx jq -r '.diff')"
lgtm="$(echo "${json_data}" | fx jq -r '.lgtm')"
num="$(echo "${json_data}" | fx jq -r '.number_of_suggestions')"
if [[ "${use_tui}" == "tui" ]]; then
fx gum format "${text}" | fx gum style \
--border=rounded \
--margin="1 4" \
--padding="1 4" \
--align="left" \
--width="$(( "${COLUMNS:-80}" - 10 ))"
else
echo "${text}"
fi
if [[ -n "${diff}" ]]; then
echo -e >&2 "Diff of suggested changes:"
echo -e >&2 "${diff}"
TMP_DIR="${FUCHSIA_DIR}/tmp/g-review"
mkdir -p "${TMP_DIR}"
diff_file="$(mktemp "${TMP_DIR}/${commit_hash}.XXXXXX.diff")"
echo "${diff}" > "${diff_file}"
echo -e >&2 "(diff saved at ${diff_file})"
fi
confirm() {
local prompt="${1:-Are you sure?} [Y/n] " # Default prompt
local response
while true; do
read -r -p "${prompt}" response
case "${response}" in
[yY]*|"") # Matches 'y', 'Y', 'yes', 'YES', etc.
return 0 # Success (yes)
;;
[nN]*) # Matches 'n', 'N', 'no', 'NO', etc.
return 1 # Failure (no)
;;
*)
echo "Invalid input. Please enter 'yes' or 'no'." >&2
;;
esac
done
}
echo
G_REVIEW_LABEL="[g-review]"
LAST_MESSAGE="$(git log -1 --pretty=%B)"
if [[ "${LAST_MESSAGE}" != *"${G_REVIEW_LABEL}"* ]]; then
if [[ "${pre_agree}" == "true" ]] || confirm "Can we append '[g-review]' to your git commit message to better track the impact of gemini reviews on CL review time?"; then
# Insert the label on a new line before the Change-Id footer.
if echo "${LAST_MESSAGE}" | grep -q '^Change-Id:'; then
NEW_MESSAGE="$(echo "${LAST_MESSAGE}" | sed "/^Change-Id:/i ${G_REVIEW_LABEL}\n")"
git commit --amend -m "${NEW_MESSAGE}"
fi
fi
fi
echo
if confirm "Did you find this review helpful?"; then
helpful=1
else
helpful=0
fi
json_string="$(fx jq -n \
--arg cl "${private_identifier}" \
--arg num "${num}" \
--arg lgtm "${lgtm}" \
--arg helpful "${helpful}" \
'{cl: $cl, num: $num, lgtm: $lgtm, helpful: $helpful}')"
track-subcommand-custom-event "g-review" "review" "${json_string}"