From eae66ce5757272964d0219d0e1e19cafdcfe4386 Mon Sep 17 00:00:00 2001 From: Faun Date: Tue, 11 Aug 2026 14:50:29 -0700 Subject: [PATCH 1/2] Show signature status in the gg log graph Verifying commits is a habit worth making visible, so surface each commit's signature state right next to the hash instead of having to run a separate verification pass. The comment records what the codes mean and the SSH caveat, since N reads as "unsigned" whether the commit really is unsigned or git simply lacks an allowed signers file to check against. --- shrc/git.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shrc/git.sh b/shrc/git.sh index bb301d0b..cd4befff 100644 --- a/shrc/git.sh +++ b/shrc/git.sh @@ -428,9 +428,13 @@ gg() { if [[ "$(current_branch)" != "$mainline_ref" ]]; then git fetch origin "$mainline_ref" fi + # %G? is the signature status: G good, B bad, U good but untrusted, X expired, + # Y expired key, R revoked key, E cannot check, N none. Note that an SSH-signed + # commit reports N unless gpg.ssh.allowedSignersFile is set, because git cannot + # even attempt verification without it, which is indistinguishable from unsigned. git log \ --graph \ - --pretty=format:'%Cred%h%Creset %aN: %s %Cgreen(%cr)%Creset' \ + --pretty=format:'%Cred%h%Creset %C(magenta)%G?%Creset %aN: %s %Cgreen(%cr)%Creset' \ --abbrev-commit \ --date=relative \ "$(current_branch)" \ From e4f1513b97fe8e4e60e5f3783fc879ef7b55b258 Mon Sep 17 00:00:00 2001 From: Faun Date: Wed, 2 Sep 2026 11:48:42 -0700 Subject: [PATCH 2/2] Verify the signatures gg's status column shows The column landed with the SSH caveat recorded but not resolved, so every line of gg output was preceded by an error and every commit read as N. Commits here are ssh-signed through 1Password, and git cannot attempt verification of an ssh signature without an allowed signers file, which makes the status indistinguishable from a genuinely unsigned commit. Configure the file and the column starts saying something: G for mine, N for unsigned, U for a signer I have no key for. The keys come from the ssh signing keys registered on GitHub rather than from ~/.ssh, because history carries four of them from different machines and GitHub has all four. Reading the local key alone would leave anything signed elsewhere reading U. The generated file stays out of this repo: it is public, and the moment it grew a colleague's key it would be publishing their address. Re-run the install step with --force after registering a new key. gg also drops the %G? token entirely when verification is impossible, so a machine without a signers file shows no column rather than a column of misleading Ns. --- config/git/config | 6 ++++ install/01_configure_git_signing.sh | 49 +++++++++++++++++++++++++++++ shrc/git.sh | 29 ++++++++++++++--- 3 files changed, 79 insertions(+), 5 deletions(-) create mode 100755 install/01_configure_git_signing.sh diff --git a/config/git/config b/config/git/config index 18a4df89..0c62fabe 100644 --- a/config/git/config +++ b/config/git/config @@ -57,6 +57,12 @@ [core] whitespace=fix,-indent-with-non-tab,trailing-space,cr-at-eol +[gpg "ssh"] + # Signature verification is impossible without this: git reports every + # ssh-signed commit as unsigned and errors out per commit. Generated from + # your GitHub-registered signing keys by install/01_configure_git_signing.sh. + allowedSignersFile = ~/.config/git/allowed_signers + [apply] whitespace = nowarn diff --git a/install/01_configure_git_signing.sh b/install/01_configure_git_signing.sh new file mode 100755 index 00000000..8bcfaa58 --- /dev/null +++ b/install/01_configure_git_signing.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Build the ssh allowed-signers file git needs to verify commit signatures. +# +# Without it git cannot even attempt verification: every signed commit reads as +# unsigned, and anything printing %G? (see gg in shrc/git.sh) errors once per +# commit. Keys come from GitHub rather than ~/.ssh so commits signed on another +# machine still verify here. Re-run after registering a new signing key: +# +# ./install.sh --force 01_configure_git_signing + +set -euo pipefail + +# Hardcoded instead of XDG_CONFIG_HOME because config/git/config has to name +# this path literally: git config values cannot expand environment variables. +SIGNERS_FILE="${HOME:?}/.config/git/allowed_signers" + +if ! command -v gh >/dev/null 2>&1; then + echo "gh is not installed; skipping allowed signers setup" + exit 0 +fi + +email="$(git config --get user.email || true)" +github_user="$(git config --get github.user || true)" + +if [[ -z $email ]] || [[ -z $github_user ]]; then + echo "user.email and github.user must be set (see ~/.gitconfig.local); skipping allowed signers setup" + exit 0 +fi + +tmpfile="$(mktemp)" +trap 'rm -f "$tmpfile"' EXIT + +# This endpoint is public, so it does not matter which account gh is +# authenticated as. +if ! gh api "/users/$github_user/ssh_signing_keys" --jq '.[].key' | + awk -v email="$email" 'NF { printf "%s namespaces=\"git\" %s\n", email, $0 }' >"$tmpfile"; then + echo "Could not fetch signing keys for $github_user; leaving $SIGNERS_FILE unchanged" + exit 0 +fi + +if [[ ! -s $tmpfile ]]; then + echo "No ssh signing keys registered for $github_user; leaving $SIGNERS_FILE unchanged" + exit 0 +fi + +mkdir -p "$(dirname "$SIGNERS_FILE")" +mv "$tmpfile" "$SIGNERS_FILE" +chmod 644 "$SIGNERS_FILE" +echo "Wrote $(grep -c '' "$SIGNERS_FILE") signing keys to $SIGNERS_FILE" diff --git a/shrc/git.sh b/shrc/git.sh index cd4befff..09906bff 100644 --- a/shrc/git.sh +++ b/shrc/git.sh @@ -412,6 +412,21 @@ divergent() { fi } +# Whether `git log` can verify the signatures it is about to print. git picks the +# verification backend from each signature's own payload, so an ssh-signed commit +# needs gpg.ssh.allowedSignersFile regardless of gpg.format; without it git errors +# once per commit and reports every commit as unsigned. Signing with ssh locally is +# a good proxy for "this history is ssh-signed", so a missing signers file only +# rules verification out in that case. +git_can_verify_signatures() { + local signers + signers="$(git config --get gpg.ssh.allowedSignersFile)" + if [[ -n $signers ]] && [[ -f ${signers/#\~/$HOME} ]]; then + return 0 + fi + [[ "$(git config --get gpg.format)" != "ssh" ]] +} + gg() { # Validate git setup before proceeding if ! validate_git_setup; then @@ -428,13 +443,17 @@ gg() { if [[ "$(current_branch)" != "$mainline_ref" ]]; then git fetch origin "$mainline_ref" fi - # %G? is the signature status: G good, B bad, U good but untrusted, X expired, - # Y expired key, R revoked key, E cannot check, N none. Note that an SSH-signed - # commit reports N unless gpg.ssh.allowedSignersFile is set, because git cannot - # even attempt verification without it, which is indistinguishable from unsigned. + # %G? is the signature status: G good, B bad, U good but untrusted (signer not in + # the allowed signers file), X expired, Y expired key, R revoked key, E cannot + # check, N none. Dropped entirely when git cannot verify, so an unverifiable + # setup shows no column rather than a column of misleading Ns. + local sig='' + if git_can_verify_signatures; then + sig='%C(magenta)%G?%Creset ' + fi git log \ --graph \ - --pretty=format:'%Cred%h%Creset %C(magenta)%G?%Creset %aN: %s %Cgreen(%cr)%Creset' \ + --pretty=format:"%Cred%h%Creset ${sig}%aN: %s %Cgreen(%cr)%Creset" \ --abbrev-commit \ --date=relative \ "$(current_branch)" \