#!/usr/bin/env bash # Fail if any versioned migration Vxxx already present on the base branch is modified # in the current HEAD (Flyway checksum mismatch risk). See CLAUDE.md (never rewrite applied V__). # # Usage: ci_check_migration_immutability.sh [BASE] # BASE — ref or full SHA (default: origin/main). For PRs pass base commit SHA. set -euo pipefail ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)" if [[ -z "$ROOT" ]]; then echo "ERROR: not inside a git repository" exit 1 fi cd "$ROOT" BASE="${1:-origin/main}" if [[ "$BASE" =~ ^[0-9a-f]{7,40}$ ]]; then if ! git cat-file -e "${BASE}^{commit}" 2>/dev/null; then git fetch --no-tags --depth=256 origin "$BASE" fi fi if ! git rev-parse --verify -q "${BASE}^{commit}" >/dev/null; then echo "ERROR: cannot resolve base revision: $BASE (fetch origin main?)" exit 1 fi max_ver_on_base() { git ls-tree -r --name-only "$BASE" -- db/migration 2>/dev/null \ | grep -E '^db/migration/V[0-9]+__' \ | sed -n 's/^db\/migration\/V\([0-9][0-9]*\)__.*/\1/p' \ | sort -n \ | tail -1 } MAX="$(max_ver_on_base || true)" MAX="${MAX:-0}" mapfile -t CHANGED < <(git diff --name-only "${BASE}...HEAD" -- 'db/migration/V*.sql' 2>/dev/null || true) if [[ ${#CHANGED[@]} -eq 0 ]]; then echo "ci_check_migration_immutability: no versioned migration changes vs $BASE (ok)" exit 0 fi err=0 for f in "${CHANGED[@]}"; do [[ "$f" =~ ^db/migration/V([0-9]+)__ ]] || continue ver="${BASH_REMATCH[1]}" ver=$((10#$ver)) if (( ver <= MAX )); then printf 'ERROR: do not modify historical migration %s (version %03d <= max on base %03d from %s). Add a new V* migration instead. See CLAUDE.md.\n' \ "$f" "$ver" "$MAX" "$BASE" >&2 err=1 fi done if (( err )); then exit 1 fi echo "ci_check_migration_immutability: ok (vs $BASE, max V on base=$MAX)"