Queue-North-Website/.githooks/pre-push

63 lines
2.0 KiB
Bash
Executable File

#!/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