* [PATCH 1/3] t3650: use option with value consistenly with equal sign
2026-03-23 16:09 [PATCH 0/3] Add option --ref to git-replay(1) Toon Claes
@ 2026-03-23 16:09 ` Toon Claes
2026-03-23 19:17 ` Kristoffer Haugsbakk
2026-03-23 16:09 ` [PATCH 2/3] builtin/replay: improve documentation on options Toon Claes
` (2 subsequent siblings)
3 siblings, 1 reply; 18+ messages in thread
From: Toon Claes @ 2026-03-23 16:09 UTC (permalink / raw)
To: git; +Cc: Justin Tobler, Siddharth Asthana, Yee Cheng Chin, Toon Claes
The tests in t3650-replay-basics have mixed use of option arguments
with value with and without equal sign. Bring in consistency and use
equal sign for all options that expect a value. This makes it easier to
distinguish them from positional arguments.
Signed-off-by: Toon Claes <toon@iotcl.com>
---
t/t3650-replay-basics.sh | 70 ++++++++++++++++++++++++------------------------
1 file changed, 35 insertions(+), 35 deletions(-)
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index 0c1e03e0fb..47811b3197 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -95,8 +95,8 @@ test_expect_success '--contained requires --onto' '
'
test_expect_success 'cannot advance target ... ordering would be ill-defined' '
- cat >expect <<-\EOF &&
- fatal: '"'"'--advance'"'"' cannot be used with multiple revision ranges because the ordering would be ill-defined
+ cat >expect <<-EOF &&
+ fatal: ${SQ}--advance${SQ} cannot be used with multiple revision ranges because the ordering would be ill-defined
EOF
test_must_fail git replay --advance=main main topic1 topic2 2>actual &&
test_cmp expect actual
@@ -109,7 +109,7 @@ test_expect_success 'replaying merge commits is not supported yet' '
'
test_expect_success 'using replay to rebase two branches, one on top of other' '
- git replay --ref-action=print --onto main topic1..topic2 >result &&
+ git replay --ref-action=print --onto=main topic1..topic2 >result &&
test_line_count = 1 result &&
@@ -125,16 +125,16 @@ test_expect_success 'using replay to rebase two branches, one on top of other' '
'
test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' '
- git -C bare replay --ref-action=print --onto main topic1..topic2 >result-bare &&
+ git -C bare replay --ref-action=print --onto=main topic1..topic2 >result-bare &&
test_cmp expect result-bare
'
test_expect_success 'using replay to rebase with a conflict' '
- test_expect_code 1 git replay --onto topic1 B..conflict
+ test_expect_code 1 git replay --onto=topic1 B..conflict
'
test_expect_success 'using replay on bare repo to rebase with a conflict' '
- test_expect_code 1 git -C bare replay --onto topic1 B..conflict
+ test_expect_code 1 git -C bare replay --onto=topic1 B..conflict
'
test_expect_success 'using replay to perform basic cherry-pick' '
@@ -143,7 +143,7 @@ test_expect_success 'using replay to perform basic cherry-pick' '
# 2nd field of result is refs/heads/main vs. refs/heads/topic2
# 4th field of result is hash for main instead of hash for topic2
- git replay --ref-action=print --advance main topic1..topic2 >result &&
+ git replay --ref-action=print --advance=main topic1..topic2 >result &&
test_line_count = 1 result &&
@@ -159,7 +159,7 @@ test_expect_success 'using replay to perform basic cherry-pick' '
'
test_expect_success 'using replay on bare repo to perform basic cherry-pick' '
- git -C bare replay --ref-action=print --advance main topic1..topic2 >result-bare &&
+ git -C bare replay --ref-action=print --advance=main topic1..topic2 >result-bare &&
test_cmp expect result-bare
'
@@ -170,11 +170,11 @@ test_expect_success 'commits that become empty are dropped' '
test_when_finished "git update-ref --stdin <original-branches &&
rm original-branches" &&
# Cherry-pick tip of topic1 ("F"), from the middle of A..empty, to main
- git replay --advance main topic1^! &&
+ git replay --advance=main topic1^! &&
# Replay all of A..empty onto main (which includes topic1 & thus F
# in the middle)
- git replay --onto main --branches --ancestry-path=empty ^A \
+ git replay --onto=main --branches --ancestry-path=empty ^A \
>result &&
git log --format="%s%d" L..empty >actual &&
test_write_lines >expect \
@@ -183,7 +183,7 @@ test_expect_success 'commits that become empty are dropped' '
'
test_expect_success 'replay on bare repo fails with both --advance and --onto' '
- test_must_fail git -C bare replay --advance main --onto main topic1..topic2 >result-bare
+ test_must_fail git -C bare replay --advance=main --onto=main topic1..topic2 >result-bare
'
test_expect_success 'replay fails when both --advance and --onto are omitted' '
@@ -191,7 +191,7 @@ test_expect_success 'replay fails when both --advance and --onto are omitted' '
'
test_expect_success 'using replay to also rebase a contained branch' '
- git replay --ref-action=print --contained --onto main main..topic3 >result &&
+ git replay --ref-action=print --contained --onto=main main..topic3 >result &&
test_line_count = 2 result &&
cut -f 3 -d " " result >new-branch-tips &&
@@ -215,12 +215,12 @@ test_expect_success 'using replay to also rebase a contained branch' '
'
test_expect_success 'using replay on bare repo to also rebase a contained branch' '
- git -C bare replay --ref-action=print --contained --onto main main..topic3 >result-bare &&
+ git -C bare replay --ref-action=print --contained --onto=main main..topic3 >result-bare &&
test_cmp expect result-bare
'
test_expect_success 'using replay to rebase multiple divergent branches' '
- git replay --ref-action=print --onto main ^topic1 topic2 topic4 >result &&
+ git replay --ref-action=print --onto=main ^topic1 topic2 topic4 >result &&
test_line_count = 2 result &&
cut -f 3 -d " " result >new-branch-tips &&
@@ -244,7 +244,7 @@ test_expect_success 'using replay to rebase multiple divergent branches' '
'
test_expect_success 'using replay on bare repo to rebase multiple divergent branches, including contained ones' '
- git -C bare replay --ref-action=print --contained --onto main ^main topic2 topic3 topic4 >result &&
+ git -C bare replay --ref-action=print --contained --onto=main ^main topic2 topic3 topic4 >result &&
test_line_count = 4 result &&
cut -f 3 -d " " result >new-branch-tips &&
@@ -276,7 +276,7 @@ test_expect_success 'using replay to update detached HEAD' '
test_when_finished git switch "$current_head" &&
git switch --detach &&
test_commit something &&
- git replay --ref-action=print --onto HEAD~2 --ref-action=print HEAD~..HEAD >updates &&
+ git replay --ref-action=print --onto=HEAD~2 --ref-action=print HEAD~..HEAD >updates &&
test_grep "update HEAD " updates
'
@@ -299,7 +299,7 @@ test_expect_success 'merge.directoryRenames=false' '
git commit -m modified to-rename/add-a-file.t &&
git -c merge.directoryRenames=false replay \
- --onto rename-onto rename-onto..rename-from
+ --onto=rename-onto rename-onto..rename-from
'
test_expect_success 'default atomic behavior updates refs directly' '
@@ -308,7 +308,7 @@ test_expect_success 'default atomic behavior updates refs directly' '
test_when_finished "git branch -D test-atomic" &&
# Test default atomic behavior (no output, refs updated)
- git replay --onto main topic1..test-atomic >output &&
+ git replay --onto=main topic1..test-atomic >output &&
test_must_be_empty output &&
# Verify ref was updated
@@ -329,7 +329,7 @@ test_expect_success 'atomic behavior in bare repository' '
test_when_finished "git -C bare update-ref refs/heads/topic2 $START" &&
# Test atomic updates work in bare repo
- git -C bare replay --onto main topic1..topic2 >output &&
+ git -C bare replay --onto=main topic1..topic2 >output &&
test_must_be_empty output &&
# Verify ref was updated in bare repo
@@ -344,7 +344,7 @@ test_expect_success 'reflog message for --advance mode' '
test_when_finished "git update-ref refs/heads/main $START" &&
# Test --advance mode reflog message
- git replay --advance main topic1..topic2 >output &&
+ git replay --advance=main topic1..topic2 >output &&
test_must_be_empty output &&
# Verify reflog message includes --advance and branch name
@@ -360,7 +360,7 @@ test_expect_success 'replay.refAction=print config option' '
# Test with config set to print
test_config replay.refAction print &&
- git replay --onto main topic1..topic2 >output &&
+ git replay --onto=main topic1..topic2 >output &&
test_line_count = 1 output &&
test_grep "^update refs/heads/topic2 " output
'
@@ -372,7 +372,7 @@ test_expect_success 'replay.refAction=update config option' '
# Test with config set to update
test_config replay.refAction update &&
- git replay --onto main topic1..topic2 >output &&
+ git replay --onto=main topic1..topic2 >output &&
test_must_be_empty output &&
# Verify ref was updated
@@ -388,14 +388,14 @@ test_expect_success 'command-line --ref-action overrides config' '
# Set config to update but use --ref-action=print
test_config replay.refAction update &&
- git replay --ref-action=print --onto main topic1..topic2 >output &&
+ git replay --ref-action=print --onto=main topic1..topic2 >output &&
test_line_count = 1 output &&
test_grep "^update refs/heads/topic2 " output
'
test_expect_success 'invalid replay.refAction value' '
test_config replay.refAction invalid &&
- test_must_fail git replay --onto main topic1..topic2 2>error &&
+ test_must_fail git replay --onto=main topic1..topic2 2>error &&
test_grep "invalid.*replay.refAction.*value" error
'
@@ -407,10 +407,10 @@ test_expect_success 'argument to --revert must be a reference' '
'
test_expect_success 'cannot revert with multiple sources' '
- cat >expect <<-\EOF &&
- fatal: '"'"'--revert'"'"' cannot be used with multiple revision ranges because the ordering would be ill-defined
+ cat >expect <<-EOF &&
+ fatal: ${SQ}--revert${SQ} cannot be used with multiple revision ranges because the ordering would be ill-defined
EOF
- test_must_fail git replay --revert main main topic1 topic2 2>actual &&
+ test_must_fail git replay --revert=main main topic1 topic2 2>actual &&
test_cmp expect actual
'
@@ -420,7 +420,7 @@ test_expect_success 'using replay --revert to revert commits' '
test_when_finished "git branch -f topic4 $START" &&
# Revert commits I and J
- git replay --revert topic4 topic4~2..topic4 &&
+ git replay --revert=topic4 topic4~2..topic4 &&
# Verify the revert commits were created (newest-first ordering
# means J is reverted first, then I on top)
@@ -452,7 +452,7 @@ test_expect_success 'using replay --revert in bare repo' '
test_when_finished "git -C bare update-ref refs/heads/topic4 $START" &&
# Revert commit J in bare repo
- git -C bare replay --revert topic4 topic4~1..topic4 &&
+ git -C bare replay --revert=topic4 topic4~1..topic4 &&
# Verify revert was created
git -C bare log -1 --format=%s topic4 >actual &&
@@ -466,11 +466,11 @@ test_expect_success 'revert of revert uses Reapply' '
test_when_finished "git branch -f topic4 $START" &&
# First revert J
- git replay --revert topic4 topic4~1..topic4 &&
+ git replay --revert=topic4 topic4~1..topic4 &&
REVERT_J=$(git rev-parse topic4) &&
# Now revert the revert - should become Reapply
- git replay --revert topic4 topic4~1..topic4 &&
+ git replay --revert=topic4 topic4~1..topic4 &&
# Verify Reapply prefix and message format
test_commit_message topic4 <<-EOF
@@ -482,21 +482,21 @@ test_expect_success 'revert of revert uses Reapply' '
test_expect_success 'git replay --revert with conflict' '
# conflict branch has C.conflict which conflicts with topic1s C
- test_expect_code 1 git replay --revert conflict B..topic1
+ test_expect_code 1 git replay --revert=conflict B..topic1
'
test_expect_success 'git replay --revert incompatible with --contained' '
- test_must_fail git replay --revert topic4 --contained topic4~1..topic4 2>error &&
+ test_must_fail git replay --revert=topic4 --contained topic4~1..topic4 2>error &&
test_grep "requires --onto" error
'
test_expect_success 'git replay --revert incompatible with --onto' '
- test_must_fail git replay --revert topic4 --onto main topic4~1..topic4 2>error &&
+ test_must_fail git replay --revert=topic4 --onto=main topic4~1..topic4 2>error &&
test_grep "cannot be used together" error
'
test_expect_success 'git replay --revert incompatible with --advance' '
- test_must_fail git replay --revert topic4 --advance main topic4~1..topic4 2>error &&
+ test_must_fail git replay --revert=topic4 --advance=main topic4~1..topic4 2>error &&
test_grep "cannot be used together" error
'
--
2.53.0.310.g728cabbaf7
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH 1/3] t3650: use option with value consistenly with equal sign
2026-03-23 16:09 ` [PATCH 1/3] t3650: use option with value consistenly with equal sign Toon Claes
@ 2026-03-23 19:17 ` Kristoffer Haugsbakk
2026-03-23 20:06 ` Junio C Hamano
0 siblings, 1 reply; 18+ messages in thread
From: Kristoffer Haugsbakk @ 2026-03-23 19:17 UTC (permalink / raw)
To: Toon Claes, git; +Cc: Justin Tobler, Siddharth Asthana, Yee Cheng Chin
On Mon, Mar 23, 2026, at 17:09, Toon Claes wrote:
> The tests in t3650-replay-basics have mixed use of option arguments
> with value with and without equal sign. Bring in consistency and use
> equal sign for all options that expect a value.
If it is about consistency, could you pick one or the other either way
or go with whatever happened to be most used right now?
Consistency by itself is a weaker argument than arguing that stuck form
is better for invoking git(1) commands, which is what gitcli(7) argues.
Which is to say: arguing for stuck form in the commit message based on
it being better is a stronger argument than wanting consistency. :)
Then once one form has been argued for or referenced it follows that you
should be consistent and use the best approach throughout.
> This makes it easier to distinguish them from positional arguments.
Maybe it’s just me, but sticking with the stuck form makes it harder to
mess up writing unintended options and positional arguments. Once
written it might be slightly more readable, but the main benefit is
using a style that makes messing up harder to pull off.
>
> Signed-off-by: Toon Claes <toon@iotcl.com>
> ---
> t/t3650-replay-basics.sh | 70 ++++++++++++++++++++++++------------------------
> 1 file changed, 35 insertions(+), 35 deletions(-)
>[snip]
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/3] t3650: use option with value consistenly with equal sign
2026-03-23 19:17 ` Kristoffer Haugsbakk
@ 2026-03-23 20:06 ` Junio C Hamano
2026-03-25 12:43 ` Toon Claes
0 siblings, 1 reply; 18+ messages in thread
From: Junio C Hamano @ 2026-03-23 20:06 UTC (permalink / raw)
To: Kristoffer Haugsbakk
Cc: Toon Claes, git, Justin Tobler, Siddharth Asthana, Yee Cheng Chin
"Kristoffer Haugsbakk" <kristofferhaugsbakk@fastmail.com> writes:
> On Mon, Mar 23, 2026, at 17:09, Toon Claes wrote:
>> The tests in t3650-replay-basics have mixed use of option arguments
>> with value with and without equal sign. Bring in consistency and use
>> equal sign for all options that expect a value.
>
> If it is about consistency, could you pick one or the other either way
> or go with whatever happened to be most used right now?
>
> Consistency by itself is a weaker argument than arguing that stuck form
> is better for invoking git(1) commands, which is what gitcli(7) argues.
>
> Which is to say: arguing for stuck form in the commit message based on
> it being better is a stronger argument than wanting consistency. :)
>
> Then once one form has been argued for or referenced it follows that you
> should be consistent and use the best approach throughout.
>
>> This makes it easier to distinguish them from positional arguments.
>
> Maybe it’s just me, but sticking with the stuck form makes it harder to
> mess up writing unintended options and positional arguments. Once
> written it might be slightly more readable, but the main benefit is
> using a style that makes messing up harder to pull off.
I am not sure if a patch whose purpose is only to make the CLI
invocation "consistent" is a welcome change, though. If we support
two forms, exercising both forms and making sure they mean the same
thing may even be better, but short of that, a random mixture of
styles as if end-user human may have picked one form on this day and
the other form on another day may be better than a complete
monoculture.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/3] t3650: use option with value consistenly with equal sign
2026-03-23 20:06 ` Junio C Hamano
@ 2026-03-25 12:43 ` Toon Claes
0 siblings, 0 replies; 18+ messages in thread
From: Toon Claes @ 2026-03-25 12:43 UTC (permalink / raw)
To: Junio C Hamano, Kristoffer Haugsbakk
Cc: git, Justin Tobler, Siddharth Asthana, Yee Cheng Chin
Junio C Hamano <gitster@pobox.com> writes:
> I am not sure if a patch whose purpose is only to make the CLI
> invocation "consistent" is a welcome change, though. If we support
> two forms, exercising both forms and making sure they mean the same
> thing may even be better, but short of that, a random mixture of
> styles as if end-user human may have picked one form on this day and
> the other form on another day may be better than a complete
> monoculture.
Okay, I'll drop this commit.
--
Cheers,
Toon
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 2/3] builtin/replay: improve documentation on options
2026-03-23 16:09 [PATCH 0/3] Add option --ref to git-replay(1) Toon Claes
2026-03-23 16:09 ` [PATCH 1/3] t3650: use option with value consistenly with equal sign Toon Claes
@ 2026-03-23 16:09 ` Toon Claes
2026-03-23 16:09 ` [PATCH 3/3] replay: allow to specify a ref with option --ref Toon Claes
2026-03-25 15:59 ` [PATCH v2 0/3] Add option --ref to git-replay(1) Toon Claes
3 siblings, 0 replies; 18+ messages in thread
From: Toon Claes @ 2026-03-23 16:09 UTC (permalink / raw)
To: git; +Cc: Justin Tobler, Siddharth Asthana, Yee Cheng Chin, Toon Claes
Most of the options for the command git-replay(1) expect a value
argument. Make that more clear in the docs, by using an equal sign
between the option and the value.
Also, none of the options are negatable, thus mark them as such using
PARSE_OPT_NONEG.
Signed-off-by: Toon Claes <toon@iotcl.com>
---
Documentation/git-replay.adoc | 3 ++-
builtin/replay.c | 32 ++++++++++++++++++--------------
2 files changed, 20 insertions(+), 15 deletions(-)
diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc
index 6698cfc047..e4fef548e0 100644
--- a/Documentation/git-replay.adoc
+++ b/Documentation/git-replay.adoc
@@ -9,7 +9,8 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t
SYNOPSIS
--------
[verse]
-(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch> | --revert <branch>) [--ref-action[=<mode>]] <revision-range>...
+(EXPERIMENTAL!) 'git replay' ([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)
+ [--ref-action=<mode>] <revision-range>
DESCRIPTION
-----------
diff --git a/builtin/replay.c b/builtin/replay.c
index fe69f6f8ce..2e9fd6281d 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -83,25 +83,29 @@ int cmd_replay(int argc,
const char *const replay_usage[] = {
N_("(EXPERIMENTAL!) git replay "
- "([--contained] --onto <newbase> | --advance <branch> | --revert <branch>) "
- "[--ref-action[=<mode>]] <revision-range>..."),
+ "([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)\n"
+ "[--ref-action=<mode>] <revision-range>"),
NULL
};
struct option replay_options[] = {
- OPT_STRING(0, "advance", &opts.advance,
- N_("branch"),
- N_("make replay advance given branch")),
- OPT_STRING(0, "onto", &opts.onto,
- N_("revision"),
- N_("replay onto given commit")),
+ OPT_STRING_F(0, "onto", &opts.onto,
+ N_("revision"),
+ N_("replay onto given commit"),
+ PARSE_OPT_NONEG),
+ OPT_STRING_F(0, "advance", &opts.advance,
+ N_("branch"),
+ N_("make replay advance given branch"),
+ PARSE_OPT_NONEG),
+ OPT_STRING_F(0, "revert", &opts.revert,
+ N_("branch"),
+ N_("revert commits onto given branch"),
+ PARSE_OPT_NONEG),
OPT_BOOL(0, "contained", &opts.contained,
N_("update all branches that point at commits in <revision-range>")),
- OPT_STRING(0, "revert", &opts.revert,
- N_("branch"),
- N_("revert commits onto given branch")),
- OPT_STRING(0, "ref-action", &ref_action,
- N_("mode"),
- N_("control ref update behavior (update|print)")),
+ OPT_STRING_F(0, "ref-action", &ref_action,
+ N_("mode"),
+ N_("control ref update behavior (update|print)"),
+ PARSE_OPT_NONEG),
OPT_END()
};
--
2.53.0.310.g728cabbaf7
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH 3/3] replay: allow to specify a ref with option --ref
2026-03-23 16:09 [PATCH 0/3] Add option --ref to git-replay(1) Toon Claes
2026-03-23 16:09 ` [PATCH 1/3] t3650: use option with value consistenly with equal sign Toon Claes
2026-03-23 16:09 ` [PATCH 2/3] builtin/replay: improve documentation on options Toon Claes
@ 2026-03-23 16:09 ` Toon Claes
2026-03-23 18:01 ` Tian Yuchen
2026-03-23 19:07 ` Kristoffer Haugsbakk
2026-03-25 15:59 ` [PATCH v2 0/3] Add option --ref to git-replay(1) Toon Claes
3 siblings, 2 replies; 18+ messages in thread
From: Toon Claes @ 2026-03-23 16:09 UTC (permalink / raw)
To: git; +Cc: Justin Tobler, Siddharth Asthana, Yee Cheng Chin, Toon Claes
To enable users to replay commits without having refs in
<revision-range> or as the value for --advance and --revert, start
accepting option --ref. When the user specifies a --ref, the refs from
the other option are ignored, and only the ref is acted on.
Signed-off-by: Toon Claes <toon@iotcl.com>
---
Documentation/git-replay.adoc | 21 ++++++++++++++-
builtin/replay.c | 9 ++++++-
replay.c | 29 +++++++++++++++-----
replay.h | 7 +++++
t/t3650-replay-basics.sh | 61 +++++++++++++++++++++++++++++++++++++++++++
5 files changed, 118 insertions(+), 9 deletions(-)
diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc
index e4fef548e0..f90d93ae31 100644
--- a/Documentation/git-replay.adoc
+++ b/Documentation/git-replay.adoc
@@ -10,7 +10,8 @@ SYNOPSIS
--------
[verse]
(EXPERIMENTAL!) 'git replay' ([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)
- [--ref-action=<mode>] <revision-range>
+ [--ref=<branch>] [--ref-action=<mode>]
+ <revision-range>
DESCRIPTION
-----------
@@ -62,6 +63,14 @@ the current user as the author, matching the behavior of `git revert`.
This option is mutually exclusive with `--onto` and `--advance`. It is also
incompatible with `--contained` (which is a modifier for `--onto` only).
+--ref <branch>::
+ Override which reference is updated with the result of the
+ replay. When used with `--onto`, only the specified reference
+ is updated instead of inferring refs from the revision range.
+ When used with `--advance` or `--revert`, the specified
+ reference is updated instead of the branch given to those
+ options. This option is incompatible with `--contained`.
+
--contained::
Update all branches that point at commits in
<revision-range>. Requires `--onto`.
@@ -184,6 +193,16 @@ top of `main`, and updates `main` to point at the result. This is useful when
commits from `topic` were previously merged or cherry-picked into `main` and
need to be undone.
+To replay onto a specific commit while updating a different reference:
+
+------------
+$ git replay --onto 112233 --ref refs/heads/mybranch aabbcc..ddeeff
+------------
+
+This replays the range `aabbcc..ddeeff` onto commit `112233` and updates
+`refs/heads/mybranch` to point at the result. This is useful when you want
+to use bare commit IDs instead of branch names.
+
NOTE: For reverting an entire merge request as a single commit (rather than
commit-by-commit), consider using `git merge-tree --merge-base $TIP HEAD $BASE`
which can avoid unnecessary merge conflicts.
diff --git a/builtin/replay.c b/builtin/replay.c
index 2e9fd6281d..f4fc18d16f 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -84,7 +84,8 @@ int cmd_replay(int argc,
const char *const replay_usage[] = {
N_("(EXPERIMENTAL!) git replay "
"([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)\n"
- "[--ref-action=<mode>] <revision-range>"),
+ "[--ref=<branch>] [--ref-action=<mode>]\n"
+ "<revision-range>"),
NULL
};
struct option replay_options[] = {
@@ -102,6 +103,10 @@ int cmd_replay(int argc,
PARSE_OPT_NONEG),
OPT_BOOL(0, "contained", &opts.contained,
N_("update all branches that point at commits in <revision-range>")),
+ OPT_STRING_F(0, "ref", &opts.ref,
+ N_("branch"),
+ N_("reference to update with result"),
+ PARSE_OPT_NONEG),
OPT_STRING_F(0, "ref-action", &ref_action,
N_("mode"),
N_("control ref update behavior (update|print)"),
@@ -121,6 +126,8 @@ int cmd_replay(int argc,
die_for_incompatible_opt3(!!opts.onto, "--onto",
!!opts.advance, "--advance",
!!opts.revert, "--revert");
+ die_for_incompatible_opt2(!!opts.ref, "--ref",
+ !!opts.contained, "--contained");
if (opts.contained && !opts.onto)
die(_("--contained requires --onto"));
diff --git a/replay.c b/replay.c
index 199066f6b3..07b94cddde 100644
--- a/replay.c
+++ b/replay.c
@@ -348,6 +348,8 @@ int replay_revisions(struct rev_info *revs,
bool detached_head;
char *advance;
char *revert;
+ const char *ref;
+ struct object_id old_oid;
enum replay_mode mode = REPLAY_MODE_PICK;
int ret;
@@ -358,6 +360,23 @@ int replay_revisions(struct rev_info *revs,
set_up_replay_mode(revs->repo, &revs->cmdline, opts->onto,
&detached_head, &advance, &revert, &onto, &update_refs);
+ if (opts->ref) {
+ struct object_id oid;
+
+ if (check_refname_format(opts->ref, 0) || !starts_with(opts->ref, "refs/")) {
+ ret = error(_("'%s' is not a valid refname"), opts->ref);
+ goto out;
+ }
+ ref = opts->ref;
+ if (!refs_read_ref(get_main_ref_store(revs->repo), opts->ref, &oid))
+ oidcpy(&old_oid, &oid);
+ else
+ oidclr(&old_oid, revs->repo->hash_algo);
+ } else {
+ ref = advance ? advance : revert;
+ oidcpy(&old_oid, &onto->object.oid);
+ }
+
/* FIXME: Should allow replaying commits with the first as a root commit */
if (prepare_revision_walk(revs) < 0) {
@@ -393,7 +412,7 @@ int replay_revisions(struct rev_info *revs,
kh_value(replayed_commits, pos) = last_commit;
/* Update any necessary branches */
- if (advance || revert)
+ if (ref)
continue;
for (decoration = get_name_decoration(&commit->object);
@@ -427,13 +446,9 @@ int replay_revisions(struct rev_info *revs,
goto out;
}
- /* In --advance or --revert mode, update the target ref */
- if (advance || revert) {
- const char *ref = advance ? advance : revert;
- replay_result_queue_update(out, ref,
- &onto->object.oid,
+ if (ref)
+ replay_result_queue_update(out, ref, &old_oid,
&last_commit->object.oid);
- }
ret = 0;
diff --git a/replay.h b/replay.h
index e916a5f975..99933f2268 100644
--- a/replay.h
+++ b/replay.h
@@ -24,6 +24,13 @@ struct replay_revisions_options {
*/
const char *onto;
+ /*
+ * Reference to update with the result of the replay. This stops replay
+ * from taking refs from `onto`, `advance`, and `revert`. Ignores
+ * `contained`.
+ */
+ const char *ref;
+
/*
* Starting point at which to create revert commits; must be a branch
* name. The branch will be updated to point to the revert commits.
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index 47811b3197..b7465b5f18 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -500,4 +500,65 @@ test_expect_success 'git replay --revert incompatible with --advance' '
test_grep "cannot be used together" error
'
+test_expect_success 'using --onto with --ref' '
+ git branch test-ref-onto topic2 &&
+ test_when_finished "git branch -D test-ref-onto" &&
+
+ git replay --ref-action=print --onto=main --ref=refs/heads/test-ref-onto topic1..topic2 >result &&
+
+ test_line_count = 1 result &&
+ test_grep "^update refs/heads/test-ref-onto " result &&
+
+ git log --format=%s $(cut -f 3 -d " " result) >actual &&
+ test_write_lines E D M L B A >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'using --advance with --ref' '
+ git branch test-ref-advance main &&
+ git branch test-ref-target main &&
+ test_when_finished "git branch -D test-ref-advance test-ref-target" &&
+
+ git replay --ref-action=print --advance=test-ref-advance --ref=refs/heads/test-ref-target topic1..topic2 >result &&
+
+ test_line_count = 1 result &&
+ test_grep "^update refs/heads/test-ref-target " result
+'
+
+test_expect_success 'using --revert with --ref' '
+ git branch test-ref-revert topic4 &&
+ git branch test-ref-revert-target topic4 &&
+ test_when_finished "git branch -D test-ref-revert test-ref-revert-target" &&
+
+ git replay --ref-action=print --revert=test-ref-revert --ref=refs/heads/test-ref-revert-target topic4~1..topic4 >result &&
+
+ test_line_count = 1 result &&
+ test_grep "^update refs/heads/test-ref-revert-target " result
+'
+
+test_expect_success '--ref is incompatible with --contained' '
+ test_must_fail git replay --onto=main --ref=refs/heads/main --contained topic1..topic2 2>err &&
+ test_grep "cannot be used together" err
+'
+
+test_expect_success '--ref with nonexistent fully-qualified ref' '
+ test_when_finished "git update-ref -d refs/heads/new-branch" &&
+
+ git replay --onto=main --ref=refs/heads/new-branch topic1..topic2 &&
+
+ git log --format=%s -2 new-branch >actual &&
+ test_write_lines E D >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '--ref must be a valid refname' '
+ test_must_fail git replay --onto=main --ref="refs/heads/bad..ref" topic1..topic2 2>err &&
+ test_grep "is not a valid refname" err
+'
+
+test_expect_success '--ref requires fully qualified ref' '
+ test_must_fail git replay --onto=main --ref=main topic1..topic2 2>err &&
+ test_grep "is not a valid refname" err
+'
+
test_done
--
2.53.0.310.g728cabbaf7
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH 3/3] replay: allow to specify a ref with option --ref
2026-03-23 16:09 ` [PATCH 3/3] replay: allow to specify a ref with option --ref Toon Claes
@ 2026-03-23 18:01 ` Tian Yuchen
2026-03-25 12:50 ` Toon Claes
2026-03-23 19:07 ` Kristoffer Haugsbakk
1 sibling, 1 reply; 18+ messages in thread
From: Tian Yuchen @ 2026-03-23 18:01 UTC (permalink / raw)
To: Toon Claes, git; +Cc: Justin Tobler, Siddharth Asthana, Yee Cheng Chin
Hi Toon,
On 3/24/26 00:09, Toon Claes wrote:
> + die_for_incompatible_opt2(!!opts.ref, "--ref",
> + !!opts.contained, "--contained");
> + /*
> + * Reference to update with the result of the replay. This stops replay
> + * from taking refs from `onto`, `advance`, and `revert`. Ignores
> + * `contained`.
> + */
> + const char *ref;
> +
Do the comment and the code implementation match up here?
> @@ -427,13 +446,9 @@ int replay_revisions(struct rev_info *revs,
> goto out;
> }
>
> - /* In --advance or --revert mode, update the target ref */
> - if (advance || revert) {
> - const char *ref = advance ? advance : revert;
> - replay_result_queue_update(out, ref,
> - &onto->object.oid,
> + if (ref)
> + replay_result_queue_update(out, ref, &old_oid,
> &last_commit->object.oid);
> - }
>
> ret = 0;
It doesn't seem like there's anything here to prevent users from
inputting multiple branches? I think this will result in at least one
branch becoming orphaned, without any error being reported.
Consider this command:
git replay --onto main --ref refs/heads/target topic1 topic2
In replay.c we have:
> if (onto_name) {
> *onto = peel_committish(repo, onto_name, "--onto");
> if (rinfo.positive_refexprs <
> strset_get_size(&rinfo.positive_refs))
> die(_("all positive revisions given must be references"));
> *update_refs = xcalloc(1, sizeof(**update_refs));
> **update_refs = rinfo.positive_refs;
> memset(&rinfo.positive_refs, 0, sizeof(**update_refs));
After topic1 and topic2 are put into update_refs, only one ref
(refs/heads/target) is updated, since what is written in the patch is:
> + if (ref)
> + replay_result_queue_update(out, ref, &old_oid,
> &last_commit->object.oid);
I believe this is not what we expect :P
Regards, Yuchen
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH 3/3] replay: allow to specify a ref with option --ref
2026-03-23 18:01 ` Tian Yuchen
@ 2026-03-25 12:50 ` Toon Claes
0 siblings, 0 replies; 18+ messages in thread
From: Toon Claes @ 2026-03-25 12:50 UTC (permalink / raw)
To: Tian Yuchen, git; +Cc: Justin Tobler, Siddharth Asthana, Yee Cheng Chin
Tian Yuchen <cat@malon.dev> writes:
> Hi Toon,
>
> On 3/24/26 00:09, Toon Claes wrote:
>
>
>> + die_for_incompatible_opt2(!!opts.ref, "--ref",
>> + !!opts.contained, "--contained");
>
>> + /*
>> + * Reference to update with the result of the replay. This stops replay
>> + * from taking refs from `onto`, `advance`, and `revert`. Ignores
>> + * `contained`.
>> + */
>> + const char *ref;
>> +
>
> Do the comment and the code implementation match up here?
The comment is a bit misleading. A user cannot set the field `contained`
in the struct when it passed `--ref`. But if a bug is made somewhere,
but both fields can be set, that's what the comment is saying.
I can add a check to verify in replay.c that not both are set, and
update the comment they are incompatible?
>> @@ -427,13 +446,9 @@ int replay_revisions(struct rev_info *revs,
>> goto out;
>> }
>>
>> - /* In --advance or --revert mode, update the target ref */
>> - if (advance || revert) {
>> - const char *ref = advance ? advance : revert;
>> - replay_result_queue_update(out, ref,
>> - &onto->object.oid,
>> + if (ref)
>> + replay_result_queue_update(out, ref, &old_oid,
>> &last_commit->object.oid);
>> - }
>>
>> ret = 0;
>
> It doesn't seem like there's anything here to prevent users from
> inputting multiple branches? I think this will result in at least one
> branch becoming orphaned, without any error being reported.
>
> Consider this command:
>
> git replay --onto main --ref refs/heads/target topic1 topic2
That's interesting, I didn't think of that.
>
> In replay.c we have:
>
>> if (onto_name) {
>> *onto = peel_committish(repo, onto_name, "--onto");
>> if (rinfo.positive_refexprs <
>> strset_get_size(&rinfo.positive_refs))
>> die(_("all positive revisions given must be references"));
>> *update_refs = xcalloc(1, sizeof(**update_refs));
>> **update_refs = rinfo.positive_refs;
>> memset(&rinfo.positive_refs, 0, sizeof(**update_refs));
>
>
> After topic1 and topic2 are put into update_refs, only one ref
> (refs/heads/target) is updated, since what is written in the patch is:
>
> > + if (ref)
> > + replay_result_queue_update(out, ref, &old_oid,
> > &last_commit->object.oid);
>
> I believe this is not what we expect :P
Let me look into that how we can deal with that.
--
Cheers,
Toon
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 3/3] replay: allow to specify a ref with option --ref
2026-03-23 16:09 ` [PATCH 3/3] replay: allow to specify a ref with option --ref Toon Claes
2026-03-23 18:01 ` Tian Yuchen
@ 2026-03-23 19:07 ` Kristoffer Haugsbakk
2026-03-25 12:49 ` Toon Claes
1 sibling, 1 reply; 18+ messages in thread
From: Kristoffer Haugsbakk @ 2026-03-23 19:07 UTC (permalink / raw)
To: Toon Claes, git; +Cc: Justin Tobler, Siddharth Asthana, Yee Cheng Chin
On Mon, Mar 23, 2026, at 17:09, Toon Claes wrote:
> To enable users to replay commits without having refs in
> <revision-range> or as the value for --advance and --revert, start
> accepting option --ref. When the user specifies a --ref, the refs from
> the other option are ignored, and only the ref is acted on.
s/option are/options are/
>
> Signed-off-by: Toon Claes <toon@iotcl.com>
> ---
>[snip]
> +To replay onto a specific commit while updating a different reference:
> +
> +------------
> +$ git replay --onto 112233 --ref refs/heads/mybranch aabbcc..ddeeff
> +------------
This example could use the stuck form like in the two previous commits.
git replay --onto=112233 --ref=refs/heads/mybranch ...
> +
> +This replays the range `aabbcc..ddeeff` onto commit `112233` and updates
> +`refs/heads/mybranch` to point at the result. This is useful when you want
> +to use bare commit IDs instead of branch names.
> +
> NOTE: For reverting an entire merge request as a single commit (rather
> than
>[snip]
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH 3/3] replay: allow to specify a ref with option --ref
2026-03-23 19:07 ` Kristoffer Haugsbakk
@ 2026-03-25 12:49 ` Toon Claes
0 siblings, 0 replies; 18+ messages in thread
From: Toon Claes @ 2026-03-25 12:49 UTC (permalink / raw)
To: Kristoffer Haugsbakk, git
Cc: Justin Tobler, Siddharth Asthana, Yee Cheng Chin
"Kristoffer Haugsbakk" <kristofferhaugsbakk@fastmail.com> writes:
> On Mon, Mar 23, 2026, at 17:09, Toon Claes wrote:
>> To enable users to replay commits without having refs in
>> <revision-range> or as the value for --advance and --revert, start
>> accepting option --ref. When the user specifies a --ref, the refs from
>> the other option are ignored, and only the ref is acted on.
>
> s/option are/options are/
Thanks!
>> Signed-off-by: Toon Claes <toon@iotcl.com>
>> ---
>>[snip]
>> +To replay onto a specific commit while updating a different reference:
>> +
>> +------------
>> +$ git replay --onto 112233 --ref refs/heads/mybranch aabbcc..ddeeff
>> +------------
>
> This example could use the stuck form like in the two previous commits.
>
> git replay --onto=112233 --ref=refs/heads/mybranch ...
Good point.
>> +
>> +This replays the range `aabbcc..ddeeff` onto commit `112233` and updates
>> +`refs/heads/mybranch` to point at the result. This is useful when you want
>> +to use bare commit IDs instead of branch names.
>> +
>> NOTE: For reverting an entire merge request as a single commit (rather
>> than
>>[snip]
--
Cheers,
Toon
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v2 0/3] Add option --ref to git-replay(1)
2026-03-23 16:09 [PATCH 0/3] Add option --ref to git-replay(1) Toon Claes
` (2 preceding siblings ...)
2026-03-23 16:09 ` [PATCH 3/3] replay: allow to specify a ref with option --ref Toon Claes
@ 2026-03-25 15:59 ` Toon Claes
2026-03-25 15:59 ` [PATCH v2 1/3] builtin/replay: mark options as not negatable Toon Claes
` (4 more replies)
3 siblings, 5 replies; 18+ messages in thread
From: Toon Claes @ 2026-03-25 15:59 UTC (permalink / raw)
To: git; +Cc: Justin Tobler, Siddharth Asthana, Yee Cheng Chin, Toon Claes
In a previous RFC[1] I suggested to implement subcommands into
git-replay(1). While it would be arguable nice to have subcommands for
the different modes, because git-replay(1) is a plumbing commands, it's
fine to keep a status quo.
This series takes one thing for that RFC though: adding option --ref.
This new option is useful if you want to have full control over which
ref is being updated, and not want to rely on the refs that are using
in the <revision-range> or as the value for --advance and --revert.
These changes answer the needs expressed in[2].
This series is based on Siddharth's series[3] to add '--revert' to
git-replay(1) (sa/replay-revert @ ba5c0d03d3).
[1]: https://lore.kernel.org/git/20260309-toon-replay-subcommands-v1-1-864ec82ef68a@iotcl.com/
[2]: https://lore.kernel.org/git/CAHTeOx-SMLh_idKhGczPKzZNOKy04uYXmUhL8Z79yRuNpmE4eA@mail.gmail.com/
[3]: https://lore.kernel.org/git/20260313054035.26605-1-siddharthasthana31@gmail.com/
---
Changes in v2:
- Dropped the test-only consistency patch.
- Separated commit to mark options as not negatable.
- Modified git-replay(1) docs to everywhere use stuck form.
- Added code and test ensure the revision range has a single tip when
both --onto and --ref are given.
- Rephrased some comments and docs.
- Link to v1: https://patch.msgid.link/20260323-toon-replay-arbitrary-ref-v1-0-5c7172f675ec@iotcl.com
---
Toon Claes (3):
builtin/replay: mark options as not negatable
replay: use stuck form in documentation and help message
replay: allow to specify a ref with option --ref
Documentation/git-replay.adoc | 42 +++++++++++++++++++--------
builtin/replay.c | 38 ++++++++++++++++---------
replay.c | 33 +++++++++++++++++-----
replay.h | 7 +++++
t/t3650-replay-basics.sh | 66 +++++++++++++++++++++++++++++++++++++++++++
5 files changed, 154 insertions(+), 32 deletions(-)
Range-diff versus v1:
1: fe4daa4c89 < -: ---------- t3650: use option with value consistenly with equal sign
2: 3170bcc916 < -: ---------- builtin/replay: improve documentation on options
-: ---------- > 1: 61ba35cc08 builtin/replay: mark options as not negatable
-: ---------- > 2: 19e54b8326 replay: use stuck form in documentation and help message
3: bdb166a9ff ! 3: 341b94f937 replay: allow to specify a ref with option --ref
@@ Metadata
## Commit message ##
replay: allow to specify a ref with option --ref
- To enable users to replay commits without having refs in
- <revision-range> or as the value for --advance and --revert, start
- accepting option --ref. When the user specifies a --ref, the refs from
- the other option are ignored, and only the ref is acted on.
+ When option '--onto' is passed to git-replay(1), the command will update
+ refs from the <revision-range> passed to the command. When using option
+ '--advance' or '--revert', the argument of that option is a ref that
+ will be updated.
+
+ To enable users to specify which ref to update, add option '--ref'. When
+ using option '--ref', the refs described above are left untouched and
+ instead the argument of this option is updated instead.
Signed-off-by: Toon Claes <toon@iotcl.com>
@@ Documentation/git-replay.adoc: SYNOPSIS
[verse]
(EXPERIMENTAL!) 'git replay' ([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)
- [--ref-action=<mode>] <revision-range>
-+ [--ref=<branch>] [--ref-action=<mode>]
-+ <revision-range>
++ [--ref=<branch>] [--ref-action=<mode>] <revision-range>
DESCRIPTION
-----------
-@@ Documentation/git-replay.adoc: the current user as the author, matching the behavior of `git revert`.
- This option is mutually exclusive with `--onto` and `--advance`. It is also
- incompatible with `--contained` (which is a modifier for `--onto` only).
-
-+--ref <branch>::
-+ Override which reference is updated with the result of the
-+ replay. When used with `--onto`, only the specified reference
-+ is updated instead of inferring refs from the revision range.
-+ When used with `--advance` or `--revert`, the specified
-+ reference is updated instead of the branch given to those
-+ options. This option is incompatible with `--contained`.
-+
- --contained::
+@@ Documentation/git-replay.adoc: incompatible with `--contained` (which is a modifier for `--onto` only).
Update all branches that point at commits in
<revision-range>. Requires `--onto`.
-@@ Documentation/git-replay.adoc: top of `main`, and updates `main` to point at the result. This is useful when
- commits from `topic` were previously merged or cherry-picked into `main` and
- need to be undone.
+
++--ref=<branch>::
++ Override which reference is updated with the result of the replay.
++ When used with `--onto`, the `<revision-range>` should have a
++ single tip and only the specified reference is updated instead of
++ inferring refs from the revision range.
++ When used with `--advance` or `--revert`, the specified reference is
++ updated instead of the branch given to those options. This option is
++ incompatible with `--contained`.
++
+ --ref-action[=<mode>]::
+ Control how references are updated. The mode can be:
+ +
+@@ Documentation/git-replay.adoc: NOTE: For reverting an entire merge request as a single commit (rather than
+ commit-by-commit), consider using `git merge-tree --merge-base $TIP HEAD $BASE`
+ which can avoid unnecessary merge conflicts.
+To replay onto a specific commit while updating a different reference:
+
+------------
-+$ git replay --onto 112233 --ref refs/heads/mybranch aabbcc..ddeeff
++$ git replay --onto=112233 --ref=refs/heads/mybranch aabbcc..ddeeff
+------------
+
+This replays the range `aabbcc..ddeeff` onto commit `112233` and updates
-+`refs/heads/mybranch` to point at the result. This is useful when you want
++`refs/heads/mybranch` to point at the result. This can be useful when you want
+to use bare commit IDs instead of branch names.
+
- NOTE: For reverting an entire merge request as a single commit (rather than
- commit-by-commit), consider using `git merge-tree --merge-base $TIP HEAD $BASE`
- which can avoid unnecessary merge conflicts.
+ GIT
+ ---
+ Part of the linkgit:git[1] suite
## builtin/replay.c ##
@@ builtin/replay.c: int cmd_replay(int argc,
@@ builtin/replay.c: int cmd_replay(int argc,
N_("(EXPERIMENTAL!) git replay "
"([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)\n"
- "[--ref-action=<mode>] <revision-range>"),
-+ "[--ref=<branch>] [--ref-action=<mode>]\n"
-+ "<revision-range>"),
++ "[--ref=<branch>] [--ref-action=<mode>] <revision-range>"),
NULL
};
struct option replay_options[] = {
@@ builtin/replay.c: int cmd_replay(int argc,
+ N_("branch"),
+ N_("revert commits onto given branch"),
PARSE_OPT_NONEG),
- OPT_BOOL(0, "contained", &opts.contained,
- N_("update all branches that point at commits in <revision-range>")),
+ OPT_STRING_F(0, "ref", &opts.ref,
+ N_("branch"),
+ N_("reference to update with result"),
@@ replay.c: int replay_revisions(struct rev_info *revs,
+ if (opts->ref) {
+ struct object_id oid;
+
++ if (update_refs && strset_get_size(update_refs) > 1) {
++ ret = error(_("'--ref' cannot be used with multiple revision ranges"));
++ goto out;
++ }
+ if (check_refname_format(opts->ref, 0) || !starts_with(opts->ref, "refs/")) {
+ ret = error(_("'%s' is not a valid refname"), opts->ref);
+ goto out;
@@ replay.h: struct replay_revisions_options {
const char *onto;
+ /*
-+ * Reference to update with the result of the replay. This stops replay
-+ * from taking refs from `onto`, `advance`, and `revert`. Ignores
++ * Reference to update with the result of the replay. This will not
++ * update any refs from `onto`, `advance`, or `revert`. Ignores
+ * `contained`.
+ */
+ const char *ref;
@@ t/t3650-replay-basics.sh: test_expect_success 'git replay --revert incompatible
+ test_must_fail git replay --onto=main --ref=main topic1..topic2 2>err &&
+ test_grep "is not a valid refname" err
+'
++
++test_expect_success '--onto with --ref rejects multiple revision ranges' '
++ test_must_fail git replay --onto=main --ref=refs/heads/topic2 ^topic1 topic2 topic4 2>err &&
++ test_grep "cannot be used with multiple revision ranges" err
++'
+
test_done
---
base-commit: ba5c0d03d39a8b1798c8f988bdc95f9a08f10fac
change-id: 20260323-toon-replay-arbitrary-ref-5a81f5f976c7
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH v2 1/3] builtin/replay: mark options as not negatable
2026-03-25 15:59 ` [PATCH v2 0/3] Add option --ref to git-replay(1) Toon Claes
@ 2026-03-25 15:59 ` Toon Claes
2026-03-25 15:59 ` [PATCH v2 2/3] replay: use stuck form in documentation and help message Toon Claes
` (3 subsequent siblings)
4 siblings, 0 replies; 18+ messages in thread
From: Toon Claes @ 2026-03-25 15:59 UTC (permalink / raw)
To: git; +Cc: Justin Tobler, Siddharth Asthana, Yee Cheng Chin, Toon Claes
The options '--onto', '--advance', '--revert', and '--ref-action' of
git-replay(1) are not negatable. Mark them as such using
PARSE_OPT_NONEG.
Signed-off-by: Toon Claes <toon@iotcl.com>
---
builtin/replay.c | 28 ++++++++++++++++------------
1 file changed, 16 insertions(+), 12 deletions(-)
diff --git a/builtin/replay.c b/builtin/replay.c
index fe69f6f8ce..1a04f33390 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -88,20 +88,24 @@ int cmd_replay(int argc,
NULL
};
struct option replay_options[] = {
- OPT_STRING(0, "advance", &opts.advance,
- N_("branch"),
- N_("make replay advance given branch")),
- OPT_STRING(0, "onto", &opts.onto,
- N_("revision"),
- N_("replay onto given commit")),
OPT_BOOL(0, "contained", &opts.contained,
N_("update all branches that point at commits in <revision-range>")),
- OPT_STRING(0, "revert", &opts.revert,
- N_("branch"),
- N_("revert commits onto given branch")),
- OPT_STRING(0, "ref-action", &ref_action,
- N_("mode"),
- N_("control ref update behavior (update|print)")),
+ OPT_STRING_F(0, "onto", &opts.onto,
+ N_("revision"),
+ N_("replay onto given commit"),
+ PARSE_OPT_NONEG),
+ OPT_STRING_F(0, "advance", &opts.advance,
+ N_("branch"),
+ N_("make replay advance given branch"),
+ PARSE_OPT_NONEG),
+ OPT_STRING_F(0, "revert", &opts.revert,
+ N_("branch"),
+ N_("revert commits onto given branch"),
+ PARSE_OPT_NONEG),
+ OPT_STRING_F(0, "ref-action", &ref_action,
+ N_("mode"),
+ N_("control ref update behavior (update|print)"),
+ PARSE_OPT_NONEG),
OPT_END()
};
--
2.53.0.310.g728cabbaf7
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH v2 2/3] replay: use stuck form in documentation and help message
2026-03-25 15:59 ` [PATCH v2 0/3] Add option --ref to git-replay(1) Toon Claes
2026-03-25 15:59 ` [PATCH v2 1/3] builtin/replay: mark options as not negatable Toon Claes
@ 2026-03-25 15:59 ` Toon Claes
2026-03-25 15:59 ` [PATCH v2 3/3] replay: allow to specify a ref with option --ref Toon Claes
` (2 subsequent siblings)
4 siblings, 0 replies; 18+ messages in thread
From: Toon Claes @ 2026-03-25 15:59 UTC (permalink / raw)
To: git; +Cc: Justin Tobler, Siddharth Asthana, Yee Cheng Chin, Toon Claes
gitcli(7) suggests to use stuck form. Change the documentation strings
to use this form.
While at it, reorder them to match the order in the docs.
Signed-off-by: Toon Claes <toon@iotcl.com>
---
Documentation/git-replay.adoc | 23 ++++++++++++-----------
builtin/replay.c | 4 ++--
2 files changed, 14 insertions(+), 13 deletions(-)
diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc
index 6698cfc047..7e749a0477 100644
--- a/Documentation/git-replay.adoc
+++ b/Documentation/git-replay.adoc
@@ -9,7 +9,8 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t
SYNOPSIS
--------
[verse]
-(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch> | --revert <branch>) [--ref-action[=<mode>]] <revision-range>...
+(EXPERIMENTAL!) 'git replay' ([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)
+ [--ref-action=<mode>] <revision-range>
DESCRIPTION
-----------
@@ -26,7 +27,7 @@ THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
OPTIONS
-------
---onto <newbase>::
+--onto=<newbase>::
Starting point at which to create the new commits. May be any
valid commit, and not just an existing branch name.
+
@@ -34,7 +35,7 @@ When `--onto` is specified, the branch(es) in the revision range will be
updated to point at the new commits, similar to the way `git rebase --update-refs`
updates multiple branches in the affected range.
---advance <branch>::
+--advance=<branch>::
Starting point at which to create the new commits; must be a
branch name.
+
@@ -42,7 +43,7 @@ The history is replayed on top of the <branch> and <branch> is updated to
point at the tip of the resulting history. This is different from `--onto`,
which uses the target only as a starting point without updating it.
---revert <branch>::
+--revert=<branch>::
Starting point at which to create the reverted commits; must be a
branch name.
+
@@ -79,7 +80,7 @@ The default mode can be configured via the `replay.refAction` configuration vari
<revision-range>::
Range of commits to replay; see "Specifying Ranges" in
- linkgit:git-rev-parse[1]. In `--advance <branch>` mode, the
+ linkgit:git-rev-parse[1]. In `--advance=<branch>` mode, the
range should have a single tip, so that it's clear to which tip the
advanced <branch> should point. Any commits in the range whose
changes are already present in the branch the commits are being
@@ -126,7 +127,7 @@ EXAMPLES
To simply rebase `mybranch` onto `target`:
------------
-$ git replay --onto target origin/main..mybranch
+$ git replay --onto=target origin/main..mybranch
------------
The refs are updated atomically and no output is produced on success.
@@ -134,14 +135,14 @@ The refs are updated atomically and no output is produced on success.
To see what would be updated without actually updating:
------------
-$ git replay --ref-action=print --onto target origin/main..mybranch
+$ git replay --ref-action=print --onto=target origin/main..mybranch
update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH}
------------
To cherry-pick the commits from mybranch onto target:
------------
-$ git replay --advance target origin/main..mybranch
+$ git replay --advance=target origin/main..mybranch
------------
Note that the first two examples replay the exact same commits and on
@@ -153,7 +154,7 @@ What if you have a stack of branches, one depending upon another, and
you'd really like to rebase the whole set?
------------
-$ git replay --contained --onto origin/main origin/main..tipbranch
+$ git replay --contained --onto=origin/main origin/main..tipbranch
------------
All three branches (`branch1`, `branch2`, and `tipbranch`) are updated
@@ -164,7 +165,7 @@ commits to replay using the syntax `A..B`; any range expression will
do:
------------
-$ git replay --onto origin/main ^base branch1 branch2 branch3
+$ git replay --onto=origin/main ^base branch1 branch2 branch3
------------
This will simultaneously rebase `branch1`, `branch2`, and `branch3`,
@@ -175,7 +176,7 @@ that they have in common, but that does not need to be the case.
To revert commits on a branch:
------------
-$ git replay --revert main topic~2..topic
+$ git replay --revert=main topic~2..topic
------------
This reverts the last two commits from `topic`, creating revert commits on
diff --git a/builtin/replay.c b/builtin/replay.c
index 1a04f33390..a5f81b67d4 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -83,8 +83,8 @@ int cmd_replay(int argc,
const char *const replay_usage[] = {
N_("(EXPERIMENTAL!) git replay "
- "([--contained] --onto <newbase> | --advance <branch> | --revert <branch>) "
- "[--ref-action[=<mode>]] <revision-range>..."),
+ "([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)\n"
+ "[--ref-action=<mode>] <revision-range>"),
NULL
};
struct option replay_options[] = {
--
2.53.0.310.g728cabbaf7
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH v2 3/3] replay: allow to specify a ref with option --ref
2026-03-25 15:59 ` [PATCH v2 0/3] Add option --ref to git-replay(1) Toon Claes
2026-03-25 15:59 ` [PATCH v2 1/3] builtin/replay: mark options as not negatable Toon Claes
2026-03-25 15:59 ` [PATCH v2 2/3] replay: use stuck form in documentation and help message Toon Claes
@ 2026-03-25 15:59 ` Toon Claes
2026-03-25 18:44 ` Junio C Hamano
2026-03-25 18:34 ` [PATCH v2 0/3] Add option --ref to git-replay(1) Junio C Hamano
2026-03-26 21:20 ` Junio C Hamano
4 siblings, 1 reply; 18+ messages in thread
From: Toon Claes @ 2026-03-25 15:59 UTC (permalink / raw)
To: git; +Cc: Justin Tobler, Siddharth Asthana, Yee Cheng Chin, Toon Claes
When option '--onto' is passed to git-replay(1), the command will update
refs from the <revision-range> passed to the command. When using option
'--advance' or '--revert', the argument of that option is a ref that
will be updated.
To enable users to specify which ref to update, add option '--ref'. When
using option '--ref', the refs described above are left untouched and
instead the argument of this option is updated instead.
Signed-off-by: Toon Claes <toon@iotcl.com>
---
Documentation/git-replay.adoc | 21 +++++++++++++-
builtin/replay.c | 8 +++++-
replay.c | 33 +++++++++++++++++-----
replay.h | 7 +++++
t/t3650-replay-basics.sh | 66 +++++++++++++++++++++++++++++++++++++++++++
5 files changed, 126 insertions(+), 9 deletions(-)
diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc
index 7e749a0477..5952ecb50d 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-action=<mode>] <revision-range>
+ [--ref=<branch>] [--ref-action=<mode>] <revision-range>
DESCRIPTION
-----------
@@ -66,6 +66,15 @@ incompatible with `--contained` (which is a modifier for `--onto` only).
Update all branches that point at commits in
<revision-range>. Requires `--onto`.
+--ref=<branch>::
+ Override which reference is updated with the result of the replay.
+ When used with `--onto`, the `<revision-range>` should have a
+ single tip and only the specified reference is updated instead of
+ inferring refs from the revision range.
+ When used with `--advance` or `--revert`, the specified reference is
+ updated instead of the branch given to those options. This option is
+ incompatible with `--contained`.
+
--ref-action[=<mode>]::
Control how references are updated. The mode can be:
+
@@ -188,6 +197,16 @@ NOTE: For reverting an entire merge request as a single commit (rather than
commit-by-commit), consider using `git merge-tree --merge-base $TIP HEAD $BASE`
which can avoid unnecessary merge conflicts.
+To replay onto a specific commit while updating a different reference:
+
+------------
+$ git replay --onto=112233 --ref=refs/heads/mybranch aabbcc..ddeeff
+------------
+
+This replays the range `aabbcc..ddeeff` onto commit `112233` and updates
+`refs/heads/mybranch` to point at the result. This can be useful when you want
+to use bare commit IDs instead of branch names.
+
GIT
---
Part of the linkgit:git[1] suite
diff --git a/builtin/replay.c b/builtin/replay.c
index a5f81b67d4..876026549e 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -84,7 +84,7 @@ int cmd_replay(int argc,
const char *const replay_usage[] = {
N_("(EXPERIMENTAL!) git replay "
"([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)\n"
- "[--ref-action=<mode>] <revision-range>"),
+ "[--ref=<branch>] [--ref-action=<mode>] <revision-range>"),
NULL
};
struct option replay_options[] = {
@@ -102,6 +102,10 @@ int cmd_replay(int argc,
N_("branch"),
N_("revert commits onto given branch"),
PARSE_OPT_NONEG),
+ OPT_STRING_F(0, "ref", &opts.ref,
+ N_("branch"),
+ N_("reference to update with result"),
+ PARSE_OPT_NONEG),
OPT_STRING_F(0, "ref-action", &ref_action,
N_("mode"),
N_("control ref update behavior (update|print)"),
@@ -121,6 +125,8 @@ int cmd_replay(int argc,
die_for_incompatible_opt3(!!opts.onto, "--onto",
!!opts.advance, "--advance",
!!opts.revert, "--revert");
+ die_for_incompatible_opt2(!!opts.ref, "--ref",
+ !!opts.contained, "--contained");
if (opts.contained && !opts.onto)
die(_("--contained requires --onto"));
diff --git a/replay.c b/replay.c
index 199066f6b3..63cec56d48 100644
--- a/replay.c
+++ b/replay.c
@@ -348,6 +348,8 @@ int replay_revisions(struct rev_info *revs,
bool detached_head;
char *advance;
char *revert;
+ const char *ref;
+ struct object_id old_oid;
enum replay_mode mode = REPLAY_MODE_PICK;
int ret;
@@ -358,6 +360,27 @@ int replay_revisions(struct rev_info *revs,
set_up_replay_mode(revs->repo, &revs->cmdline, opts->onto,
&detached_head, &advance, &revert, &onto, &update_refs);
+ if (opts->ref) {
+ struct object_id oid;
+
+ if (update_refs && strset_get_size(update_refs) > 1) {
+ ret = error(_("'--ref' cannot be used with multiple revision ranges"));
+ goto out;
+ }
+ if (check_refname_format(opts->ref, 0) || !starts_with(opts->ref, "refs/")) {
+ ret = error(_("'%s' is not a valid refname"), opts->ref);
+ goto out;
+ }
+ ref = opts->ref;
+ if (!refs_read_ref(get_main_ref_store(revs->repo), opts->ref, &oid))
+ oidcpy(&old_oid, &oid);
+ else
+ oidclr(&old_oid, revs->repo->hash_algo);
+ } else {
+ ref = advance ? advance : revert;
+ oidcpy(&old_oid, &onto->object.oid);
+ }
+
/* FIXME: Should allow replaying commits with the first as a root commit */
if (prepare_revision_walk(revs) < 0) {
@@ -393,7 +416,7 @@ int replay_revisions(struct rev_info *revs,
kh_value(replayed_commits, pos) = last_commit;
/* Update any necessary branches */
- if (advance || revert)
+ if (ref)
continue;
for (decoration = get_name_decoration(&commit->object);
@@ -427,13 +450,9 @@ int replay_revisions(struct rev_info *revs,
goto out;
}
- /* In --advance or --revert mode, update the target ref */
- if (advance || revert) {
- const char *ref = advance ? advance : revert;
- replay_result_queue_update(out, ref,
- &onto->object.oid,
+ if (ref)
+ replay_result_queue_update(out, ref, &old_oid,
&last_commit->object.oid);
- }
ret = 0;
diff --git a/replay.h b/replay.h
index e916a5f975..0ab74b9805 100644
--- a/replay.h
+++ b/replay.h
@@ -24,6 +24,13 @@ struct replay_revisions_options {
*/
const char *onto;
+ /*
+ * Reference to update with the result of the replay. This will not
+ * update any refs from `onto`, `advance`, or `revert`. Ignores
+ * `contained`.
+ */
+ const char *ref;
+
/*
* Starting point at which to create revert commits; must be a branch
* name. The branch will be updated to point to the revert commits.
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index 0c1e03e0fb..938be64770 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -500,4 +500,70 @@ test_expect_success 'git replay --revert incompatible with --advance' '
test_grep "cannot be used together" error
'
+test_expect_success 'using --onto with --ref' '
+ git branch test-ref-onto topic2 &&
+ test_when_finished "git branch -D test-ref-onto" &&
+
+ git replay --ref-action=print --onto=main --ref=refs/heads/test-ref-onto topic1..topic2 >result &&
+
+ test_line_count = 1 result &&
+ test_grep "^update refs/heads/test-ref-onto " result &&
+
+ git log --format=%s $(cut -f 3 -d " " result) >actual &&
+ test_write_lines E D M L B A >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'using --advance with --ref' '
+ git branch test-ref-advance main &&
+ git branch test-ref-target main &&
+ test_when_finished "git branch -D test-ref-advance test-ref-target" &&
+
+ git replay --ref-action=print --advance=test-ref-advance --ref=refs/heads/test-ref-target topic1..topic2 >result &&
+
+ test_line_count = 1 result &&
+ test_grep "^update refs/heads/test-ref-target " result
+'
+
+test_expect_success 'using --revert with --ref' '
+ git branch test-ref-revert topic4 &&
+ git branch test-ref-revert-target topic4 &&
+ test_when_finished "git branch -D test-ref-revert test-ref-revert-target" &&
+
+ git replay --ref-action=print --revert=test-ref-revert --ref=refs/heads/test-ref-revert-target topic4~1..topic4 >result &&
+
+ test_line_count = 1 result &&
+ test_grep "^update refs/heads/test-ref-revert-target " result
+'
+
+test_expect_success '--ref is incompatible with --contained' '
+ test_must_fail git replay --onto=main --ref=refs/heads/main --contained topic1..topic2 2>err &&
+ test_grep "cannot be used together" err
+'
+
+test_expect_success '--ref with nonexistent fully-qualified ref' '
+ test_when_finished "git update-ref -d refs/heads/new-branch" &&
+
+ git replay --onto=main --ref=refs/heads/new-branch topic1..topic2 &&
+
+ git log --format=%s -2 new-branch >actual &&
+ test_write_lines E D >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '--ref must be a valid refname' '
+ test_must_fail git replay --onto=main --ref="refs/heads/bad..ref" topic1..topic2 2>err &&
+ test_grep "is not a valid refname" err
+'
+
+test_expect_success '--ref requires fully qualified ref' '
+ test_must_fail git replay --onto=main --ref=main topic1..topic2 2>err &&
+ test_grep "is not a valid refname" err
+'
+
+test_expect_success '--onto with --ref rejects multiple revision ranges' '
+ test_must_fail git replay --onto=main --ref=refs/heads/topic2 ^topic1 topic2 topic4 2>err &&
+ test_grep "cannot be used with multiple revision ranges" err
+'
+
test_done
--
2.53.0.310.g728cabbaf7
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH v2 3/3] replay: allow to specify a ref with option --ref
2026-03-25 15:59 ` [PATCH v2 3/3] replay: allow to specify a ref with option --ref Toon Claes
@ 2026-03-25 18:44 ` Junio C Hamano
0 siblings, 0 replies; 18+ messages in thread
From: Junio C Hamano @ 2026-03-25 18:44 UTC (permalink / raw)
To: Toon Claes; +Cc: git, Justin Tobler, Siddharth Asthana, Yee Cheng Chin
Toon Claes <toon@iotcl.com> writes:
> When option '--onto' is passed to git-replay(1), the command will update
> refs from the <revision-range> passed to the command. When using option
> '--advance' or '--revert', the argument of that option is a ref that
> will be updated.
>
> To enable users to specify which ref to update, add option '--ref'. When
> using option '--ref', the refs described above are left untouched and
> instead the argument of this option is updated instead.
>
> Signed-off-by: Toon Claes <toon@iotcl.com>
> ---
> Documentation/git-replay.adoc | 21 +++++++++++++-
> builtin/replay.c | 8 +++++-
> replay.c | 33 +++++++++++++++++-----
> replay.h | 7 +++++
> t/t3650-replay-basics.sh | 66 +++++++++++++++++++++++++++++++++++++++++++
> 5 files changed, 126 insertions(+), 9 deletions(-)
>
> diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc
> index 7e749a0477..5952ecb50d 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-action=<mode>] <revision-range>
> + [--ref=<branch>] [--ref-action=<mode>] <revision-range>
>
> DESCRIPTION
> -----------
> @@ -66,6 +66,15 @@ incompatible with `--contained` (which is a modifier for `--onto` only).
> Update all branches that point at commits in
> <revision-range>. Requires `--onto`.
>
> +--ref=<branch>::
As this thing takes a full refname (e.g., "--ref=refsheads/mybranch"
in the example in hunk ll.197,+16), we probably want
--ref=<ref>::
instead.
In the modern documentation style, this should be `--ref=<ref>`::
but let's consistently use traditional style and leave the clean-up
until the dust settles and when the command becomes more quiescent.
> @@ -188,6 +197,16 @@ NOTE: For reverting an entire merge request as a single commit (rather than
> commit-by-commit), consider using `git merge-tree --merge-base $TIP HEAD $BASE`
> which can avoid unnecessary merge conflicts.
>
> +To replay onto a specific commit while updating a different reference:
> +
> +------------
> +$ git replay --onto=112233 --ref=refs/heads/mybranch aabbcc..ddeeff
> +------------
> +
> +This replays the range `aabbcc..ddeeff` onto commit `112233` and updates
> +`refs/heads/mybranch` to point at the result. This can be useful when you want
> +to use bare commit IDs instead of branch names.
> +
> GIT
> ---
> Part of the linkgit:git[1] suite
> diff --git a/builtin/replay.c b/builtin/replay.c
> index a5f81b67d4..876026549e 100644
> --- a/builtin/replay.c
> +++ b/builtin/replay.c
> @@ -84,7 +84,7 @@ int cmd_replay(int argc,
> const char *const replay_usage[] = {
> N_("(EXPERIMENTAL!) git replay "
> "([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)\n"
> - "[--ref-action=<mode>] <revision-range>"),
> + "[--ref=<branch>] [--ref-action=<mode>] <revision-range>"),
Ditto.
> NULL
> };
> struct option replay_options[] = {
> @@ -102,6 +102,10 @@ int cmd_replay(int argc,
> N_("branch"),
> N_("revert commits onto given branch"),
> PARSE_OPT_NONEG),
> + OPT_STRING_F(0, "ref", &opts.ref,
> + N_("branch"),
> + N_("reference to update with result"),
> + PARSE_OPT_NONEG),
Ditto.
> diff --git a/replay.c b/replay.c
> index 199066f6b3..63cec56d48 100644
> --- a/replay.c
> +++ b/replay.c
> @@ -348,6 +348,8 @@ int replay_revisions(struct rev_info *revs,
> bool detached_head;
> char *advance;
> char *revert;
> + const char *ref;
> + struct object_id old_oid;
> enum replay_mode mode = REPLAY_MODE_PICK;
> int ret;
>
> @@ -358,6 +360,27 @@ int replay_revisions(struct rev_info *revs,
> set_up_replay_mode(revs->repo, &revs->cmdline, opts->onto,
> &detached_head, &advance, &revert, &onto, &update_refs);
>
> + if (opts->ref) {
> + struct object_id oid;
> +
> + if (update_refs && strset_get_size(update_refs) > 1) {
> + ret = error(_("'--ref' cannot be used with multiple revision ranges"));
> + goto out;
> + }
> + if (check_refname_format(opts->ref, 0) || !starts_with(opts->ref, "refs/")) {
Can we do something about this overly long line?
> + ret = error(_("'%s' is not a valid refname"), opts->ref);
> + goto out;
> + }
> + ref = opts->ref;
> + if (!refs_read_ref(get_main_ref_store(revs->repo), opts->ref, &oid))
> + oidcpy(&old_oid, &oid);
> + else
> + oidclr(&old_oid, revs->repo->hash_algo);
> + } else {
> + ref = advance ? advance : revert;
> + oidcpy(&old_oid, &onto->object.oid);
> + }
> +
> /* FIXME: Should allow replaying commits with the first as a root commit */
>
> if (prepare_revision_walk(revs) < 0) {
> @@ -393,7 +416,7 @@ int replay_revisions(struct rev_info *revs,
> kh_value(replayed_commits, pos) = last_commit;
>
> /* Update any necessary branches */
> - if (advance || revert)
> + if (ref)
> continue;
Nice.
> @@ -427,13 +450,9 @@ int replay_revisions(struct rev_info *revs,
> goto out;
> }
>
> - /* In --advance or --revert mode, update the target ref */
> - if (advance || revert) {
> - const char *ref = advance ? advance : revert;
> - replay_result_queue_update(out, ref,
> - &onto->object.oid,
> + if (ref)
> + replay_result_queue_update(out, ref, &old_oid,
> &last_commit->object.oid);
Nice, too.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v2 0/3] Add option --ref to git-replay(1)
2026-03-25 15:59 ` [PATCH v2 0/3] Add option --ref to git-replay(1) Toon Claes
` (2 preceding siblings ...)
2026-03-25 15:59 ` [PATCH v2 3/3] replay: allow to specify a ref with option --ref Toon Claes
@ 2026-03-25 18:34 ` Junio C Hamano
2026-03-26 21:20 ` Junio C Hamano
4 siblings, 0 replies; 18+ messages in thread
From: Junio C Hamano @ 2026-03-25 18:34 UTC (permalink / raw)
To: Toon Claes; +Cc: git, Justin Tobler, Siddharth Asthana, Yee Cheng Chin
Toon Claes <toon@iotcl.com> writes:
> In a previous RFC[1] I suggested to implement subcommands into
> git-replay(1). While it would be arguable nice to have subcommands for
> the different modes, because git-replay(1) is a plumbing commands, it's
> fine to keep a status quo.
>
> This series takes one thing for that RFC though: adding option --ref.
> This new option is useful if you want to have full control over which
> ref is being updated, and not want to rely on the refs that are using
> in the <revision-range> or as the value for --advance and --revert.
>
> These changes answer the needs expressed in[2].
>
> This series is based on Siddharth's series[3] to add '--revert' to
> git-replay(1) (sa/replay-revert @ ba5c0d03d3).
Which unfortunately got updated just 20 hours ago X-<.
Will try to wriggle it in, but please double check when I push the
integration result of today.
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v2 0/3] Add option --ref to git-replay(1)
2026-03-25 15:59 ` [PATCH v2 0/3] Add option --ref to git-replay(1) Toon Claes
` (3 preceding siblings ...)
2026-03-25 18:34 ` [PATCH v2 0/3] Add option --ref to git-replay(1) Junio C Hamano
@ 2026-03-26 21:20 ` Junio C Hamano
4 siblings, 0 replies; 18+ messages in thread
From: Junio C Hamano @ 2026-03-26 21:20 UTC (permalink / raw)
To: Toon Claes; +Cc: git, Justin Tobler, Siddharth Asthana, Yee Cheng Chin
Toon Claes <toon@iotcl.com> writes:
> This series is based on Siddharth's series[3] to add '--revert' to
> git-replay(1) (sa/replay-revert @ ba5c0d03d3).
As sa/replay-revert has been updated, I rebased these three patches
on top of the updated version of that other topic and merged it to
'seen', which broke CI with t3650.
I naturally suspected that I made some stupid mistakes while
rebasing, so I applied these three patches directly on top of that
old sa/replay-revert. Unfortunately, the same test t3650 fails
exactly the same way with merge-ort aborting.
The failing test run fails like so:
ok 45 - --ref with nonexistent fully-qualified ref
expecting success of 3650.46 '--ref must be a valid refname':
test_must_fail git replay --onto=main --ref="refs/heads/bad..ref" topic1..topic2 2>err &&
test_grep "is not a valid refname" err
/home/gitster/w/git.git/t/test-lib-functions.sh: line 1180: 2840466 Aborted (core dumped) "$@" 2>&7
test_must_fail: died by signal 6: git replay --onto=main --ref=refs/heads/bad..ref topic1..topic2
not ok 46 - --ref must be a valid refname
#
# test_must_fail git replay --onto=main --ref="refs/heads/bad..ref" topic1..topic2 2>err &&
# test_grep "is not a valid refname" err
#
1
^ permalink raw reply [flat|nested] 18+ messages in thread