diff --git a/git/hooks/pre-commit b/git/hooks/pre-commit index 877b9ad..fb7bf0b 100755 --- a/git/hooks/pre-commit +++ b/git/hooks/pre-commit @@ -74,4 +74,42 @@ if echo "$added_lines" | grep -qE "(password|secret|token|api_key)[[:space:]]*=[ fi fi +# ── Repo-declared checks ──────────────────────────────────────── (REVIEWED-100) +# This hook is global to every repo (core.hooksPath), so it must hold no repo +# knowledge. A repo opts in by declaring `.precommit-triggers` at its root: +# +# [more pathspecs] | +# +# If the staged diff touches a declared pathspec, the command runs and a non-zero +# exit refuses the commit. Path matching is delegated to git's own pathspec +# engine rather than reimplemented here. +# +# Deliberately dependency-free — no yq, no python. A global convention that needs +# a toolchain silently fails to travel to the next machine or repo, and a check +# that silently does not run is worse than no check, because its absence reads as +# a pass. That is why this departs from the YAML used by data python tools read. +repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)" +triggers_file="${repo_root:-.}/.precommit-triggers" +if [ -n "$repo_root" ] && [ -f "$triggers_file" ]; then + # fd 3, so a check that reads stdin cannot swallow the rest of this file + while IFS='|' read -r paths cmd <&3 || [ -n "${paths:-}" ]; do + case "${paths%%[![:space:]]*}${paths#"${paths%%[![:space:]]*}"}" in \#*) continue ;; esac + cmd="$(printf '%s' "${cmd:-}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + [ -n "$cmd" ] || continue + # shellcheck disable=SC2086 — word splitting is intended: multiple pathspecs + if [ -z "$(git diff --cached --name-only -- $paths 2>/dev/null)" ]; then + continue + fi + echo -e "${YELLOW}Staged change touches [${paths# }] — running declared check:${NC} $cmd" + if ! ( cd "$repo_root" && eval "$cmd" ) < /dev/null; then + echo -e "${RED}Declared check FAILED — commit refused.${NC}" + echo " declared in: .precommit-triggers" + echo " command: $cmd" + echo " --no-verify bypasses this; it is a tripwire, not a boundary." + exit 1 + fi + echo -e "${GREEN}Declared check passed.${NC}" + done 3< "$triggers_file" +fi + echo -e "${GREEN}Pre-commit checks passed!${NC}"