set -euo pipefail
ROOT="$(git rev-parse --show-toplevel)"
cd "$ROOT"
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
if command -v gitleaks >/dev/null 2>&1; then
GL_CFG=""
if [ -f "$ROOT/.gitleaks.toml" ]; then
GL_CFG="$ROOT/.gitleaks.toml"
elif [ -f "$HOME/Code/.gitleaks.toml" ]; then
GL_CFG="$HOME/Code/.gitleaks.toml"
fi
gl_args=(git --staged --no-banner --redact)
[ -n "$GL_CFG" ] && gl_args+=(-c "$GL_CFG")
if ! gitleaks "${gl_args[@]}"; then
echo "pre-commit: gitleaks found a secret in the staged changes."
echo " remove it (or allowlist a false positive), then restage."
echo " bypass: git commit --no-verify."
exit 1
fi
echo "pre-commit: gitleaks clean."
else
echo "pre-commit: gitleaks not installed; skipping secret scan (astra gates on push)."
fi
touched="$(git diff --cached --name-only --diff-filter=MDR -- '*migrations/*.sql')"
if [ -n "$touched" ]; then
echo "pre-commit: these already-committed migrations were modified, renamed, or deleted:"
while IFS= read -r m; do
[ -n "$m" ] && echo " $m"
done <<< "$touched"
echo " A migration is immutable once applied; the runner checksums the"
echo " whole file, comments included. Write a new migration instead."
echo " Bypass ONLY if it has never been applied anywhere, including"
echo " prod, staging, and your dev database: git commit --no-verify."
exit 1
fi
staged_fe="$(git diff --cached --name-only --diff-filter=ACMR -- '*.js' '*.css' '*.html')"
if [ -n "$staged_fe" ]; then
while IFS= read -r lint; do
[ -n "$lint" ] || continue
if ! fe_out=$(bash "$lint" 2>&1); then
echo "$fe_out"
echo "pre-commit: frontend lint failed ($lint)."
echo " fix the rules above, then restage."
echo " bypass: git commit --no-verify."
exit 1
fi
echo "pre-commit: frontend lint clean ($lint)."
done <<< "$(find . -maxdepth 3 -path ./target -prune -o \
-path '*/scripts/lint-frontend.sh' -print 2>/dev/null | sort)"
fi
SKIP_PATHS="${SKIP_PATHS:-}"
staged="$(git diff --cached --name-only --diff-filter=ACMR -- '*.rs')"
if [ -n "$SKIP_PATHS" ]; then
staged="$(printf '%s\n' "$staged" | grep -Ev "$SKIP_PATHS" || true)"
fi
if [ -n "$staged" ]; then
crates=""
while IFS= read -r f; do
[ -n "$f" ] || continue
d="$(dirname "$f")"
while [ "$d" != "." ] && [ ! -f "$d/Cargo.toml" ]; do
d="$(dirname "$d")"
done
[ -f "$d/Cargo.toml" ] || continue
crates="$crates$d"$'\n'
done <<< "$staged"
crates="$(printf '%s' "$crates" | sort -u)"
failed=0
while IFS= read -r c; do
[ -n "$c" ] || continue
if ! (cd "$c" && cargo fmt --check >/dev/null 2>&1); then
echo "pre-commit: rustfmt gate failed in $c"
failed=1
fi
done <<< "$crates"
if [ "$failed" -ne 0 ]; then
echo "pre-commit: run 'cargo fmt' in the crates above, then restage."
echo "pre-commit: commit aborted (use --no-verify to bypass)."
exit 1
fi
echo "pre-commit: rustfmt gate clean."
fi
if [ -f "$ROOT/server/openapi.json" ]; then
specish="$(git diff --cached --name-only --diff-filter=ACMR \
-- 'server/Cargo.toml' 'server/src/*.rs' 'server/src/**/*.rs' 'server/openapi.json')"
if [ -n "$specish" ]; then
echo "pre-commit: checking openapi.json against the generated spec..."
gen="$(mktemp)"
trap 'rm -f "$gen"' EXIT
if (cd "$ROOT/server" && cargo run --quiet --bin export-openapi -- --stdout) > "$gen" 2>/dev/null; then
if ! git show :server/openapi.json 2>/dev/null | diff -q - "$gen" >/dev/null; then
echo "pre-commit: server/openapi.json is stale (or regenerated but not staged)."
echo " cd server && cargo run --bin export-openapi"
echo " git add server/openapi.json"
echo " Then vendor the same bytes into the OTHER repo, which this"
echo " commit cannot carry and Sando will fail on:"
echo " cp server/openapi.json ../synckit/synckit-client/tests/openapi.json"
echo " Bypass: git commit --no-verify."
exit 1
fi
echo "pre-commit: openapi.json current."
else
echo "pre-commit: could not build export-openapi; skipping spec check."
echo " cargo_test in Sando is the backstop, 15 minutes into the build."
fi
fi
fi
if [ -f "$ROOT/scripts/githooks/pre-commit.local" ]; then
bash "$ROOT/scripts/githooks/pre-commit.local" || exit 1
fi