Git development
 help / color / mirror / Atom feed
* [PATCH] rebase -i: introduce `pick -x` to add "cherry picked from commit ..."
@ 2026-07-05 14:09 Trevor Gross
  2026-07-05 18:58 ` Junio C Hamano
                   ` (2 more replies)
  0 siblings, 3 replies; 7+ messages in thread
From: Trevor Gross @ 2026-07-05 14:09 UTC (permalink / raw)
  To: git
  Cc: Trevor Gross, Jeff King, Junio C Hamano, Stefan Haller,
	Derrick Stolee, Phillip Wood

It is sometimes useful to do cherry picks via rebases when there is a
sequence of picks or other git operations to combine. However, there is
no interactive rebase equivalent to the cherry-pick `-x` flag, which
adds a line to the commit body indicating the original commit.

Using `exec git cherry-pick ... -x` does work, but is not as nice
because it interrupts rebase flow; after resolving a conflict, both `git
cherry-pick --continue` and `git rebase --continue` must be run.

To improve this, introduce `-x` to the pick, reword, and edit todo
rebase commands.  This uses the same logic as cherry-pick to add a
"(cherry picked from commit ...)" note to the commit body.

Of note is that rebase will fastforward wherever possible, meaning the
check for TODO_RECORD_ORIGIN doesn't get hit and the message will not
get amended. This differs from the cherry-pick logic, which will add
"cherry picked from ..." even if a rewrite isn't otherwise necessary.

Signed-off-by: Trevor Gross <tg@trevorgross.com>
---

Link to PR with the CI runs: https://github.com/git/git/pull/2194

 Documentation/git-rebase.adoc | 16 +++++++++++++
 rebase-interactive.c          |  9 +++++---
 sequencer.c                   | 19 ++++++++++++++--
 t/t3404-rebase-interactive.sh | 42 +++++++++++++++++++++++++++++++++++
 4 files changed, 81 insertions(+), 5 deletions(-)

diff --git a/Documentation/git-rebase.adoc b/Documentation/git-rebase.adoc
index f6c22d1598..d8a8e2c2d6 100644
--- a/Documentation/git-rebase.adoc
+++ b/Documentation/git-rebase.adoc
@@ -978,6 +978,22 @@ pick f4593f9 four
 exec make test
 --------------------

+Similar to `git cherry-pick`, `-x` can be specified to append a "(cherry
+picked from commit …​)" line to the commit body if the the commit base
+changes. That is, the following todo list:
+
+--------------
+pick 123456 -x
+edit 654321 -x
+--------------
+
+acts the same as:
+
+---------------------------
+$ git cherry-pick -x 123456
+$ git cherry-pick -xe 654321
+---------------------------
+
 SPLITTING COMMITS
 -----------------

diff --git a/rebase-interactive.c b/rebase-interactive.c
index 809f76a87b..6a86ab5a94 100644
--- a/rebase-interactive.c
+++ b/rebase-interactive.c
@@ -47,9 +47,9 @@ void append_todo_help(int command_count,
 		      struct strbuf *buf)
 {
 	const char *msg = _("\nCommands:\n"
-"p, pick <commit> = use commit\n"
-"r, reword <commit> = use commit, but edit the commit message\n"
-"e, edit <commit> = use commit, but stop for amending\n"
+"p, pick   [ -x ] <commit> = use commit\n"
+"r, reword [ -x ] <commit> = use commit, but edit the commit message\n"
+"e, edit   [ -x ] <commit> = use commit, but stop for amending\n"
 "s, squash <commit> = use commit, but meld into previous commit\n"
 "f, fixup [-C | -c] <commit> = like \"squash\" but keep only the previous\n"
 "                   commit's log message, unless -C is used, in which case\n"
@@ -68,6 +68,9 @@ void append_todo_help(int command_count,
 "                      to this position in the new commits. The <ref> is\n"
 "                      updated at the end of the rebase\n"
 "\n"
+"With pick, reword, or edit, -x will append a line that says \"(cherry\n"
+"picked from commit <sha>)\", similar to git-cherry-pick."
+"\n"
 "These lines can be re-ordered; they are executed from top to bottom.\n");
 	unsigned edit_todo = !(shortrevisions && shortonto);

diff --git a/sequencer.c b/sequencer.c
index 57855b0066..fde09dd77d 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1884,6 +1884,7 @@ enum todo_item_flags {
 	TODO_EDIT_MERGE_MSG    = (1 << 0),
 	TODO_REPLACE_FIXUP_MSG = (1 << 1),
 	TODO_EDIT_FIXUP_MSG    = (1 << 2),
+	TODO_RECORD_ORIGIN     = (1 << 3),
 };

 static const char first_commit_msg_str[] = N_("This is the 1st commit message:");
@@ -2390,7 +2391,7 @@ static int do_pick_commit(struct repository *r,
 		if (find_commit_subject(msg.message, &p))
 			strbuf_addstr(&ctx->message, p);

-		if (opts->record_origin) {
+		if (opts->record_origin || (item->flags & TODO_RECORD_ORIGIN)) {
 			strbuf_complete_line(&ctx->message);
 			if (!has_conforming_footer(&ctx->message, NULL, 0))
 				strbuf_addch(&ctx->message, '\n');
@@ -2758,6 +2759,14 @@ static int parse_insn_line(struct repository *r, struct replay_opts *opts,
 		return error(_("missing arguments for %s"),
 			     command_to_string(item->command));

+	if (item->command == TODO_PICK || item->command == TODO_REWORD ||
+	    item->command == TODO_EDIT) {
+		if (skip_prefix(bol, "-x", &bol)) {
+			bol += strspn(bol, " \t");
+			item->flags |= TODO_RECORD_ORIGIN;
+		}
+	}
+
 	if (item->command == TODO_EXEC || item->command == TODO_LABEL ||
 	    item->command == TODO_RESET || item->command == TODO_UPDATE_REF) {
 		int ret = 0;
@@ -5524,7 +5533,7 @@ static int single_pick(struct repository *r,
 		       struct replay_opts *opts)
 {
 	int check_todo;
-	struct todo_item item;
+	struct todo_item item = { 0 };

 	item.command = opts->action == REPLAY_PICK ?
 			TODO_PICK : TODO_REVERT;
@@ -6340,6 +6349,12 @@ static void todo_list_to_strbuf(struct repository *r,
 					  short_commit_name(r, item->commit) :
 					  oid_to_hex(&item->commit->object.oid);

+			if (item->command == TODO_PICK || item->command == TODO_EDIT ||
+			    item->command == TODO_REWORD) {
+				if (item->flags & TODO_RECORD_ORIGIN)
+					strbuf_addstr(buf, " -x");
+			}
+
 			if (item->command == TODO_FIXUP) {
 				if (item->flags & TODO_EDIT_FIXUP_MSG)
 					strbuf_addstr(buf, " -c");
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 58b3bb0c27..3ff86ebaae 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -2337,6 +2337,48 @@ test_expect_success 'non-merge commands reject merge commits' '
 	test_cmp expect actual
 '

+
+test_expect_success 'rebase -i with pick -x' '
+	git checkout A &&
+	orig_j="$(git rev-parse J)" &&
+	orig_k="$(git rev-parse K)" &&
+	orig_l="$(git rev-parse L)" &&
+	cat >fake-todo <<-EOF &&
+	# No message since this is a fastforward
+	pick -x F
+	# The rest should get the "cherry picked from " message
+	pick -x J
+	reword -x K
+	edit -x L
+	EOF
+	(
+		set_replace_editor fake-todo &&
+		git rebase -i HEAD
+	) &&
+	git log --format="---%n%s%n%b" >actual &&
+	cat >expect <<-EOF &&
+	---
+	L
+	(cherry picked from commit $orig_l)
+
+	---
+	K
+	(cherry picked from commit $orig_k)
+
+	---
+	J
+	(cherry picked from commit $orig_j)
+
+	---
+	F
+
+	---
+	A
+
+	EOF
+	test_cmp expect actual
+'
+
 # This must be the last test in this file
 test_expect_success '$EDITOR and friends are unchanged' '
 	test_editor_unchanged
--
2.50.1 (Apple Git-155)

^ permalink raw reply related	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-07-06 20:24 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-05 14:09 [PATCH] rebase -i: introduce `pick -x` to add "cherry picked from commit ..." Trevor Gross
2026-07-05 18:58 ` Junio C Hamano
2026-07-05 22:23   ` Matt Hunter
2026-07-05 20:52 ` Junio C Hamano
2026-07-06  0:24 ` Jeff King
2026-07-06 10:08   ` Phillip Wood
2026-07-06 20:24     ` Junio C Hamano

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox