* [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
` (4 subsequent siblings)
5 siblings, 0 replies; 25+ 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] 25+ 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
` (3 subsequent siblings)
5 siblings, 0 replies; 25+ 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] 25+ 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
` (2 subsequent siblings)
5 siblings, 1 reply; 25+ 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] 25+ 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
2026-03-31 7:56 ` Toon Claes
0 siblings, 1 reply; 25+ 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] 25+ messages in thread* Re: [PATCH v2 3/3] replay: allow to specify a ref with option --ref
2026-03-25 18:44 ` Junio C Hamano
@ 2026-03-31 7:56 ` Toon Claes
0 siblings, 0 replies; 25+ messages in thread
From: Toon Claes @ 2026-03-31 7:56 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Justin Tobler, Siddharth Asthana, Yee Cheng Chin
Junio C Hamano <gitster@pobox.com> writes:
> 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.
Yes, I noticed various things aren't up to stadard. I'm happy to include
a patch that cleans it up. But agreed, the dust needs to settle first.
--
Cheers,
Toon
^ permalink raw reply [flat|nested] 25+ 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
2026-04-01 20:55 ` [PATCH v3 " Toon Claes
5 siblings, 0 replies; 25+ 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] 25+ 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
2026-03-31 7:55 ` Toon Claes
2026-04-01 20:55 ` [PATCH v3 " Toon Claes
5 siblings, 1 reply; 25+ 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] 25+ messages in thread* Re: [PATCH v2 0/3] Add option --ref to git-replay(1)
2026-03-26 21:20 ` Junio C Hamano
@ 2026-03-31 7:55 ` Toon Claes
2026-03-31 21:42 ` Junio C Hamano
0 siblings, 1 reply; 25+ messages in thread
From: Toon Claes @ 2026-03-31 7:55 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Justin Tobler, Siddharth Asthana, Yee Cheng Chin
Junio C Hamano <gitster@pobox.com> writes:
> 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
> #
No need for you to worry about those conflicts. I'm happy to do the
rebase myself.
--
Cheers,
Toon
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 0/3] Add option --ref to git-replay(1)
2026-03-31 7:55 ` Toon Claes
@ 2026-03-31 21:42 ` Junio C Hamano
0 siblings, 0 replies; 25+ messages in thread
From: Junio C Hamano @ 2026-03-31 21:42 UTC (permalink / raw)
To: Toon Claes; +Cc: git, Justin Tobler, Siddharth Asthana, Yee Cheng Chin
Toon Claes <toon@iotcl.com> writes:
> Junio C Hamano <gitster@pobox.com> writes:
>
>> 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
>> #
>
> No need for you to worry about those conflicts. I'm happy to do the
> rebase myself.
Will wait for updates. Thanks.
I said I suspected that my rebasing introduced a bug, but what I was
reporting in the message you are responding to is that the patches
without my rebases are breaking tests. So perhaps you do not have
to rebase and there won't be any problematic conflicts, but the
patches do need to be updated to address the test failure.
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v3 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
` (4 preceding siblings ...)
2026-03-26 21:20 ` Junio C Hamano
@ 2026-04-01 20:55 ` Toon Claes
2026-04-01 20:55 ` [PATCH v3 1/3] builtin/replay: mark options as not negatable Toon Claes
` (2 more replies)
5 siblings, 3 replies; 25+ messages in thread
From: Toon Claes @ 2026-04-01 20:55 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 v3:
- In the docs, clarify --ref requires a fully qualified ref, not just a
branch name.
- Fix crash by initializing merge_opt to `{ 0 }`.
- Link to v2: https://patch.msgid.link/20260325-toon-replay-arbitrary-ref-v2-0-553038702c9c@iotcl.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 | 45 +++++++++++++++++++++--------
builtin/replay.c | 38 ++++++++++++++++---------
replay.c | 35 +++++++++++++++++------
replay.h | 7 +++++
t/t3650-replay-basics.sh | 66 +++++++++++++++++++++++++++++++++++++++++++
5 files changed, 157 insertions(+), 34 deletions(-)
Range-diff versus v2:
1: 260dff6c82 = 1: dd46a0efd5 builtin/replay: mark options as not negatable
2: eca4dffa18 ! 2: b290493bd0 replay: use stuck form in documentation and help message
@@ Documentation/git-replay.adoc: git-replay - EXPERIMENTAL: Replay commits on a ne
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>
+(EXPERIMENTAL!) 'git replay' ([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)
+ [--ref-action=<mode>] <revision-range>
@@ Documentation/git-replay.adoc: The default mode can be configured via the `repla
<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
+- linkgit:git-rev-parse[1]. In `--advance <branch>` or
+- `--revert <branch>` mode, the range should have a single tip,
++ linkgit:git-rev-parse[1]. In `--advance=<branch>` or
++ `--revert=<branch>` mode, the range should have a single tip,
+ so that it's clear to which tip the advanced or reverted
+ <branch> should point. Any commits in the range whose changes
+ are already present in the branch the commits are being
@@ Documentation/git-replay.adoc: EXAMPLES
To simply rebase `mybranch` onto `target`:
@@ builtin/replay.c: 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>..."),
+- "[--ref-action[=<mode>]] <revision-range>"),
+ "([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)\n"
+ "[--ref-action=<mode>] <revision-range>"),
NULL
3: 2676c2ae78 ! 3: 44e825d9e6 replay: allow to specify a ref with option --ref
@@ Commit message
using option '--ref', the refs described above are left untouched and
instead the argument of this option is updated instead.
+ Because this introduces code paths in replay.c that jump to `out` before
+ init_basic_merge_options() is called on `merge_opt`, zero-initialize the
+ struct.
+
Signed-off-by: Toon Claes <toon@iotcl.com>
## Documentation/git-replay.adoc ##
@@ 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=<ref>] [--ref-action=<mode>] <revision-range>
DESCRIPTION
-----------
@@ Documentation/git-replay.adoc: incompatible with `--contained` (which is a modif
Update all branches that point at commits in
<revision-range>. Requires `--onto`.
-+--ref=<branch>::
++--ref=<ref>::
+ Override which reference is updated with the result of the replay.
++ The ref must be fully qualified.
+ 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`.
++ 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:
@@ 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>] <revision-range>"),
++ "[--ref=<ref>] [--ref-action=<mode>] <revision-range>"),
NULL
};
struct option replay_options[] = {
@@ builtin/replay.c: int cmd_replay(int argc,
N_("mode"),
N_("control ref update behavior (update|print)"),
@@ builtin/replay.c: int cmd_replay(int argc,
- die_for_incompatible_opt3(!!opts.onto, "--onto",
- !!opts.advance, "--advance",
- !!opts.revert, "--revert");
+ opts.contained, "--contained");
+ die_for_incompatible_opt2(!!opts.revert, "--revert",
+ opts.contained, "--contained");
+ die_for_incompatible_opt2(!!opts.ref, "--ref",
+ !!opts.contained, "--contained");
- if (opts.contained && !opts.onto)
- die(_("--contained requires --onto"));
+ /* Parse ref action mode from command line or config */
+ ref_mode = get_ref_action_mode(repo, ref_action);
## replay.c ##
@@ replay.c: int replay_revisions(struct rev_info *revs,
+ struct commit *last_commit = NULL;
+ struct commit *commit;
+ struct commit *onto = NULL;
+- struct merge_options merge_opt;
++ struct merge_options merge_opt = { 0 };
+ struct merge_result result = {
+ .clean = 1,
+ };
bool detached_head;
char *advance;
char *revert;
---
base-commit: 2760ee49834953c0860fa5d7983a6af4d27cb6a9
change-id: 20260323-toon-replay-arbitrary-ref-5a81f5f976c7
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH v3 1/3] builtin/replay: mark options as not negatable
2026-04-01 20:55 ` [PATCH v3 " Toon Claes
@ 2026-04-01 20:55 ` Toon Claes
2026-04-01 20:55 ` [PATCH v3 2/3] replay: use stuck form in documentation and help message Toon Claes
2026-04-01 20:55 ` [PATCH v3 3/3] replay: allow to specify a ref with option --ref Toon Claes
2 siblings, 0 replies; 25+ messages in thread
From: Toon Claes @ 2026-04-01 20:55 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 a0879b020f..85aa9fa0a4 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -89,20 +89,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] 25+ messages in thread* [PATCH v3 2/3] replay: use stuck form in documentation and help message
2026-04-01 20:55 ` [PATCH v3 " Toon Claes
2026-04-01 20:55 ` [PATCH v3 1/3] builtin/replay: mark options as not negatable Toon Claes
@ 2026-04-01 20:55 ` Toon Claes
2026-04-01 20:55 ` [PATCH v3 3/3] replay: allow to specify a ref with option --ref Toon Claes
2 siblings, 0 replies; 25+ messages in thread
From: Toon Claes @ 2026-04-01 20:55 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 | 25 +++++++++++++------------
builtin/replay.c | 4 ++--
2 files changed, 15 insertions(+), 14 deletions(-)
diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc
index 997097e420..5bb478c281 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,8 +80,8 @@ 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>` or
- `--revert <branch>` mode, the range should have a single tip,
+ linkgit:git-rev-parse[1]. In `--advance=<branch>` or
+ `--revert=<branch>` mode, the range should have a single tip,
so that it's clear to which tip the advanced or reverted
<branch> should point. Any commits in the range whose changes
are already present in the branch the commits are being
@@ -127,7 +128,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.
@@ -135,14 +136,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
@@ -154,7 +155,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
@@ -165,7 +166,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`,
@@ -176,7 +177,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 85aa9fa0a4..fbfeb780b6 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -84,8 +84,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] 25+ messages in thread* [PATCH v3 3/3] replay: allow to specify a ref with option --ref
2026-04-01 20:55 ` [PATCH v3 " Toon Claes
2026-04-01 20:55 ` [PATCH v3 1/3] builtin/replay: mark options as not negatable Toon Claes
2026-04-01 20:55 ` [PATCH v3 2/3] replay: use stuck form in documentation and help message Toon Claes
@ 2026-04-01 20:55 ` Toon Claes
2 siblings, 0 replies; 25+ messages in thread
From: Toon Claes @ 2026-04-01 20:55 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.
Because this introduces code paths in replay.c that jump to `out` before
init_basic_merge_options() is called on `merge_opt`, zero-initialize the
struct.
Signed-off-by: Toon Claes <toon@iotcl.com>
---
Documentation/git-replay.adoc | 22 ++++++++++++++-
builtin/replay.c | 8 +++++-
replay.c | 35 +++++++++++++++++------
replay.h | 7 +++++
t/t3650-replay-basics.sh | 66 +++++++++++++++++++++++++++++++++++++++++++
5 files changed, 128 insertions(+), 10 deletions(-)
diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc
index 5bb478c281..a32f72aead 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=<ref>] [--ref-action=<mode>] <revision-range>
DESCRIPTION
-----------
@@ -66,6 +66,16 @@ incompatible with `--contained` (which is a modifier for `--onto` only).
Update all branches that point at commits in
<revision-range>. Requires `--onto`.
+--ref=<ref>::
+ Override which reference is updated with the result of the replay.
+ The ref must be fully qualified.
+ 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:
+
@@ -189,6 +199,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 fbfeb780b6..39e3a86f6c 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-action=<mode>] <revision-range>"),
+ "[--ref=<ref>] [--ref-action=<mode>] <revision-range>"),
NULL
};
struct option replay_options[] = {
@@ -103,6 +103,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)"),
@@ -126,6 +130,8 @@ int cmd_replay(int argc,
opts.contained, "--contained");
die_for_incompatible_opt2(!!opts.revert, "--revert",
opts.contained, "--contained");
+ die_for_incompatible_opt2(!!opts.ref, "--ref",
+ !!opts.contained, "--contained");
/* Parse ref action mode from command line or config */
ref_mode = get_ref_action_mode(repo, ref_action);
diff --git a/replay.c b/replay.c
index 199066f6b3..444f7ceefc 100644
--- a/replay.c
+++ b/replay.c
@@ -341,13 +341,15 @@ int replay_revisions(struct rev_info *revs,
struct commit *last_commit = NULL;
struct commit *commit;
struct commit *onto = NULL;
- struct merge_options merge_opt;
+ struct merge_options merge_opt = { 0 };
struct merge_result result = {
.clean = 1,
};
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 217f6fb292..d5c7dd1bf4 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -495,4 +495,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] 25+ messages in thread