From: Souma Date: Sat, 12 Sep 2026 16:00:43 GMT Subject: [PATCH v3 0/2] history: support signing rewritten commits Message-ID: <20260912160045.36064-1-git@5ouma.me> In-Reply-To: <20260703145037.69832-1-git@5ouma.me> 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[=]` 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 @@ 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 ## 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 @@ 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