From 4ce2f14f7533a8a651e10ccffcb56ae76c3ea520 Mon Sep 17 00:00:00 2001 From: Joakim Persson Date: Sun, 20 Sep 2026 00:31:34 +0200 Subject: [PATCH] hooks: actually replace the generator in setup-hooks.sh (ebabdce shipped the old one) ebabdce's message said setup-hooks.sh now activates the tracked hook instead of generating a copy of it. The committed file was still the generator. This is that change, for real. How the wrong file got committed, since the mechanism is worth knowing: I wrote the new setup-hooks.sh, ran it, then hit an unrelated bad test and undid it with `git reset --hard HEAD~1`. setup-hooks.sh is TRACKED, so my uncommitted edit to it was reverted by that reset -- while the new, UNTRACKED hooks/pre-commit survived, because reset --hard does not touch untracked files. `git add setup-hooks.sh` then staged the restored generator, and the commit described what I had written rather than what was in the tree. Nothing failed loudly: the file was valid shell, shellcheck passed, and I verified the HOOK's behaviour rather than the SCRIPT's, so the evidence I collected was real but about the wrong file. Effect of the defect: hooks/pre-commit and core.hooksPath were correct, so this clone was gated the whole time. But anyone running ./setup-hooks.sh would have re-created .git/hooks/pre-commit -- reinstating the very copy ebabdce removed, where it is now INERT (core.hooksPath wins) and looks like protection while never running. The activator now also VERIFIES its own effect: it reads core.hooksPath back and exits 1 if it is not 'hooks', because a gate that was never activated behaves exactly like one with nothing to report. Checked before committing this time: 0 heredoc generators present, core.hooksPath set at line 20, and running it creates NO .git/hooks/pre-commit. --- setup-hooks.sh | 62 +++++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/setup-hooks.sh b/setup-hooks.sh index 501d1ea..9f9ee45 100755 --- a/setup-hooks.sh +++ b/setup-hooks.sh @@ -1,39 +1,39 @@ #!/bin/bash -# Install git hooks for this project +# Activate the repo's tracked git hooks for this clone. +# +# Hooks live under hooks/ (version-controlled) and are wired via core.hooksPath, +# so every machine gets the same hooks after running this once. +# +# This script used to GENERATE .git/hooks/pre-commit from a heredoc. That made the +# running hook a copy, and a copy drifts: measured 2026-09-19, the installed hook on +# one machine was an older revision than this script emitted, having lost the Linux +# gitleaks install hint. core.hooksPath runs the tracked file itself, so the hook +# that runs and the hook in git history cannot disagree. +# +# core.hooksPath is LOCAL config and is never cloned, which is why this step exists +# at all: a fresh clone has hooks/ and no active gate until someone runs this. set -e -HOOK_DIR="$(git rev-parse --show-toplevel)/.git/hooks" -mkdir -p "$HOOK_DIR" +cd "$(git rev-parse --show-toplevel)" -# --- pre-commit hook: secret scanning with gitleaks --- -cat > "$HOOK_DIR/pre-commit" << 'HOOK' -#!/bin/bash -# Pre-commit hook — scans staged files for secrets using gitleaks +git config core.hooksPath hooks +chmod +x hooks/* 2>/dev/null || true -if ! command -v gitleaks >/dev/null 2>&1; then - echo "" - echo "⚠️ gitleaks is not installed — skipping secret scan" - echo " Install: brew install gitleaks (macOS)" - echo " Or: curl -sSL https://github.com/gitleaks/gitleaks/releases/latest/download/gitleaks_\$(uname -s)_\$(uname -m).tar.gz | sudo tar -xz -C /usr/local/bin gitleaks" - echo "" - exit 0 -fi - -echo "🔒 Scanning for secrets..." - -if gitleaks protect --staged --no-banner 2>/dev/null; then - echo "✅ No secrets detected" - exit 0 -else - echo "" - echo "❌ Secrets detected in staged changes — commit blocked" - echo "" - echo " Details: gitleaks protect --staged --verbose" - echo " Bypass: git commit --no-verify" - echo "" +# Prove the setting took rather than trusting that it did: a hook that was never +# activated behaves exactly like one that has nothing to report. +active="$(git config --get core.hooksPath || true)" +if [ "$active" != "hooks" ]; then + echo "❌ core.hooksPath is '$active', not 'hooks' — the gate is NOT active." >&2 exit 1 fi -HOOK -chmod +x "$HOOK_DIR/pre-commit" -echo "✅ Pre-commit hook installed (.git/hooks/pre-commit)" +# A hook left behind by the old copy-based install would be shadowed by +# core.hooksPath and never run again, so say so rather than leaving a decoy. +if [ -e .git/hooks/pre-commit ]; then + echo "⚠️ .git/hooks/pre-commit still exists and is now INERT (core.hooksPath wins)." + echo " It is a leftover from the old copy-based install; remove it:" + echo " rm .git/hooks/pre-commit" +fi + +echo "✅ core.hooksPath set to hooks/ — tracked hooks are now active" +echo " (pre-commit secret scan via gitleaks)"