#!/usr/bin/env bash set -euo pipefail # Usage: # check-history.sh [--verbose] # Example: # check-history.sh 2 4 # checks commits 2,3,4,5 # check-history.sh 5 1 # checks only commit 5 # check-history.sh 1 1 --verbose # verbose mode VERBOSE=0 if [ $# -lt 2 ] || [ $# -gt 3 ]; then echo "Usage: $0 [--verbose|-v]" exit 1 fi if [ $# -eq 3 ] && { [ "$3" = "--verbose" ] || [ "$3" = "-v" ]; }; then VERBOSE=1 fi # Debug logging helper debug() { if [ "$VERBOSE" -eq 1 ]; then echo "[DEBUG] $*" >&2 fi } # Convert user-friendly 1-indexed "Nth from HEAD" into 0-indexed offset START_OFFSET="$(( $1 - 1 ))" COUNT="$2" REPO_ROOT="$(git rev-parse --show-toplevel)" TMP_BASE="/tmp/check-history-$$" WORKTREES_DIR="$TMP_BASE/worktrees" LOGS_DIR="$TMP_BASE/logs" PNPM_STORE="$HOME/.cache/check-history/pnpm-store" debug "REPO_ROOT: $REPO_ROOT" debug "TMP_BASE: $TMP_BASE" debug "PNPM_STORE: $PNPM_STORE" mkdir -p "$WORKTREES_DIR" "$LOGS_DIR" "$PNPM_STORE" echo "Checking commits from offset $1 (HEAD~$START_OFFSET) for $COUNT commits…" echo # Build list of commits as if by `git rebase -i HEAD~N` (HEAD=0 newest) as if by `git rebase -i HEAD~N` # HEAD = index 0, parent = index 1, etc. mapfile -t HEAD_TO_OLD <&2 exit 1 fi # Compute slice from BEGIN_INDEX to START_INDEX inclusive COMMITS=("${ALL_COMMITS[@]:$BEGIN_INDEX:$COUNT}") debug "Selected commits:" for c in "${COMMITS[@]}"; do debug " $c" done # ----------------------------- # Frontend builder # ----------------------------- build_frontend() { ( [ "$VERBOSE" -eq 1 ] && echo "[BUILD_DEBUG] Entering rfc-edge-frontend directory" || true cd rfc-edge-frontend || { echo "❌ Failed to cd into rfc-edge-frontend" return 1 } [ "$VERBOSE" -eq 1 ] && echo "[BUILD_DEBUG] cd succeeded, PWD=$PWD" || true export PNPM_HOME="$PWD/.pnpm_home" export PNPM_STORE_PATH="$PNPM_STORE" export PNPM_HARD_LINKS=false # Copy .env file from main repo if it exists and isn't present in worktree if [ ! -f .env ] && [ -f "$REPO_ROOT/rfc-edge-frontend/.env" ]; then [ "$VERBOSE" -eq 1 ] && echo "[BUILD_DEBUG] Copying .env file from main repo" || true cp "$REPO_ROOT/rfc-edge-frontend/.env" .env elif [ -f .env ]; then [ "$VERBOSE" -eq 1 ] && echo "[BUILD_DEBUG] .env file already exists in worktree" || true else [ "$VERBOSE" -eq 1 ] && echo "[BUILD_DEBUG] No .env file found in main repo" || true fi [ "$VERBOSE" -eq 1 ] && echo "[BUILD_DEBUG] Environment variables set:" || true [ "$VERBOSE" -eq 1 ] && echo "[BUILD_DEBUG] PNPM_HOME=$PNPM_HOME" || true [ "$VERBOSE" -eq 1 ] && echo "[BUILD_DEBUG] PNPM_STORE_PATH=$PNPM_STORE_PATH" || true mkdir -p "$PNPM_HOME" "$PNPM_STORE_PATH" [ "$VERBOSE" -eq 1 ] && echo "[BUILD_DEBUG] Created pnpm directories" || true [ "$VERBOSE" -eq 1 ] && echo "[BUILD_DEBUG] Running pnpm install..." || true if [ "$VERBOSE" -eq 1 ]; then pnpm install --frozen-lockfile /dev/null 2>&1 fi || { echo "❌ pnpm install failed" return 1 } [ "$VERBOSE" -eq 1 ] && echo "[BUILD_DEBUG] pnpm install succeeded" || true [ "$VERBOSE" -eq 1 ] && echo "[BUILD_DEBUG] Running pnpm build..." || true if [ "$VERBOSE" -eq 1 ]; then pnpm build /dev/null 2>&1 fi || { echo "❌ pnpm build failed" return 1 } [ "$VERBOSE" -eq 1 ] && echo "[BUILD_DEBUG] pnpm build succeeded" || true [ "$VERBOSE" -eq 1 ] && echo "[BUILD_DEBUG] Running pnpm check..." || true if [ "$VERBOSE" -eq 1 ]; then pnpm check /dev/null 2>&1 fi || { echo "❌ pnpm check failed" return 1 } [ "$VERBOSE" -eq 1 ] && echo "[BUILD_DEBUG] pnpm check succeeded" || true ) } # ----------------------------- # Ctrl-C cleanup # ----------------------------- cleanup() { echo " Caught interrupt, cleaning worktrees…" >&2 git worktree prune >/dev/null 2>&1 || true rm -rf "$TMP_BASE" >/dev/null 2>&1 || true exit 130 } trap cleanup INT # ----------------------------- # Commit checker # ----------------------------- check_commit() { local commit="$1" local worktree="$2" local log="$3" [ "$VERBOSE" -eq 1 ] && debug "check_commit() called for $commit" || true [ "$VERBOSE" -eq 1 ] && debug " worktree: $worktree" || true [ "$VERBOSE" -eq 1 ] && debug " log: $log" || true ( set -euo pipefail echo "===== COMMIT $commit =====" [ "$VERBOSE" -eq 1 ] && echo "[CHECK_DEBUG] Adding worktree at $worktree" || true git worktree add --force --detach "$worktree" "$commit" >/dev/null [ "$VERBOSE" -eq 1 ] && echo "[CHECK_DEBUG] Worktree created, changing directory" || true cd "$worktree" [ "$VERBOSE" -eq 1 ] && echo "[CHECK_DEBUG] In worktree, PWD=$PWD" || true if [ -d rfc-edge-frontend ]; then [ "$VERBOSE" -eq 1 ] && echo "[CHECK_DEBUG] Found rfc-edge-frontend directory" || true echo "→ Building frontend…" if ! build_frontend; then echo "❌ Frontend build failed" exit 1 fi [ "$VERBOSE" -eq 1 ] && echo "[CHECK_DEBUG] Frontend build completed successfully" || true else [ "$VERBOSE" -eq 1 ] && echo "[CHECK_DEBUG] No rfc-edge-frontend directory found, skipping frontend build" || true fi echo "→ Running cargo check…" [ "$VERBOSE" -eq 1 ] && echo "[CHECK_DEBUG] Starting cargo check" || true if ! cargo check --quiet; then echo "❌ Cargo check failed" exit 1 fi [ "$VERBOSE" -eq 1 ] && echo "[CHECK_DEBUG] Cargo check completed successfully" || true echo "✓ OK" ) >"$log" 2>&1 return $? } # ----------------------------- # Sequential execution # ----------------------------- FAIL=0 i=1 TOTAL=${#COMMITS[@]} for commit in "${COMMITS[@]}"; do worktree="$WORKTREES_DIR/$commit" log="$LOGS_DIR/$commit.log" debug "===== Processing commit $i/$TOTAL =====" debug "Commit: $commit" debug "Worktree: $worktree" debug "Log file: $log" echo "[ $i / $TOTAL ] Checking $commit…" if ! check_commit "$commit" "$worktree" "$log"; then echo "❌ FAILED at commit $commit" sed 's/^/ /' "$log" FAIL=1 break fi debug "Commit $commit passed" sed 's/^/ /' "$log" debug "Removing worktree at $worktree" git worktree remove --force "$worktree" >/dev/null 2>&1 ((i++)) echo done # ----------------------------- # Final output # ----------------------------- if [ "$FAIL" -eq 1 ]; then echo "❌ Stopped early due to failure." debug "Cleaning up: removing $TMP_BASE" else echo "🎉 All commits passed!" debug "All commits verified successfully" debug "Cleaning up: removing $TMP_BASE" fi rm -rf "$TMP_BASE" >/dev/null 2>&1 debug "Cleanup complete"