#!/usr/bin/env bash # Validates Flyway schema migrations under db/migration/. # - No duplicate version numbers in the repo # - Migrations already on the base branch are immutable # - New migrations must use a version number greater than the highest on the base branch # # Usage: scripts/check-flyway-migrations.sh [base-ref] # Default base ref: origin/master (override with FLYWAY_BASE_REF) set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" MIGRATION_DIR="backend/src/main/resources/db/migration" BASE_REF="${1:-${FLYWAY_BASE_REF:-origin/master}}" cd "$ROOT" if [[ ! -d "$MIGRATION_DIR" ]]; then echo "Missing migration directory: $MIGRATION_DIR" >&2 exit 1 fi declare -A seen_versions=() max_local=0 for file in "$MIGRATION_DIR"/V*.sql; do [[ -e "$file" ]] || continue name="$(basename "$file")" if [[ ! "$name" =~ ^V([0-9]+)__.+\.sql$ ]]; then echo "ERROR: Invalid migration filename (expected V__description.sql): $name" >&2 exit 1 fi version="${BASH_REMATCH[1]}" version=$((10#$version)) if [[ -n "${seen_versions[$version]:-}" ]]; then echo "ERROR: Duplicate Flyway version V${version}:" >&2 echo " ${seen_versions[$version]}" >&2 echo " $name" >&2 exit 1 fi seen_versions[$version]="$name" if (( version > max_local )); then max_local=$version fi done if ! git rev-parse --verify "$BASE_REF" >/dev/null 2>&1; then echo "Flyway check: unique versions OK (max V${max_local}). Skipping base-branch rules (${BASE_REF} not found)." exit 0 fi max_base=0 while IFS= read -r path; do [[ -z "$path" ]] && continue name="$(basename "$path")" if [[ "$name" =~ ^V([0-9]+)__.+\.sql$ ]]; then version="${BASH_REMATCH[1]}" version=$((10#$version)) if (( version > max_base )); then max_base=$version fi fi done < <(git ls-tree -r --name-only "$BASE_REF" -- "$MIGRATION_DIR" 2>/dev/null | grep -E '/V[0-9]+__.*\.sql$' || true) while IFS= read -r file; do [[ -z "$file" ]] && continue if git cat-file -e "$BASE_REF:$file" 2>/dev/null; then if ! git diff --quiet "$BASE_REF" -- "$file"; then echo "ERROR: Modified existing migration (immutable after merge): $file" >&2 echo "Create a new V$((max_base + 1))__... migration instead of editing $file." >&2 exit 1 fi else name="$(basename "$file")" version="${name#V}" version="${version%%__*}" version=$((10#$version)) if (( version <= max_base )); then echo "ERROR: New migration $name uses V${version}, but ${BASE_REF} already has migrations up to V${max_base}." >&2 echo "Use V$((max_base + 1))__your_description.sql or rebase and renumber." >&2 exit 1 fi fi done < <(git ls-files "$MIGRATION_DIR"/V*.sql) echo "Flyway check OK (local max V${max_local}, ${BASE_REF} max V${max_base})."