Repository navigation
Add OpenPGP and X.509 commit signing - #2827
anandghegde wants to merge 1 commit into
Conversation
SSH commit signing landed in git-up#2789, which deliberately left the other two formats alone: a repository configured with commit.gpgsign and an OpenPGP or X.509 gpg.format silently produced an unsigned commit. This extends the same machinery to cover both, so GitUp now honours commit.gpgsign for every format Git itself supports. The signature is produced by running the configured signing program the way Git runs it, "<program> --status-fd=2 -bsau <key>", with the commit buffer on stdin. Program resolution follows Git's rules: gpg.openpgp.program then gpg.program (its legacy synonym) then "gpg" for OpenPGP, and gpg.x509.program then "gpgsm" for X.509, with gpg.program deliberately not applying to X.509. When user.signingkey is unset, the committer identity is passed as the user ID, which is what Git does so that GnuPG selects the key matching the commit rather than whichever key it considers its own default. Success is decided by GnuPG's machine-readable status rather than its exit code alone, because GnuPG can exit zero without having produced a signature. That is the reason for --status-fd=2, and it matches how Git validates the same invocation. An unrecognised gpg.format is now an error instead of quietly producing an unsigned commit. Git rejects it too, and silently dropping the signature is the worst available outcome for someone who asked for signed commits. Every one of these rules was verified against git 2.50.1 by pointing gpg.program at a recording stub and comparing the resulting argument vector.
|
|
Thanks for this @anandghegde, it's a feature I've wanted in GitUp for a long time ❤️ . The program lookup order, the Non-ASCII committer names break the default signing key
return [NSString stringWithFormat:@"%s <%s>", committer->name, committer->email];
return GCUserFromSignature(committer);A test with a non-ASCII Optional: clearer errors when gpg fails
The most common failure from a GUI app is gpg-agent trying to ask for a passphrase in a terminal it doesn't have ( Follow-up (will make a PR): commits created by history operations are still unsignedLike #2789, this only signs commits made on HEAD: normal commits, merges, amend and conflict resolution. Every other way GitUp creates commits goes through
git signs those commits again (e.g. A heads-up for whoever takes this on: switching Follow-up (will make a PR): look up
|
|
I think the UTF-8 point is valid and should be addressed before merging. Thank you @anandghegde for the PR and I'll gladly merge after that fix. |
I AGREE TO THE GITUP CONTRIBUTOR LICENSE AGREEMENT
Closes #42.
SSH commit signing landed in #2789. That change deliberately scoped itself to SSH and left a comment saying so:
This picks up the other two formats, so
commit.gpgsignis now honoured for everygpg.formatGit supports. That closes the long-standing request in #42, where people have been dropping to the command line just to commit.The signing call
The signature comes from running the configured program exactly the way Git runs it —
<program> --status-fd=2 -bsau <key>with the commit buffer on stdin — reusing theGCTaskplumbing and the once-per-operationPATHrefresh that #2789 already put in place.Program resolution follows Git's rules:
openpgp(and unset)gpg.openpgp.program, thengpg.programgpgx509gpg.x509.programgpgsmgpg.programis Git's legacy synonym forgpg.openpgp.programand deliberately does not apply to X.509 — easy to get wrong, so there's a test pinning it.When
user.signingkeyis unset, the committer identity is passed as the user ID. Git does this so GnuPG picks the key matching the commit rather than whichever key GnuPG considers its own default; omitting-uentirely would quietly sign with a different key thangit commitwould have used.Why
--status-fd=2GnuPG can exit zero without having produced a signature. Git guards against that by asking for the machine-readable status and requiring
SIG_CREATED, and this does the same. Trusting the exit code alone would mean writing a commit with a garbagegpgsigheader.Behaviour change worth calling out
An unrecognised
gpg.formatis now an error rather than a silently unsigned commit. Git rejects it too (error: invalid value for 'gpg.format': 'pgp'), and for someone who explicitly asked for signed commits, silently dropping the signature is the worst of the available outcomes. The existing test that asserted "OpenPGP config remains unsigned" is updated accordingly — it now covers the still-correct case of a format configured withoutcommit.gpgsign.Verification
Every rule above was checked against the real thing rather than against the documentation. I pointed
gpg.program/gpg.openpgp.program/gpg.x509.programat a stub that records its argument vector, rangit commitunder git 2.50.1, and compared:--status-fd=2 -bsau <key>, in that orderuser.signingkeyforwarded verbatim when setBot <bot@example.com>— the committer identity — when it is genuinely unsetgpg.openpgp.programbeatinggpg.programgpg.x509.programused for X.509 withgpg.programignoredThe unit tests drive the same stub through
GCCreateCommitFromTreeWithOptionalSignatureand assert the same argument vectors, so the parity is pinned rather than just observed once.Seven new tests in
GCCommitSigning-Tests.mcover the default format, program precedence, the signing key fallback, X.509, a GnuPG that exits zero without signing, a GnuPG that fails outright, and an invalidgpg.format../format-source.shis clean and the app builds.One gap I want to be upfront about: there is no end-to-end test against a real
gpg, because that would need a throwaway keyring generated at test time and GnuPG isn't guaranteed on the runner. The stub tests pin the invocation; they can't prove GnuPG is happy with it. The SSH tests could go further there only becausessh-keygenis always present.Unrelated:
GCSingleCommitRepositoryTests/testClonefails for me on a cleanmastertoo — it clones over the network.