git: Consolidate signing UX for Commit and Tag objects, and introduce auto-sign via new plugin package#1860
Conversation
cedric-appdirect
left a comment
There was a problem hiding this comment.
I had some time to look at this PR and I see the benefit for having this plugins out-of-tree. I think we would need to add the Verifier API with trust level as I did in my draft PR to complete the design. I can work on that as a follow up to this PR if you don't have time.
I don't see any issue in using this API to degin signing or even verifying plugins once the API is added following the same principle.
|
@cedric-appdirect thanks for the review. I've created an issue to track the verification part of this feature. Feel free to start a discussion there in case you want to pick that issue up. |
There was a problem hiding this comment.
Pull request overview
This PR introduces a new x/plugin registry to enable application-level registration of an object signer and updates commit/tag creation to optionally auto-sign objects when commit.gpgSign=true or tag.gpgSign=true, aligning commit/tag signing UX and behavior more closely with Git.
Changes:
- Add a generic, thread-safe plugin registry (
x/plugin) and define anObjectSignerplugin key. - Update commit and tag creation to select a signer from explicit options first, then fall back to the
ObjectSignerplugin when config enables auto-signing. - Extend config support/tests for
commit.gpgSign,tag.gpgSign,gpg.format,gpg "ssh".allowedSignersFile, anduser.signingKey.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| x/plugin/plugin.go | Implements the plugin registry core (register/get/freeze) and test-reset hook. |
| x/plugin/plugin_signer.go | Defines the ObjectSigner plugin key and plugin-level Signer interface. |
| x/plugin/plugin_test.go | Adds registry behavior tests (freeze, overwrite, concurrency, etc.). |
| worktree_commit.go | Adds commit auto-sign selection logic using config + ObjectSigner plugin. |
| worktree_commit_test.go | Replaces PGP signing tests with signer-selection tests using a mock signer + linkname reset. |
| repository.go | Adds tag auto-sign selection logic and updates tag signing to use Signer instead of PGP key. |
| repository_test.go | Replaces PGP tag signing tests with signer-selection tests using the mock signer + linkname reset. |
| options.go | Replaces CreateTagOptions.SignKey with CreateTagOptions.Signer. |
| config/config.go | Adds config fields and marshal/unmarshal support for signing-related keys. |
| config/config_test.go | Adds tests for new config keys and round-trip behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Signed-off-by: Paulo Gomes <pjbgf@linux.com>
The plugin package provides a generic, thread-safe registry for plugin factory functions. It enables off-tree implementations to be registered and retrieved at runtime. The first plugin type introduced is ObjectSigner. When registered, the plugin will define a function factory which will be used when creating commits or tags. Signed-off-by: Paulo Gomes <pjbgf@linux.com>
When no CommitOptions.Signer is provided, buildCommitObject falls back to the globally registered ObjectSigner plugin. An explicit CommitOptions.Signer always takes precedence over the plugin. Signed-off-by: Paulo Gomes <pjbgf@linux.com>
These changes align the user experience, so that both Tags and Commits share the same API. The implementation details are now pushed back into the out-of-tree plugins, removing external dependencies from the core go-git logic. Signed-off-by: Paulo Gomes <pjbgf@linux.com>
Add support for GPG configuration in git config, following existing
patterns for User, Author, and Committer sections.
New configuration options:
- gpg.format: signature format ("openpgp" or "ssh")
- gpg.ssh.allowedSignersFile: path to SSH allowed signers file
This enables reading Git's gpg.ssh.allowedSignersFile setting for
SSH signature verification workflows.
Expand on sections/keys exposed by config.Config so that go-git can implement heuristics to automatically GPG/SSH sign objects. Added fields: tag.gpgSign, commit.gpgSign and user.signingKey. Signed-off-by: Paulo Gomes <pjbgf@linux.com>
Auto-signing objects now respect the config values for tag.gpgSign and commit.gpgSign, meaning that when an ObjectSigner is registered, it will only be used when either setting is enabled. Signed-off-by: Paulo Gomes <pjbgf@linux.com>
Signed-off-by: Paulo Gomes <pjbgf@linux.com>
Signed-off-by: Paulo Gomes <paulo@entire.io>
Signed-off-by: Paulo Gomes <paulo@entire.io>
Plain bool fields are indistinguishable from "not set" when their value is false, because config.Merge skips zero-valued fields. This prevented a lower-scope tag.gpgSign=false (or commit.gpgSign=false) from overriding a higher-scope true, which broke Git's "last scope wins" semantics and could force unexpected auto-signing. Replace the bool fields with OptBool, a tri-state type whose zero value (OptBoolUnset) is correctly skipped by Merge, while OptBoolFalse (1) is non-zero and propagates through the merge chain. Also fix stale error message strings in tests left over by the go fix pass, and make tests resilient to host-level commit.gpgSign settings. Signed-off-by: Paulo Gomes <paulo@entire.io> Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Paulo Gomes <paulo@entire.io>
Add a ConfigSource plugin interface with a single Load(scope) method that returns a config.ConfigStorer for the requested scope (global or system). This decouples config loading from the host filesystem, allowing implementations backed by environment variables, in-memory data, or remote sources. Key changes: - x/plugin: Define ConfigSource interface and ConfigLoader plugin key - x/plugin/config: Add Empty, Static, and readOnlyStorer implementations - repository: ConfigScoped uses the registered ConfigSource plugin, falling back to NewEmpty when none is registered - config: Deprecate LoadConfig in favour of the new Load function - tests: Register a default static ConfigSource in TestMain so all tests are isolated from host git configuration Signed-off-by: Paulo Gomes <paulo@entire.io> Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 25 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
cloneConfig: deep-copy slice fields in RemoteConfig (URLs, Fetch) and URL (InsteadOfs) so in-place mutations do not leak across copies. Guard against nil map values that would panic on dereference. Fully deep-copy Raw (format/config.Config) including sections, subsections, options, and includes instead of a shallow struct copy. ConfigScoped: load and return the local config without requiring a ConfigLoader plugin when scope is LocalScope, aligning with the ConfigSource contract that Load is never called with LocalScope. Signed-off-by: Paulo Gomes <paulo@entire.io> Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
These changes align the user experience, so that both Tags and Commits
share the same API.
Users can now define a signer at application-level, via new
pluginpackage,to auto-sign objects when
tag.gpgSign=trueorcommit.gpgSign=true, betteraligning with Git behaviour.
Signers will be implemented as an
ObjectSignerplugin out-of-tree. The go-gitproject will host some options (e.g.
ssh,gpgandauto) ingithub.com/go-git/x,but users are recommended to implement their own signers whenever their use-case
requires it.
New
pluginpackageA new
pluginpackage was introduced to provide a generic, thread-saferegistry for plugin factory functions. It enables off-tree implementations
to be registered and retrieved at runtime.
The plugin-based approach comes with two additional benefits:
needing to make changes to the core go-git codebase.
go-git users that do not require those features. This is temporarily not
true for signature verifiers, but that will change in the near future.
Existing registration mechanisms (e.g. hash.RegisterHash) will be reviewed and
potentially replaced by the new
pluginpackage.New signing API UX
Here are a few examples of what using the new API should look like.
Signing all tags and commits via
ObjectSignerpluginSigning single commit using
objectsigner/{ssh,gpg}packageSigning single tag using
objectsigner/{ssh,gpg}packageRelates to #1849.
Follow-up from #1847.
Supersedes: #1828.
SystemandGlobalconfigs are loaded from theConfigLoaderplugin as opposed to directly from disk/env - and by default both return empty configs. This provides greater control for applications that don't want to be exposed to side effects from environmental configuration. A follow-up PR will introduce an opt-in way to align with upstream Git by introducing additionalConfigLoaderimplementations.