From: Trevor Gross Date: Sun, 05 Jul 2026 14:09:06 GMT Subject: [PATCH] rebase -i: introduce `pick -x` to add "cherry picked from commit ..." Message-ID: <20260705140931.98262-2-tg@trevorgross.com> 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 --- 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 = use commit\n" -"r, reword = use commit, but edit the commit message\n" -"e, edit = use commit, but stop for amending\n" +"p, pick [ -x ] = use commit\n" +"r, reword [ -x ] = use commit, but edit the commit message\n" +"e, edit [ -x ] = use commit, but stop for amending\n" "s, squash = use commit, but meld into previous commit\n" "f, fixup [-C | -c] = 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 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 )\", 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)