Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config/git/config
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
49 changes: 49 additions & 0 deletions install/01_configure_git_signing.sh
Original file line number Diff line number Diff line change
@@ -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"
25 changes: 24 additions & 1 deletion shrc/git.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -428,9 +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 (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 %aN: %s %Cgreen(%cr)%Creset' \
--pretty=format:"%Cred%h%Creset ${sig}%aN: %s %Cgreen(%cr)%Creset" \
--abbrev-commit \
--date=relative \
"$(current_branch)" \
Expand Down