diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 0000000..35fdd4e --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Rejects a push that would put an unsigned or bad-signature commit onto a +# branch. Root cause this guards against: GUI git clients (e.g. Gittyup) +# commit via libgit2 directly and never invoke gpg, silently ignoring +# commit.gpgsign. Bypass intentionally with `git push --no-verify`. +zero=0000000000000000000000000000000000000000 + +while read -r local_ref local_sha remote_ref _remote_sha; do + [ "$local_sha" = "$zero" ] && continue # branch deletion + case "$remote_ref" in refs/heads/*) ;; *) continue ;; esac + + if [ "$_remote_sha" = "$zero" ]; then + range="$local_sha --not --remotes" + else + range="$_remote_sha..$local_sha" + fi + + bad="" + for sha in $(git rev-list $range --); do + sig="$(git log -1 --pretty=%G? "$sha")" + case "$sig" in + N | B) bad="$bad $sha" ;; + esac + done + + if [ -n "$bad" ]; then + echo "pre-push: unsigned or bad-signature commit(s) pushing to $remote_ref, refusing:" >&2 + for sha in $bad; do + git log -1 --pretty=' %h %G? %s' "$sha" >&2 + done + echo "Fix: git commit --amend -S (or rebase -i + amend), then push again." >&2 + echo "Bypass: git push --no-verify" >&2 + exit 1 + fi +done + +# Chain to the global/system hook this local override is shadowing. +global_hooks="$(git config --global core.hooksPath 2>/dev/null)" +[ -z "$global_hooks" ] && global_hooks="$(git config --system core.hooksPath 2>/dev/null)" +if [ -n "$global_hooks" ]; then + global_hooks="${global_hooks/#\~/$HOME}" # git stores ~ verbatim + [ -x "$global_hooks/pre-push" ] && exec "$global_hooks/pre-push" "$@" +fi +exit 0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 45b6d41..5f9f0c9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,6 +36,21 @@ If you're touching anything under `docs/manual/`, you'll also want `pandoc`, (see [Documentation Pipeline](#documentation-pipeline)) — otherwise CI will catch problems on push. +**Point your clone at the tracked git hooks.** `.githooks/pre-push` rejects a +push carrying an unsigned or bad-signature commit — GUI git clients (Gittyup +included) commonly commit via libgit2 and skip `commit.gpgsign` silently. +This isn't wired up automatically (most users of this config never push to +this repo), so opt in once per clone: + +```fish +git config core.hooksPath .githooks +``` + +Bypass a specific push with `git push --no-verify` if you have a genuine +reason to. See [Secrets & Machine-Specific +Config](#secrets--machine-specific-config) if you'd rather set this from +your own `local.fish` than type it by hand. + ## Issues Issues live on the Gitea repo. Three templates cover the common cases, each