chore: the pre-push hook refuses unsigned commits for main, as the deploy does
All checks were successful
CI / check (push) Successful in 11m56s
CI / deploy (push) Successful in 19s

deploy/redeploy.sh deploys main only if every new commit is signed by a key
in deploy/allowed-signers, and finds out after CI. On 2026-10-09 that held
learn.halkyon.eu at v0.2.2 for two days: 37 commits had reached main
unsigned, and every deploy since refused them. tool/check_signatures.sh
applies the same rule to a push, to the commits it brings to main that the
remote doesn't have yet (the list as the pushed commit has it), and
tool/check.sh runs it first when it runs as the pre-push hook: a second,
where the rest takes minutes. CI runs check.sh under its own name and
skips it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-09 18:56:18 +02:00
commit 3bbdb9d747
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 53 additions and 1 deletions

View file

@ -10,6 +10,12 @@
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
# As the pre-push hook, first: commits going to main must be signed as deploying it requires
# (tool/check_signatures.sh), which takes a moment, where the rest takes minutes.
if [ "$(basename "$0")" = pre-push ]; then
tool/check_signatures.sh "$@"
fi
if ! git diff --quiet HEAD; then
echo 'Note: the working tree has uncommitted changes; they are checked too.'
fi

46
tool/check_signatures.sh Executable file
View file

@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Refuses a push to main that brings a commit not signed by a key in deploy/allowed-signers:
# the rule deploy/redeploy.sh applies before it deploys main, after CI passed. Checked here, a
# push of an unsigned commit fails on the laptop instead of after CI (it did on 2026-10-09,
# when 37 such commits had kept learn.halkyon.eu at v0.2.2 since 2026-10-07).
#
# tool/check.sh runs it first as the pre-push hook, passing on git's arguments (the remote) and
# stdin (a line per ref pushed: local ref, local commit, remote ref, remote commit). It checks
# the commits pushed to main that no ref of that remote has yet. By hand, for what main would
# bring:
#
# printf 'refs/heads/main %s refs/heads/main 0\n' "$(git rev-parse main)" |
# tool/check_signatures.sh origin
set -euo pipefail
remote=${1:-origin}
allowed=$(mktemp)
trap 'rm -f "$allowed"' EXIT
unsigned=''
while read -r _local_ref local_sha remote_ref _remote_sha; do
[ "$remote_ref" = refs/heads/main ] || continue
# Deleting a branch pushes no commits.
case $local_sha in *[!0]*) ;; *) continue ;; esac
# The list as the pushed commit has it; its comment lines match no fingerprint.
git show "$local_sha:deploy/allowed-signers" >"$allowed"
unsigned+=$(git log --format='%H %G? %GP' "$local_sha" --not --remotes="$remote" |
while read -r commit status key; do
# G: a good signature; U: good, by a key the keyring doesn't vouch for (the list does).
if { [ "$status" = G ] || [ "$status" = U ]; } && [ -n "$key" ] &&
grep -qx "$key" "$allowed"; then
continue
fi
echo "$commit"
done)$'\n'
done
if [ -n "${unsigned//[[:space:]]/}" ]; then
echo 'Not signed by a key in deploy/allowed-signers, which deploying main requires:' >&2
for commit in $unsigned; do
git log -1 --format=' %h %s' "$commit" >&2
done
echo 'Sign them, e.g. git rebase --force-rebase --gpg-sign <the commit before the first>,' \
'and push again.' >&2
exit 1
fi