[PATCH v3 0/2] history: support signing rewritten commits
From: Souma <hidden>
Date: 2026-09-12 16:00:58
The history commands create commits directly and via the replay
machinery, but currently have no way to honor `commit.gpgSign` or an
explicit signing request. This means users who require signed commits
lose that property when rewriting history.
Teach the replay API to accept a signing key, then expose the standard
`-S`/`--gpg-sign[=<key-id>]` and `--no-gpg-sign` interface across the
`git history drop`, `git history fixup`, `git history reword`, and `git
history split` subcommands. The selected policy applies to every new
commit, including both halves of a split and replayed descendants.
The implementation follows the precedence used by rebase, cherry-pick,
and revert: `commit.gpgSign` supplies the default, command-line options
override it, and the last command-line option wins.
The signature records the attestation of the current committer to the
rewritten commit while retaining the original author identity; it does
not claim authorship of commits written by somebody else.
Changes since v2:
- Shorten the commit messages based on review feedback
- Rename the history implementation commit from `builtin/history` to
`history`
- Fix the continuation-backslash formatting in `OPT_HISTORY_GPG_SIGN`
Souma (2):
replay: allow callers to sign commits
history: sign rewritten commits
Documentation/git-history.adoc | 16 +++++--
builtin/history.c | 84 ++++++++++++++++++++++++++--------
replay.c | 13 ++++--
replay.h | 6 +++
t/t3451-history-reword.sh | 63 +++++++++++++++++++++++++
t/t3452-history-split.sh | 44 ++++++++++++++++++
t/t3453-history-fixup.sh | 39 ++++++++++++++++
t/t3454-history-drop.sh | 50 ++++++++++++++++++++
8 files changed, 286 insertions(+), 29 deletions(-)
Range-diff against v2:
1: 3f4dc0b982 ! 1: ca35b0acaa replay: allow callers to sign commits
@@ Metadata
## Commit message ##
replay: allow callers to sign commits
- The replay machinery creates commits directly through
- `commit_tree_extended()`, but callers cannot currently request
- signatures. Commands that replay rewritten history consequently cannot
- carry their signing policy through to descendant commits.
-
- Add `sign_commit` to `replay_revisions_options` and thread it through
- commit creation. `NULL` preserves the existing unsigned behavior, an
- empty string selects the default signing key, and a non-empty string
- selects an explicit key. Existing callers zero-initialize the options
- structure, so their behavior is unchanged.
+ Add a signing-key option to replay_revisions_options and pass it to
+ commit_tree_extended() when creating replayed commits.
Signed-off-by: Souma [off-list ref]
@@ replay.c: static struct commit *pick_regular_commit(struct repository *repo,
+ enum replay_empty_commit_action empty,
+ const char *sign_commit)
{
- struct commit *base, *replayed_base;
struct tree *pickme_tree, *base_tree, *replayed_base_tree;
+
@@ replay.c: static struct commit *pick_regular_commit(struct repository *repo,
}
}
- return create_commit(repo, result->tree, pickme, replayed_base, mode);
+ return create_commit(repo, result->tree, pickme, replayed_base, mode,
-+ sign_commit);
++ sign_commit);
}
void replay_result_release(struct replay_result *result)
@@ replay.c: int replay_revisions(struct rev_info *revs,
- last_commit = pick_regular_commit(revs->repo, commit, replayed_commits,
- mode == REPLAY_MODE_REVERT ? last_commit : onto,
-- &merge_opt, &result, mode, opts->empty);
-+ &merge_opt, &result, mode, opts->empty,
-+ opts->sign_commit);
- if (!last_commit)
- break;
+ last_commit = pick_regular_commit(revs->repo, commit, base,
+ &merge_opt, &result,
+- mode, opts->empty);
++ mode, opts->empty,
++ opts->sign_commit);
+ }
+ if (!last_commit)
## replay.h ##
@@ replay.h: struct replay_revisions_options {
2: 0e63c0b66a ! 2: f0a1a88411 builtin/history: sign rewritten commits
@@ Metadata
Author: Souma [off-list ref]
## Commit message ##
- builtin/history: sign rewritten commits
+ history: sign rewritten commits
- The history commands create replacement commits directly instead of
- using the sequencer or the commit porcelain. As a result, rewritten
- commits ignore `commit.gpgSign` and cannot be signed on demand.
+ Add --gpg-sign/--no-gpg-sign support to git history and honor
+ commit.gpgSign when creating replacement commits. Thread the selected
+ signing key through direct rewrites and replayed descendants while
+ preserving the original author identity.
- Read the signing configuration before parsing options so that it
- establishes the default and later `-S`/`--gpg-sign` or `--no-gpg-sign`
- options override it. Pass the selected key through direct rewrites and
- the replay machinery.
-
- Sign every newly created commit, including both halves of a split and
- replayed descendants. Dropping the tip creates no replacement commit,
- so there is nothing to sign. As with `rebase --gpg-sign`, the signature
- records the attestation of the current committer to the rewritten
- commit while retaining the original author identity; it does not claim
- authorship of commits written by somebody else.
-
- Document the behavior and add GPG-gated coverage for configuration,
- command-line overrides, last-option-wins precedence, replayed
- descendants, split commits, an explicit signing key, and the
- no-new-commit drop case.
+ Cover configuration, command-line precedence, explicit keys, split commits,
+ and replayed descendants with GPG-gated tests.
Signed-off-by: Souma [off-list ref]
@@ builtin/history.c: enum commit_tree_flags {
+ return git_default_config(var, value, ctx, NULL);
+}
+
-+#define OPT_HISTORY_GPG_SIGN(v) { \
-+ .type = OPTION_STRING, \
-+ .short_name = 'S', \
-+ .long_name = "gpg-sign", \
-+ .value = (v), \
-+ .argh = N_("key-id"), \
++#define OPT_HISTORY_GPG_SIGN(v) { \
++ .type = OPTION_STRING, \
++ .short_name = 'S', \
++ .long_name = "gpg-sign", \
++ .value = (v), \
++ .argh = N_("key-id"), \
+ .help = N_("GPG-sign rewritten commits"), \
-+ .flags = PARSE_OPT_OPTARG, \
-+ .defval = (intptr_t)"", \
++ .flags = PARSE_OPT_OPTARG, \
++ .defval = (intptr_t)"", \
+}
+
static int commit_tree_ext(struct repository *repo,
--
2.55.0