Checking signatures locally is voluntary; requiring them happens on the server. An update hook runs once per pushed ref with the ref name and its old and new hashes, so it can apply a rule to main alone. Sam installs one next to the pre-receive hook of Fail or Warn?, with the server's own copy of the allowed signers:
cat > ../server/booknest.git/hooks/update <<'EOF'
#!/bin/sh
# Accept new commits on main only if they carry a signature from a trusted key
ref=$1 old=$2 new=$3
[ "$ref" = refs/heads/main ] || exit 0
for c in $(git rev-list "$old..$new"); do
git -c gpg.ssh.allowedSignersFile=allowed_signers verify-commit "$c" 2>/dev/null ||
{ echo "error: $(git rev-parse --short "$c") is not signed by a trusted key"; exit 1; }
done
EOF
chmod +x ../server/booknest.git/hooks/update
cp .allowed_signers ../server/booknest.git/allowed_signers
# Edit README.md: note that main accepts only signed commits
git commit -q --no-gpg-sign -am "Document the signed-commit rule"
git push -q; echo "exit status $?"
git commit -q --amend --no-edit
git push -q && git log --format="%h %G? %s" -1... remote: error: 7109779 is not signed by a trusted key remote: error: hook declined to update refs/heads/main To ../server/booknest.git ! [remote rejected] main -> main (hook declined) error: failed to push some refs to '../server/booknest.git' exit status 1 ... d8a6fe2 G Document the signed-commit rule
--no-gpg-sign played the teammate without signing set up; --amend re-signed the commit because commit.gpgSign is on. The hook reads the allowed signers from the server, never from the pushed commits: otherwise a pusher could add their own key in the same push. Hosted platforms offer the same rule as "require signed commits" (GitHub). Require signatures on the branches that ship, and leave feature branches free for cheap rebases.