Thread (14 messages) flat view 14 messages, 2 authors, 5d ago
COOLING5d

Revision v3 of 2 in this series.

Revisions (2)
  1. v2 [diff vs current]
  2. v3 current

[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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help