#!/usr/bin/env bash # # Nothing uncommitted goes out with a push. # # ## Where this came from # # There was already a `pre-push` in `.git/hooks` on this checkout when the # template was adopted on 2026-08-18. Setting `core.hooksPath` to this directory # would have silently stopped it running — the whole class of failure these # hooks exist to prevent, applied to a hook — so its useful half was moved here, # where it is versioned and every clone gets it. # # ## What was kept, and what was dropped # # **Kept:** the two working-tree checks. A push that leaves edits behind is how # documentation ends up one commit adrift of the code it describes, and # `docs/WORK_CYCLE.md` is built on those travelling together. # # **Dropped:** its third check, which refused the push when the branch was ahead # of its remote. That is the precondition for pushing at all, so it fired on # every real push and its only instruction was to re-run with `--no-verify` — a # guard that can never pass teaches people to bypass the two beside it that can. # # ## Escape hatch # # git push --no-verify # # Deliberately not silent about it: the checks below print what they found # before refusing, so an intentional bypass is a decision with the evidence in # front of it. # # Exit 0 nothing outstanding, 1 something is. set -uo pipefail cd "$(git rev-parse --show-toplevel)" || exit 1 say() { printf '\033[1mpre-push:\033[0m %s\n' "$*" >&2; } fail=0 if ! git diff --quiet --exit-code; then say "uncommitted working-tree changes:" git diff --name-only | sed 's/^/ /' >&2 fail=1 fi if ! git diff --cached --quiet --exit-code; then say "staged but uncommitted changes:" git diff --cached --name-only | sed 's/^/ /' >&2 fail=1 fi if [ "$fail" -ne 0 ]; then say "" say "push refused. Commit or stash the above first — a push that leaves edits" say "behind is how the documents end up describing a commit nobody is running." say "To push anyway: git push --no-verify" exit 1 fi exit 0