All of lore.kernel.org
 help / color / mirror / Atom feed
From: Patrick Monette <pmonette@google.com>
To: git@vger.kernel.org
Cc: newren@gmail.com, ps@pks.im, toon@iotcl.com,
	 Patrick Monette <pmonette@google.com>
Subject: [PATCH 2/2] replay: add the -S option
Date: Fri, 25 Sep 2026 16:53:48 -0400	[thread overview]
Message-ID: <20260925205348.1210154-3-pmonette@google.com> (raw)
In-Reply-To: <20260925205348.1210154-1-pmonette@google.com>

`git replay` currently doesn't support signing. In fact, there is a
FIXME to address this.

Add the -S option and its related options --gpg-sign and --no-gpg-sign.

Signed-off-by: Patrick Monette <pmonette@google.com>
---
 Documentation/git-replay.adoc |  10 +++-
 builtin/replay.c              |  12 +++-
 replay.c                      |  12 ++--
 replay.h                      |   6 ++
 t/meson.build                 |   1 +
 t/t3651-replay-gpg-sign.sh    | 107 ++++++++++++++++++++++++++++++++++
 6 files changed, 141 insertions(+), 7 deletions(-)
 create mode 100755 t/t3651-replay-gpg-sign.sh

diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc
index 58b4c0c470..2e5caba0be 100644
--- a/Documentation/git-replay.adoc
+++ b/Documentation/git-replay.adoc
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 (EXPERIMENTAL!) 'git replay' ([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)
-			     [--ref=<ref>] [--ref-action=<mode>] [--linearize] <revision-range>
+			     [--ref=<ref>] [--ref-action=<mode>] [--linearize] [-S[<keyid>]] <revision-range>
 
 DESCRIPTION
 -----------
@@ -106,6 +106,14 @@ behavior of git-rebase(1)'s `--no-rebase-merges` option.)
 `--contained`. To linearize several branches, replay them in separate
 `git replay` invocations.
 
+-S[<keyid>]::
+--gpg-sign[=<keyid>]::
+--no-gpg-sign::
+	GPG-sign commits. The `keyid` argument is optional and
+	defaults to the committer identity; if specified, it must be
+	stuck to the option without a space. `--no-gpg-sign` is useful to
+	countermand a `--gpg-sign` option given earlier on the command line.
+
 <revision-range>::
 	Range of commits to replay; see "Specifying Ranges" in
 	linkgit:git-rev-parse[1]. In `--advance=<branch>` or
diff --git a/builtin/replay.c b/builtin/replay.c
index d39626a37d..87c628e2ed 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -85,7 +85,7 @@ int cmd_replay(int argc,
 	const char *const replay_usage[] = {
 		N_("(EXPERIMENTAL!) git replay "
 		   "([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)\n"
-		   "[--ref=<ref>] [--ref-action=<mode>] [--linearize] <revision-range>"),
+		   "[--ref=<ref>] [--ref-action=<mode>] [--linearize] [-S[<keyid>]] <revision-range>"),
 		NULL
 	};
 	struct option replay_options[] = {
@@ -113,6 +113,16 @@ int cmd_replay(int argc,
 			     PARSE_OPT_NONEG),
 		OPT_BOOL(0, "linearize", &opts.linearize,
 			 N_("drop merge commits, replaying only non-merge commits")),
+		{
+			.type = OPTION_STRING,
+			.short_name = 'S',
+			.long_name = "gpg-sign",
+			.value = &opts.sign_commit,
+			.argh = N_("key-id"),
+			.help = N_("GPG-sign commits"),
+			.flags = PARSE_OPT_OPTARG,
+			.defval = (intptr_t) "",
+		},
 		OPT_END()
 	};
 
diff --git a/replay.c b/replay.c
index ad87863565..9a84e297b1 100644
--- a/replay.c
+++ b/replay.c
@@ -85,13 +85,13 @@ static struct commit *create_commit(struct repository *repo,
 				    struct tree *tree,
 				    struct commit *based_on,
 				    struct commit *parent,
-				    enum replay_mode mode)
+				    enum replay_mode mode,
+				    const char *sign_commit)
 {
 	struct object_id ret;
 	struct object *obj = NULL;
 	struct commit_list *parents = NULL;
 	char *author = NULL;
-	char *sign_commit = NULL; /* FIXME: cli users might want to sign again */
 	struct commit_extra_header *extra = NULL;
 	struct strbuf msg = STRBUF_INIT;
 	const char *out_enc = get_commit_output_encoding();
@@ -288,7 +288,8 @@ static struct commit *pick_regular_commit(struct repository *repo,
 					  struct merge_options *merge_opt,
 					  struct merge_result *result,
 					  enum replay_mode mode,
-					  enum replay_empty_commit_action empty)
+					  enum replay_empty_commit_action empty,
+					  const char *sign_commit)
 {
 	struct tree *pickme_tree, *base_tree, *replayed_base_tree;
 	struct commit *new_commit;
@@ -363,7 +364,7 @@ static struct commit *pick_regular_commit(struct repository *repo,
 	}
 
 	new_commit = create_commit(repo, result->tree, pickme, replayed_base,
-				   mode);
+				   mode, sign_commit);
 	if (!new_commit)
 		result->clean = -1;
 	return new_commit;
@@ -486,7 +487,8 @@ int replay_revisions(struct rev_info *revs,
 
 			last_commit = pick_regular_commit(revs->repo, commit, base,
 							  &merge_opt, &result,
-							  mode, opts->empty);
+							  mode, opts->empty,
+							  opts->sign_commit);
 		}
 
 		if (!last_commit)
diff --git a/replay.h b/replay.h
index 2c71afbfde..7e93ab9565 100644
--- a/replay.h
+++ b/replay.h
@@ -67,6 +67,12 @@ struct replay_revisions_options {
 	 * Whether to linearize the commits (i.e. drop merge commits).
 	 */
 	int linearize;
+
+	/*
+	 * If non-NULL, GPG-sign the new commits. An empty string signs with
+	 * the default key (the committer identity); otherwise, the key ID.
+	 */
+	const char *sign_commit;
 };
 
 /* This struct is used as an out-parameter by `replay_revisions()`. */
diff --git a/t/meson.build b/t/meson.build
index 3ca7b27104..93c01e26a2 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -426,6 +426,7 @@ integration_tests = [
   't3601-rm-pathspec-file.sh',
   't3602-rm-sparse-checkout.sh',
   't3650-replay-basics.sh',
+  't3651-replay-gpg-sign.sh',
   't3700-add.sh',
   't3701-add-interactive.sh',
   't3702-add-edit.sh',
diff --git a/t/t3651-replay-gpg-sign.sh b/t/t3651-replay-gpg-sign.sh
new file mode 100755
index 0000000000..61a3375b52
--- /dev/null
+++ b/t/t3651-replay-gpg-sign.sh
@@ -0,0 +1,107 @@
+#!/bin/sh
+
+test_description='git replay --[no-]gpg-sign'
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-gpg.sh"
+
+if ! test_have_prereq GPG
+then
+	skip_all='skip all git replay --[no-]gpg-sign tests, gpg not available'
+	test_done
+fi
+
+# Checks that "topic" was replayed onto "main", and that the replayed
+# commits are all signed ("signed") or all unsigned ("unsigned").
+check_replayed () {
+	git merge-base --is-ancestor main topic &&
+	git rev-list main..topic >replayed &&
+	test_line_count = 2 replayed &&
+	for commit in $(cat replayed)
+	do
+		case "$1" in
+		signed)
+			git verify-commit $commit || return 1
+			;;
+		unsigned)
+			test_must_fail git verify-commit $commit || return 1
+			;;
+		esac
+	done
+}
+
+test_expect_success 'setup' '
+	test_commit A &&
+	test_commit B &&
+	git switch -c topic A &&
+	echo C >C &&
+	git add C &&
+	git commit -S -m C &&
+	git tag C &&
+	echo D >D &&
+	git add D &&
+	git commit -S -m D &&
+	git tag D &&
+	git switch main
+'
+
+test_expect_success 'replay without --gpg-sign does not sign' '
+	git branch -f topic D &&
+	git verify-commit C &&
+	git verify-commit D &&
+	git replay --onto main A..topic &&
+	check_replayed unsigned
+'
+
+test_expect_success 'replay --gpg-sign signs with the default key' '
+	git branch -f topic D &&
+	git replay --gpg-sign --onto main A..topic &&
+	check_replayed signed &&
+	echo "C O Mitter <committer@example.com>" >expect &&
+	git log -1 --format="%GS" topic >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'replay -S<keyid> signs with the given key' '
+	git branch -f topic D &&
+	git replay -SB7227189 --onto main A..topic &&
+	git rev-list main..topic >replayed &&
+	test_line_count = 2 replayed &&
+	echo D4BE22311AD3131E5EDA29A461092E85B7227189 >expect &&
+	for commit in $(cat replayed)
+	do
+		git log -1 --format="%GP" $commit >actual &&
+		test_cmp expect actual || return 1
+	done
+'
+
+test_expect_success 'replay --no-gpg-sign countermands --gpg-sign' '
+	git branch -f topic D &&
+	git replay --gpg-sign --no-gpg-sign --onto main A..topic &&
+	check_replayed unsigned
+'
+
+test_expect_success 'replay ignores commit.gpgSign' '
+	git branch -f topic D &&
+	git -c commit.gpgSign=true replay --onto main A..topic &&
+	check_replayed unsigned
+'
+
+test_expect_success 'replay fails and updates no ref when signing fails' '
+	git branch -f topic D &&
+	test_must_fail git replay -Snonexistent-key --onto main A..topic &&
+	test_cmp_rev D topic
+'
+
+test_expect_success 'replay --ref fails and updates no ref when signing fails' '
+	git branch -f topic D &&
+	test_must_fail git replay -Snonexistent-key --onto main \
+		--ref refs/heads/other A..topic &&
+	test_must_fail git rev-parse --verify refs/heads/other &&
+	test_cmp_rev D topic
+'
+
+test_done
-- 
2.56.0.rc1.315.gc6ed9934b7-goog


  parent reply	other threads:[~2026-09-25 20:53 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-25 20:53 [PATCH 0/2] replay: add signing support Patrick Monette
2026-09-25 20:53 ` [PATCH 1/2] replay: handle failure to create commits Patrick Monette
2026-09-28  7:41   ` Patrick Steinhardt
2026-09-25 20:53 ` Patrick Monette [this message]
2026-09-28  7:41   ` [PATCH 2/2] replay: add the -S option Patrick Steinhardt
2026-10-02 21:23   ` Junio C Hamano
2026-09-28  7:41 ` [PATCH 0/2] replay: add signing support Patrick Steinhardt
2026-09-28 15:23   ` Junio C Hamano

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260925205348.1210154-3-pmonette@google.com \
    --to=pmonette@google.com \
    --cc=git@vger.kernel.org \
    --cc=newren@gmail.com \
    --cc=ps@pks.im \
    --cc=toon@iotcl.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.