Files
computer-playbook/scripts/git/assert/merge_signed.sh
T
kevinveenbirkenbach f2e9ce04d4 fix(git-hooks): run the merge-signed gate on prepare-commit-msg
git writes MERGE_HEAD only after the pre-merge-commit hook returns, so the
gate never found it and aborted every non-fast-forward merge with "No merge
in progress"; git then wrote the merge state via abort_commit(), leaving a
half-finished merge that git status reported as "still merging". The gate
had therefore never verified a single signature.

git also skips pre-merge-commit entirely once a merge conflicts, so the
conflict-resolution commit was unguarded as well. prepare-commit-msg fires
with MERGE_HEAD present on both paths and no-ops on ordinary commits.

Verified in a scratch repo with a real pre-commit install: ordinary commit
passes silently, clean automerge and conflict-resolution commit both block
an unsigned incoming commit, and the extra stash/restore cycle round-trips
unstaged changes byte-identically.

pre_commit install never removes hook types dropped from the config, so the
install script now also uninstalls the stale pre-merge-commit hook.
2026-07-28 17:01:24 +02:00

23 lines
773 B
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# Merge gate: every commit the merge brings in (HEAD..MERGE_HEAD) must carry
# a signature. `git log %G?` yields N for unsigned; any other status counts
# as signed, matching the test-signed HEAD gate.
git_dir="$(git rev-parse --git-dir)"
if [ ! -f "${git_dir}/MERGE_HEAD" ]; then
exit 0
fi
unsigned="$(git log --pretty='%H %G? %s' HEAD..MERGE_HEAD | awk '$2 == "N"')"
if [ -n "${unsigned}" ]; then
echo "❌ Unsigned commit(s) coming in from the merged branch:" >&2
printf '%s\n' "${unsigned}" >&2
echo "Sign them (git rebase --exec 'git commit --amend --no-edit -S' or git-sign-push) and retry the merge." >&2
exit 1
fi
count="$(git rev-list --count HEAD..MERGE_HEAD)"
echo "✅ All ${count} incoming commit(s) are signed."