* [PATCH v2 1/5] replay: remove dead code and rearrange
2025-12-30 15:01 ` [PATCH v2 0/5] " kristofferhaugsbakk
@ 2025-12-30 15:01 ` kristofferhaugsbakk
2025-12-30 22:50 ` Elijah Newren
2025-12-30 15:01 ` [PATCH v2 2/5] replay: find *onto only after testing for ref name kristofferhaugsbakk
` (5 subsequent siblings)
6 siblings, 1 reply; 35+ messages in thread
From: kristofferhaugsbakk @ 2025-12-30 15:01 UTC (permalink / raw)
To: git
Cc: Kristoffer Haugsbakk, christian.couder, newren, Siddharth Asthana,
Phillip Wood
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
22d99f01 (replay: add --advance or 'cherry-pick' mode, 2023-11-24) both
added `--advance` and made one of `--onto` or `--advance` mandatory.
But `determine_replay_mode` claims that there is a third alternative;
neither of `--onto` or `--advance` were given:
if (onto_name) {
...
} else if (*advance_name) {
...
} else {
...
}
But this is false—the fallthrough else-block is dead code.
Commit 22d99f01 was iterated upon by several people.[1] The initial
author wrote code for a sort of *guess mode*, allowing for shorter
commands when that was possible. But the next person instead made one
of the aforementioned options mandatory. In turn this code was dead on
arrival in git.git.
[1]: https://lore.kernel.org/git/CABPp-BEcJqjD4ztsZo2FTZgWT5ZOADKYEyiZtda+d0mSd1quPQ@mail.gmail.com/
Let’s remove this code. We can also join the if-block with the
condition `!*advance_name` into the `*onto` block since we do not set
`*advance_name` in this function. It only looked like we might set it
since the dead code has this line:
*advance_name = xstrdup_or_null(last_key);
Let’s also rename the function since we do not determine
the replay mode here. We simply populate data structures.
Note that there might be more dead code caused by this *guess mode*.
We only concern ourselves with this function for now.
Helped-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Notes (series):
v2: [new]
See the link in the commit message.
Not strictly needed for this series but I think it makes sense to fix it
here.
builtin/replay.c | 70 +++++++++++-------------------------------------
1 file changed, 16 insertions(+), 54 deletions(-)
diff --git a/builtin/replay.c b/builtin/replay.c
index 6172c8aacc9..54849f65c87 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -154,16 +154,16 @@ static void get_ref_information(struct repository *repo,
free(fullname);
}
}
-static void determine_replay_mode(struct repository *repo,
- struct rev_cmdline_info *cmd_info,
- const char *onto_name,
- char **advance_name,
- struct commit **onto,
- struct strset **update_refs)
+static void populate_for_onto_or_advance_mode(struct repository *repo,
+ struct rev_cmdline_info *cmd_info,
+ const char *onto_name,
+ char **advance_name,
+ struct commit **onto,
+ struct strset **update_refs)
{
struct ref_info rinfo;
get_ref_information(repo, cmd_info, &rinfo);
if (!rinfo.positive_refexprs)
@@ -174,69 +174,30 @@ static void determine_replay_mode(struct repository *repo,
if (onto_name) {
*onto = peel_committish(repo, onto_name);
if (rinfo.positive_refexprs <
strset_get_size(&rinfo.positive_refs))
die(_("all positive revisions given must be references"));
- } else if (*advance_name) {
+ *update_refs = xcalloc(1, sizeof(**update_refs));
+ **update_refs = rinfo.positive_refs;
+ memset(&rinfo.positive_refs, 0, sizeof(**update_refs));
+ } else {
struct object_id oid;
char *fullname = NULL;
+ if (!*advance_name)
+ BUG("expected either onto_name or *advance_name in this function");
+
*onto = peel_committish(repo, *advance_name);
if (repo_dwim_ref(repo, *advance_name, strlen(*advance_name),
&oid, &fullname, 0) == 1) {
free(*advance_name);
*advance_name = fullname;
} else {
die(_("argument to --advance must be a reference"));
}
if (rinfo.positive_refexprs > 1)
die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
- } else {
- int positive_refs_complete = (
- rinfo.positive_refexprs ==
- strset_get_size(&rinfo.positive_refs));
- int negative_refs_complete = (
- rinfo.negative_refexprs ==
- strset_get_size(&rinfo.negative_refs));
- /*
- * We need either positive_refs_complete or
- * negative_refs_complete, but not both.
- */
- if (rinfo.negative_refexprs > 0 &&
- positive_refs_complete == negative_refs_complete)
- die(_("cannot implicitly determine whether this is an --advance or --onto operation"));
- if (negative_refs_complete) {
- struct hashmap_iter iter;
- struct strmap_entry *entry;
- const char *last_key = NULL;
-
- if (rinfo.negative_refexprs == 0)
- die(_("all positive revisions given must be references"));
- else if (rinfo.negative_refexprs > 1)
- die(_("cannot implicitly determine whether this is an --advance or --onto operation"));
- else if (rinfo.positive_refexprs > 1)
- die(_("cannot advance target with multiple source branches because ordering would be ill-defined"));
-
- /* Only one entry, but we have to loop to get it */
- strset_for_each_entry(&rinfo.negative_refs,
- &iter, entry) {
- last_key = entry->key;
- }
-
- free(*advance_name);
- *advance_name = xstrdup_or_null(last_key);
- } else { /* positive_refs_complete */
- if (rinfo.negative_refexprs > 1)
- die(_("cannot implicitly determine correct base for --onto"));
- if (rinfo.negative_refexprs == 1)
- *onto = rinfo.onto;
- }
- }
- if (!*advance_name) {
- *update_refs = xcalloc(1, sizeof(**update_refs));
- **update_refs = rinfo.positive_refs;
- memset(&rinfo.positive_refs, 0, sizeof(**update_refs));
}
strset_clear(&rinfo.negative_refs);
strset_clear(&rinfo.positive_refs);
}
@@ -384,12 +345,13 @@ int cmd_replay(int argc,
"'%s' bit in 'struct rev_info' will be forced"),
"simplify_history");
revs.simplify_history = 0;
}
- determine_replay_mode(repo, &revs.cmdline, onto_name, &advance_name,
- &onto, &update_refs);
+ populate_for_onto_or_advance_mode(repo, &revs.cmdline,
+ onto_name, &advance_name,
+ &onto, &update_refs);
if (!onto) /* FIXME: Should handle replaying down to root commit */
die("Replaying down to root commit is not supported yet!");
if (prepare_revision_walk(&revs) < 0) {
--
2.52.0.10.g08704017180
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v2 1/5] replay: remove dead code and rearrange
2025-12-30 15:01 ` [PATCH v2 1/5] replay: remove dead code and rearrange kristofferhaugsbakk
@ 2025-12-30 22:50 ` Elijah Newren
2025-12-30 23:37 ` Junio C Hamano
2026-01-02 9:51 ` Kristoffer Haugsbakk
0 siblings, 2 replies; 35+ messages in thread
From: Elijah Newren @ 2025-12-30 22:50 UTC (permalink / raw)
To: kristofferhaugsbakk
Cc: git, Kristoffer Haugsbakk, christian.couder, Siddharth Asthana,
Phillip Wood
On Tue, Dec 30, 2025 at 7:03 AM <kristofferhaugsbakk@fastmail.com> wrote:
>
> From: Kristoffer Haugsbakk <code@khaugsbakk.name>
>
> 22d99f01 (replay: add --advance or 'cherry-pick' mode, 2023-11-24) both
> added `--advance` and made one of `--onto` or `--advance` mandatory.
> But `determine_replay_mode` claims that there is a third alternative;
> neither of `--onto` or `--advance` were given:
>
> if (onto_name) {
> ...
> } else if (*advance_name) {
> ...
> } else {
> ...
> }
>
> But this is false—the fallthrough else-block is dead code.
>
> Commit 22d99f01 was iterated upon by several people.[1] The initial
> author wrote code for a sort of *guess mode*, allowing for shorter
> commands when that was possible. But the next person instead made one
> of the aforementioned options mandatory. In turn this code was dead on
> arrival in git.git.
>
> [1]: https://lore.kernel.org/git/CABPp-BEcJqjD4ztsZo2FTZgWT5ZOADKYEyiZtda+d0mSd1quPQ@mail.gmail.com/
>
> Let’s remove this code. We can also join the if-block with the
> condition `!*advance_name` into the `*onto` block since we do not set
> `*advance_name` in this function. It only looked like we might set it
> since the dead code has this line:
>
> *advance_name = xstrdup_or_null(last_key);
>
> Let’s also rename the function since we do not determine
> the replay mode here. We simply populate data structures.
>
> Note that there might be more dead code caused by this *guess mode*.
> We only concern ourselves with this function for now.
>
> Helped-by: Elijah Newren <newren@gmail.com>
> Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
> ---
>
> Notes (series):
> v2: [new]
>
> See the link in the commit message.
>
> Not strictly needed for this series but I think it makes sense to fix it
> here.
>
> builtin/replay.c | 70 +++++++++++-------------------------------------
> 1 file changed, 16 insertions(+), 54 deletions(-)
>
> diff --git a/builtin/replay.c b/builtin/replay.c
> index 6172c8aacc9..54849f65c87 100644
> --- a/builtin/replay.c
> +++ b/builtin/replay.c
> @@ -154,16 +154,16 @@ static void get_ref_information(struct repository *repo,
>
> free(fullname);
> }
> }
>
> -static void determine_replay_mode(struct repository *repo,
> - struct rev_cmdline_info *cmd_info,
> - const char *onto_name,
> - char **advance_name,
> - struct commit **onto,
> - struct strset **update_refs)
> +static void populate_for_onto_or_advance_mode(struct repository *repo,
> + struct rev_cmdline_info *cmd_info,
> + const char *onto_name,
> + char **advance_name,
> + struct commit **onto,
> + struct strset **update_refs)
Renaming makes sense, but the new name is quite the mouthful, and it
feels slightly odd because "onto" is both a command line flag and a
variable -- and the variable value is used regardless of which command
line flag is used. Since the variable is used either way, there's a
risk someone might be confused by this function name. Maybe just
setup_replay_mode() ? Or maybe others have other suggestions?
> {
> struct ref_info rinfo;
>
> get_ref_information(repo, cmd_info, &rinfo);
> if (!rinfo.positive_refexprs)
> @@ -174,69 +174,30 @@ static void determine_replay_mode(struct repository *repo,
> if (onto_name) {
> *onto = peel_committish(repo, onto_name);
> if (rinfo.positive_refexprs <
> strset_get_size(&rinfo.positive_refs))
> die(_("all positive revisions given must be references"));
> - } else if (*advance_name) {
> + *update_refs = xcalloc(1, sizeof(**update_refs));
> + **update_refs = rinfo.positive_refs;
> + memset(&rinfo.positive_refs, 0, sizeof(**update_refs));
> + } else {
> struct object_id oid;
> char *fullname = NULL;
>
> + if (!*advance_name)
> + BUG("expected either onto_name or *advance_name in this function");
> +
> *onto = peel_committish(repo, *advance_name);
> if (repo_dwim_ref(repo, *advance_name, strlen(*advance_name),
> &oid, &fullname, 0) == 1) {
> free(*advance_name);
> *advance_name = fullname;
> } else {
> die(_("argument to --advance must be a reference"));
> }
> if (rinfo.positive_refexprs > 1)
> die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
> - } else {
> - int positive_refs_complete = (
> - rinfo.positive_refexprs ==
> - strset_get_size(&rinfo.positive_refs));
> - int negative_refs_complete = (
> - rinfo.negative_refexprs ==
> - strset_get_size(&rinfo.negative_refs));
> - /*
> - * We need either positive_refs_complete or
> - * negative_refs_complete, but not both.
> - */
> - if (rinfo.negative_refexprs > 0 &&
> - positive_refs_complete == negative_refs_complete)
> - die(_("cannot implicitly determine whether this is an --advance or --onto operation"));
> - if (negative_refs_complete) {
> - struct hashmap_iter iter;
> - struct strmap_entry *entry;
> - const char *last_key = NULL;
> -
> - if (rinfo.negative_refexprs == 0)
> - die(_("all positive revisions given must be references"));
> - else if (rinfo.negative_refexprs > 1)
> - die(_("cannot implicitly determine whether this is an --advance or --onto operation"));
> - else if (rinfo.positive_refexprs > 1)
> - die(_("cannot advance target with multiple source branches because ordering would be ill-defined"));
> -
> - /* Only one entry, but we have to loop to get it */
> - strset_for_each_entry(&rinfo.negative_refs,
> - &iter, entry) {
> - last_key = entry->key;
> - }
> -
> - free(*advance_name);
> - *advance_name = xstrdup_or_null(last_key);
> - } else { /* positive_refs_complete */
> - if (rinfo.negative_refexprs > 1)
> - die(_("cannot implicitly determine correct base for --onto"));
> - if (rinfo.negative_refexprs == 1)
> - *onto = rinfo.onto;
> - }
> - }
> - if (!*advance_name) {
> - *update_refs = xcalloc(1, sizeof(**update_refs));
> - **update_refs = rinfo.positive_refs;
> - memset(&rinfo.positive_refs, 0, sizeof(**update_refs));
> }
> strset_clear(&rinfo.negative_refs);
> strset_clear(&rinfo.positive_refs);
> }
>
> @@ -384,12 +345,13 @@ int cmd_replay(int argc,
> "'%s' bit in 'struct rev_info' will be forced"),
> "simplify_history");
> revs.simplify_history = 0;
> }
>
> - determine_replay_mode(repo, &revs.cmdline, onto_name, &advance_name,
> - &onto, &update_refs);
> + populate_for_onto_or_advance_mode(repo, &revs.cmdline,
> + onto_name, &advance_name,
> + &onto, &update_refs);
>
> if (!onto) /* FIXME: Should handle replaying down to root commit */
> die("Replaying down to root commit is not supported yet!");
>
> if (prepare_revision_walk(&revs) < 0) {
> --
> 2.52.0.10.g08704017180
Looks fine otherwise.
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [PATCH v2 1/5] replay: remove dead code and rearrange
2025-12-30 22:50 ` Elijah Newren
@ 2025-12-30 23:37 ` Junio C Hamano
2026-01-02 9:51 ` Kristoffer Haugsbakk
1 sibling, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2025-12-30 23:37 UTC (permalink / raw)
To: Elijah Newren
Cc: kristofferhaugsbakk, git, Kristoffer Haugsbakk, christian.couder,
Siddharth Asthana, Phillip Wood
> > -static void determine_replay_mode(struct repository *repo,
> > ...
> > +static void populate_for_onto_or_advance_mode(struct repository *repo,
> > ...
> Renaming makes sense, but the new name is quite the mouthful, and it
> feels slightly odd because "onto" is both a command line flag and a
> variable -- and the variable value is used regardless of which command
> line flag is used. Since the variable is used either way, there's a
> risk someone might be confused by this function name. Maybe just
> setup_replay_mode() ? Or maybe others have other suggestions?
After reading the above, the name that came to my mind is (curiously)
determine_replay_mode() ;-).
> Looks fine otherwise.
Thanks.
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v2 1/5] replay: remove dead code and rearrange
2025-12-30 22:50 ` Elijah Newren
2025-12-30 23:37 ` Junio C Hamano
@ 2026-01-02 9:51 ` Kristoffer Haugsbakk
1 sibling, 0 replies; 35+ messages in thread
From: Kristoffer Haugsbakk @ 2026-01-02 9:51 UTC (permalink / raw)
To: Elijah Newren, Kristoffer Haugsbakk
Cc: git, Christian Couder, Siddharth Asthana, Phillip Wood
On Tue, Dec 30, 2025, at 23:50, Elijah Newren wrote:
> On Tue, Dec 30, 2025 at 7:03 AM <kristofferhaugsbakk@fastmail.com> wrote:
>>[snip]
>> -static void determine_replay_mode(struct repository *repo,
>> - struct rev_cmdline_info *cmd_info,
>> - const char *onto_name,
>> - char **advance_name,
>> - struct commit **onto,
>> - struct strset **update_refs)
>> +static void populate_for_onto_or_advance_mode(struct repository *repo,
>> + struct rev_cmdline_info *cmd_info,
>> + const char *onto_name,
>> + char **advance_name,
>> + struct commit **onto,
>> + struct strset **update_refs)
>
> Renaming makes sense, but the new name is quite the mouthful, and it
> feels slightly odd because "onto" is both a command line flag and a
> variable -- and the variable value is used regardless of which command
> line flag is used. Since the variable is used either way, there's a
> risk someone might be confused by this function name. Maybe just
> setup_replay_mode() ? Or maybe others have other suggestions?
Yeah, it is a mouthful.
I can use `set_up_replay_mode`.
>>[snip]
>
> Looks fine otherwise.
Thanks for reviewing this round!
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v2 2/5] replay: find *onto only after testing for ref name
2025-12-30 15:01 ` [PATCH v2 0/5] " kristofferhaugsbakk
2025-12-30 15:01 ` [PATCH v2 1/5] replay: remove dead code and rearrange kristofferhaugsbakk
@ 2025-12-30 15:01 ` kristofferhaugsbakk
2025-12-30 22:51 ` Elijah Newren
2025-12-30 15:01 ` [PATCH v2 3/5] replay: die descriptively when invalid commit-ish is given kristofferhaugsbakk
` (4 subsequent siblings)
6 siblings, 1 reply; 35+ messages in thread
From: kristofferhaugsbakk @ 2025-12-30 15:01 UTC (permalink / raw)
To: git
Cc: Kristoffer Haugsbakk, christian.couder, newren, Siddharth Asthana,
Phillip Wood, Junio C Hamano
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
We are about to make `peel_committish` die when it cannot find
a commit-ish instead of returning `NULL`. But that would make e.g.
`git replay --advance=refs/non-existent` die with a less descriptive
error message; the highest-level error message is that the name does
not exist as a ref, not that we cannot find a commit-ish based on
the name.
Let’s try to find the ref and only after that try to peel to
as a commit-ish.
Also add a regression test to protect this error-order from future
modifications.
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Notes (series):
v2: [new]
Fallout of v1. Needs to be moved so that the new error message does not
“shadow” this one.
See: https://lore.kernel.org/git/xmqqpl85pb7k.fsf@gitster.g/
builtin/replay.c | 2 +-
t/t3650-replay-basics.sh | 7 +++++++
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/builtin/replay.c b/builtin/replay.c
index 54849f65c87..35813140e99 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -184,18 +184,18 @@ static void populate_for_onto_or_advance_mode(struct repository *repo,
char *fullname = NULL;
if (!*advance_name)
BUG("expected either onto_name or *advance_name in this function");
- *onto = peel_committish(repo, *advance_name);
if (repo_dwim_ref(repo, *advance_name, strlen(*advance_name),
&oid, &fullname, 0) == 1) {
free(*advance_name);
*advance_name = fullname;
} else {
die(_("argument to --advance must be a reference"));
}
+ *onto = peel_committish(repo, *advance_name);
if (rinfo.positive_refexprs > 1)
die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
}
strset_clear(&rinfo.negative_refs);
strset_clear(&rinfo.positive_refs);
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index 58b37599357..7dea62f064f 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -49,10 +49,17 @@ test_expect_success 'setup' '
test_expect_success 'setup bare' '
git clone --bare . bare
'
+test_expect_success 'argument to --advance must be a reference' '
+ echo "fatal: argument to --advance must be a reference" >expect &&
+ oid=$(git rev-parse main) &&
+ test_must_fail git replay --advance=$oid topic1..topic2 2>actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'using replay to rebase two branches, one on top of other' '
git replay --onto main topic1..topic2 >result &&
test_line_count = 1 result &&
--
2.52.0.10.g08704017180
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v2 2/5] replay: find *onto only after testing for ref name
2025-12-30 15:01 ` [PATCH v2 2/5] replay: find *onto only after testing for ref name kristofferhaugsbakk
@ 2025-12-30 22:51 ` Elijah Newren
0 siblings, 0 replies; 35+ messages in thread
From: Elijah Newren @ 2025-12-30 22:51 UTC (permalink / raw)
To: kristofferhaugsbakk
Cc: git, Kristoffer Haugsbakk, christian.couder, Siddharth Asthana,
Phillip Wood, Junio C Hamano
On Tue, Dec 30, 2025 at 7:03 AM <kristofferhaugsbakk@fastmail.com> wrote:
>
> From: Kristoffer Haugsbakk <code@khaugsbakk.name>
>
> We are about to make `peel_committish` die when it cannot find
> a commit-ish instead of returning `NULL`. But that would make e.g.
> `git replay --advance=refs/non-existent` die with a less descriptive
> error message; the highest-level error message is that the name does
> not exist as a ref, not that we cannot find a commit-ish based on
> the name.
>
> Let’s try to find the ref and only after that try to peel to
> as a commit-ish.
>
> Also add a regression test to protect this error-order from future
> modifications.
"error-order" looked like a typo and took a while for me to parse.
Maybe drop the hyphen or replace with "order of errors"?
>
> Suggested-by: Junio C Hamano <gitster@pobox.com>
> Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
> ---
>
> Notes (series):
> v2: [new]
>
> Fallout of v1. Needs to be moved so that the new error message does not
> “shadow” this one.
>
> See: https://lore.kernel.org/git/xmqqpl85pb7k.fsf@gitster.g/
>
> builtin/replay.c | 2 +-
> t/t3650-replay-basics.sh | 7 +++++++
> 2 files changed, 8 insertions(+), 1 deletion(-)
>
> diff --git a/builtin/replay.c b/builtin/replay.c
> index 54849f65c87..35813140e99 100644
> --- a/builtin/replay.c
> +++ b/builtin/replay.c
> @@ -184,18 +184,18 @@ static void populate_for_onto_or_advance_mode(struct repository *repo,
> char *fullname = NULL;
>
> if (!*advance_name)
> BUG("expected either onto_name or *advance_name in this function");
>
> - *onto = peel_committish(repo, *advance_name);
> if (repo_dwim_ref(repo, *advance_name, strlen(*advance_name),
> &oid, &fullname, 0) == 1) {
> free(*advance_name);
> *advance_name = fullname;
> } else {
> die(_("argument to --advance must be a reference"));
> }
> + *onto = peel_committish(repo, *advance_name);
> if (rinfo.positive_refexprs > 1)
> die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
> }
> strset_clear(&rinfo.negative_refs);
> strset_clear(&rinfo.positive_refs);
> diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
> index 58b37599357..7dea62f064f 100755
> --- a/t/t3650-replay-basics.sh
> +++ b/t/t3650-replay-basics.sh
> @@ -49,10 +49,17 @@ test_expect_success 'setup' '
>
> test_expect_success 'setup bare' '
> git clone --bare . bare
> '
>
> +test_expect_success 'argument to --advance must be a reference' '
> + echo "fatal: argument to --advance must be a reference" >expect &&
> + oid=$(git rev-parse main) &&
> + test_must_fail git replay --advance=$oid topic1..topic2 2>actual &&
> + test_cmp expect actual
> +'
> +
> test_expect_success 'using replay to rebase two branches, one on top of other' '
> git replay --onto main topic1..topic2 >result &&
>
> test_line_count = 1 result &&
>
> --
> 2.52.0.10.g08704017180
Looks good otherwise.
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v2 3/5] replay: die descriptively when invalid commit-ish is given
2025-12-30 15:01 ` [PATCH v2 0/5] " kristofferhaugsbakk
2025-12-30 15:01 ` [PATCH v2 1/5] replay: remove dead code and rearrange kristofferhaugsbakk
2025-12-30 15:01 ` [PATCH v2 2/5] replay: find *onto only after testing for ref name kristofferhaugsbakk
@ 2025-12-30 15:01 ` kristofferhaugsbakk
2025-12-30 22:52 ` Elijah Newren
2025-12-30 15:01 ` [PATCH v2 4/5] replay: die if we cannot parse object kristofferhaugsbakk
` (3 subsequent siblings)
6 siblings, 1 reply; 35+ messages in thread
From: kristofferhaugsbakk @ 2025-12-30 15:01 UTC (permalink / raw)
To: git
Cc: Kristoffer Haugsbakk, christian.couder, newren, Siddharth Asthana,
Phillip Wood
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
Giving an invalid commit-ish to `--onto` makes git-replay(1) fail with:
fatal: Replaying down to root commit is not supported yet!
Going backwards from this point:
1. `onto` is `NULL` from `determine_replay_mode`;
2. that function in turn calls `peel_committish`; and
3. here we return `NULL` if `repo_get_oid` fails.
Let’s die immediately with a descriptive error message instead.
Doing this also provides us with a descriptive error if we “forget” to
provide an argument to `--onto` (but we really do unintentionally):[1]
$ git replay --onto ^main topic1
fatal: '^main' is not a valid commit-ish
Note that the `--advance` case won’t be triggered in practice because
of the “argument to --advance must be a reference” check (see the
previous test, and commit).
† 1: The argument to `--onto` is mandatory and the option parser accepts
both `--onto=<name>` (stuck form) and `--onto name`. The latter
form makes it easy to unintentionally pass something to the option
when you really meant to pass a positional argument.
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Notes (series):
v2:
Let’s use a slightly longer subject line in the commit message so that it
looks more like a full sentence (no dropped/implied words).[1]
Also remove the test for `--advance` which is now wrong because of the
previous commit/patch. And reword the commit message now that only `--onto`
is relevant in practice.
There was also feedback about *where* to give this error:[2]
> How many callers use this function? I am wondering if it is better
> to give a better message at the caller(s), rather than here, where
> we lack context to tell something like "You gave string 'ource' as
> the argument to the '--onto' option, but 'ource' does not name any
> commit" (in other words, "for what our caller is trying to peel
> <name> to a commit").
But I opted to keep the check here by using the new `mode` parameter to
provide the context; it is either `--onto` or `--advance`.
Also remove the “not supported yet” now that `*onto` cannot be `NULL` at
this point. I wasn’t confident enough to pull the trigger on that in the
first round. But after Elijah’s comment[3] I feel like I understand the code
well enough.
Also change the test to use printf since it’s only one line. That will be
in line with the later commits/patches here.
🔗 1: https://lore.kernel.org/git/xmqqecolrip7.fsf@gitster.g/
🔗 2: https://lore.kernel.org/git/xmqqikdxriw3.fsf@gitster.g/
🔗 3: https://lore.kernel.org/git/CABPp-BEcJqjD4ztsZo2FTZgWT5ZOADKYEyiZtda+d0mSd1quPQ@mail.gmail.com/
builtin/replay.c | 15 +++++++--------
t/t3650-replay-basics.sh | 7 +++++++
2 files changed, 14 insertions(+), 8 deletions(-)
diff --git a/builtin/replay.c b/builtin/replay.c
index 35813140e99..07a6767ade1 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -25,17 +25,19 @@ static const char *short_commit_name(struct repository *repo,
{
return repo_find_unique_abbrev(repo, &commit->object.oid,
DEFAULT_ABBREV);
}
-static struct commit *peel_committish(struct repository *repo, const char *name)
+static struct commit *peel_committish(struct repository *repo,
+ const char *name,
+ const char *mode)
{
struct object *obj;
struct object_id oid;
if (repo_get_oid(repo, name, &oid))
- return NULL;
+ die(_("'%s' is not a valid commit-ish for %s"), name, mode);
obj = parse_object(repo, &oid);
return (struct commit *)repo_peel_to_type(repo, name, 0, obj,
OBJ_COMMIT);
}
@@ -170,11 +172,11 @@ static void populate_for_onto_or_advance_mode(struct repository *repo,
die(_("need some commits to replay"));
die_for_incompatible_opt2(!!onto_name, "--onto",
!!*advance_name, "--advance");
if (onto_name) {
- *onto = peel_committish(repo, 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;
@@ -191,11 +193,11 @@ static void populate_for_onto_or_advance_mode(struct repository *repo,
free(*advance_name);
*advance_name = fullname;
} else {
die(_("argument to --advance must be a reference"));
}
- *onto = peel_committish(repo, *advance_name);
+ *onto = peel_committish(repo, *advance_name, "--advance");
if (rinfo.positive_refexprs > 1)
die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
}
strset_clear(&rinfo.negative_refs);
strset_clear(&rinfo.positive_refs);
@@ -349,13 +351,10 @@ int cmd_replay(int argc,
populate_for_onto_or_advance_mode(repo, &revs.cmdline,
onto_name, &advance_name,
&onto, &update_refs);
- if (!onto) /* FIXME: Should handle replaying down to root commit */
- die("Replaying down to root commit is not supported yet!");
-
if (prepare_revision_walk(&revs) < 0) {
ret = error(_("error preparing revisions"));
goto cleanup;
}
@@ -367,11 +366,11 @@ int cmd_replay(int argc,
while ((commit = get_revision(&revs))) {
const struct name_decoration *decoration;
khint_t pos;
int hr;
- if (!commit->parents)
+ if (!commit->parents) /* FIXME: Should handle replaying down to root commit */
die(_("replaying down to root commit is not supported yet!"));
if (commit->parents->next)
die(_("replaying merge commits is not supported yet!"));
last_commit = pick_regular_commit(repo, commit, replayed_commits,
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index 7dea62f064f..d4399aa1662 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -56,10 +56,17 @@ test_expect_success 'argument to --advance must be a reference' '
oid=$(git rev-parse main) &&
test_must_fail git replay --advance=$oid topic1..topic2 2>actual &&
test_cmp expect actual
'
+test_expect_success '--onto with invalid commit-ish' '
+ printf "fatal: ${SQ}refs/not-valid${SQ} is not " >expect &&
+ printf "a valid commit-ish for --onto\n" >>expect &&
+ test_must_fail git replay --onto=refs/not-valid topic1..topic2 2>actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'using replay to rebase two branches, one on top of other' '
git replay --onto main topic1..topic2 >result &&
test_line_count = 1 result &&
--
2.52.0.10.g08704017180
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v2 3/5] replay: die descriptively when invalid commit-ish is given
2025-12-30 15:01 ` [PATCH v2 3/5] replay: die descriptively when invalid commit-ish is given kristofferhaugsbakk
@ 2025-12-30 22:52 ` Elijah Newren
2026-01-02 11:11 ` Kristoffer Haugsbakk
0 siblings, 1 reply; 35+ messages in thread
From: Elijah Newren @ 2025-12-30 22:52 UTC (permalink / raw)
To: kristofferhaugsbakk
Cc: git, Kristoffer Haugsbakk, christian.couder, Siddharth Asthana,
Phillip Wood
On Tue, Dec 30, 2025 at 7:03 AM <kristofferhaugsbakk@fastmail.com> wrote:
>
> From: Kristoffer Haugsbakk <code@khaugsbakk.name>
>
> Giving an invalid commit-ish to `--onto` makes git-replay(1) fail with:
>
> fatal: Replaying down to root commit is not supported yet!
>
> Going backwards from this point:
>
> 1. `onto` is `NULL` from `determine_replay_mode`;
`determine_replay_mode` no longer exists due to your new patch 1.
> 2. that function in turn calls `peel_committish`; and
> 3. here we return `NULL` if `repo_get_oid` fails.
>
> Let’s die immediately with a descriptive error message instead.
>
> Doing this also provides us with a descriptive error if we “forget” to
> provide an argument to `--onto` (but we really do unintentionally):[1]
>
> $ git replay --onto ^main topic1
> fatal: '^main' is not a valid commit-ish
>
> Note that the `--advance` case won’t be triggered in practice because
> of the “argument to --advance must be a reference” check (see the
> previous test, and commit).
>
> † 1: The argument to `--onto` is mandatory and the option parser accepts
> both `--onto=<name>` (stuck form) and `--onto name`. The latter
> form makes it easy to unintentionally pass something to the option
> when you really meant to pass a positional argument.
>
> Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
> ---
>
> Notes (series):
> v2:
>
> Let’s use a slightly longer subject line in the commit message so that it
> looks more like a full sentence (no dropped/implied words).[1]
>
> Also remove the test for `--advance` which is now wrong because of the
> previous commit/patch. And reword the commit message now that only `--onto`
> is relevant in practice.
>
> There was also feedback about *where* to give this error:[2]
>
> > How many callers use this function? I am wondering if it is better
> > to give a better message at the caller(s), rather than here, where
> > we lack context to tell something like "You gave string 'ource' as
> > the argument to the '--onto' option, but 'ource' does not name any
> > commit" (in other words, "for what our caller is trying to peel
> > <name> to a commit").
>
> But I opted to keep the check here by using the new `mode` parameter to
> provide the context; it is either `--onto` or `--advance`.
>
> Also remove the “not supported yet” now that `*onto` cannot be `NULL` at
> this point. I wasn’t confident enough to pull the trigger on that in the
> first round. But after Elijah’s comment[3] I feel like I understand the code
> well enough.
>
> Also change the test to use printf since it’s only one line. That will be
> in line with the later commits/patches here.
>
> 🔗 1: https://lore.kernel.org/git/xmqqecolrip7.fsf@gitster.g/
> 🔗 2: https://lore.kernel.org/git/xmqqikdxriw3.fsf@gitster.g/
> 🔗 3: https://lore.kernel.org/git/CABPp-BEcJqjD4ztsZo2FTZgWT5ZOADKYEyiZtda+d0mSd1quPQ@mail.gmail.com/
>
> builtin/replay.c | 15 +++++++--------
> t/t3650-replay-basics.sh | 7 +++++++
> 2 files changed, 14 insertions(+), 8 deletions(-)
>
> diff --git a/builtin/replay.c b/builtin/replay.c
> index 35813140e99..07a6767ade1 100644
> --- a/builtin/replay.c
> +++ b/builtin/replay.c
> @@ -25,17 +25,19 @@ static const char *short_commit_name(struct repository *repo,
> {
> return repo_find_unique_abbrev(repo, &commit->object.oid,
> DEFAULT_ABBREV);
> }
>
> -static struct commit *peel_committish(struct repository *repo, const char *name)
> +static struct commit *peel_committish(struct repository *repo,
> + const char *name,
> + const char *mode)
> {
> struct object *obj;
> struct object_id oid;
>
> if (repo_get_oid(repo, name, &oid))
> - return NULL;
> + die(_("'%s' is not a valid commit-ish for %s"), name, mode);
> obj = parse_object(repo, &oid);
> return (struct commit *)repo_peel_to_type(repo, name, 0, obj,
> OBJ_COMMIT);
> }
>
> @@ -170,11 +172,11 @@ static void populate_for_onto_or_advance_mode(struct repository *repo,
> die(_("need some commits to replay"));
>
> die_for_incompatible_opt2(!!onto_name, "--onto",
> !!*advance_name, "--advance");
> if (onto_name) {
> - *onto = peel_committish(repo, 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;
> @@ -191,11 +193,11 @@ static void populate_for_onto_or_advance_mode(struct repository *repo,
> free(*advance_name);
> *advance_name = fullname;
> } else {
> die(_("argument to --advance must be a reference"));
> }
> - *onto = peel_committish(repo, *advance_name);
> + *onto = peel_committish(repo, *advance_name, "--advance");
> if (rinfo.positive_refexprs > 1)
> die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
> }
> strset_clear(&rinfo.negative_refs);
> strset_clear(&rinfo.positive_refs);
> @@ -349,13 +351,10 @@ int cmd_replay(int argc,
>
> populate_for_onto_or_advance_mode(repo, &revs.cmdline,
> onto_name, &advance_name,
> &onto, &update_refs);
>
> - if (!onto) /* FIXME: Should handle replaying down to root commit */
> - die("Replaying down to root commit is not supported yet!");
> -
Removing the `if` makes sense given the current code, but I wonder if
we should keep a corrected FIXME here:
/* FIXME: Should allow replaying commits with the first as a root commit */
This is out-of-scope for this series, but behind that FIXME...
I'm guessing the user would specify to cherry-pick onto NULL via something like
git replay --root A..B
which would translate into making `onto` be NULL, and mean that the
first commit after A would be a root commit.
Similarly the user could be allowed to do something like
git replay --advance new-empty-branch A..B
where new-empty-branch doesn't yet point to a commit, this would also
result in `onto` being NULL, and start new-empty-branch by
cherry-picking some commits into it.
> if (prepare_revision_walk(&revs) < 0) {
> ret = error(_("error preparing revisions"));
> goto cleanup;
> }
>
> @@ -367,11 +366,11 @@ int cmd_replay(int argc,
> while ((commit = get_revision(&revs))) {
> const struct name_decoration *decoration;
> khint_t pos;
> int hr;
>
> - if (!commit->parents)
> + if (!commit->parents) /* FIXME: Should handle replaying down to root commit */
> die(_("replaying down to root commit is not supported yet!"));
I wonder if I should have written s/to/from/ here ?
> if (commit->parents->next)
> die(_("replaying merge commits is not supported yet!"));
>
> last_commit = pick_regular_commit(repo, commit, replayed_commits,
> diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
> index 7dea62f064f..d4399aa1662 100755
> --- a/t/t3650-replay-basics.sh
> +++ b/t/t3650-replay-basics.sh
> @@ -56,10 +56,17 @@ test_expect_success 'argument to --advance must be a reference' '
> oid=$(git rev-parse main) &&
> test_must_fail git replay --advance=$oid topic1..topic2 2>actual &&
> test_cmp expect actual
> '
>
> +test_expect_success '--onto with invalid commit-ish' '
> + printf "fatal: ${SQ}refs/not-valid${SQ} is not " >expect &&
> + printf "a valid commit-ish for --onto\n" >>expect &&
> + test_must_fail git replay --onto=refs/not-valid topic1..topic2 2>actual &&
> + test_cmp expect actual
> +'
> +
> test_expect_success 'using replay to rebase two branches, one on top of other' '
> git replay --onto main topic1..topic2 >result &&
>
> test_line_count = 1 result &&
>
> --
> 2.52.0.10.g08704017180
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [PATCH v2 3/5] replay: die descriptively when invalid commit-ish is given
2025-12-30 22:52 ` Elijah Newren
@ 2026-01-02 11:11 ` Kristoffer Haugsbakk
0 siblings, 0 replies; 35+ messages in thread
From: Kristoffer Haugsbakk @ 2026-01-02 11:11 UTC (permalink / raw)
To: Elijah Newren, Kristoffer Haugsbakk
Cc: git, Christian Couder, Siddharth Asthana, Phillip Wood
On Tue, Dec 30, 2025, at 23:52, Elijah Newren wrote:
>>[snip]
>> @@ -349,13 +351,10 @@ int cmd_replay(int argc,
>>
>> populate_for_onto_or_advance_mode(repo, &revs.cmdline,
>> onto_name, &advance_name,
>> &onto, &update_refs);
>>
>> - if (!onto) /* FIXME: Should handle replaying down to root commit */
>> - die("Replaying down to root commit is not supported yet!");
>> -
>
> Removing the `if` makes sense given the current code, but I wonder if
> we should keep a corrected FIXME here:
> /* FIXME: Should allow replaying commits with the first as a root commit */
Okay, I will change to keeping this updated comment at this line but
remove the if-block. And I will remove the moved comment:
if (!commit->parents) /* FIXME: Should handle replaying down to root commit */
die(_("replaying down to root commit is not supported yet!"));
Specifically I will remove the if-block on this patch/commit and make
another patch for both renaming the comment and the “replaying down”
die-statement.
>
> This is out-of-scope for this series, but behind that FIXME...
>
> I'm guessing the user would specify to cherry-pick onto NULL via something like
> git replay --root A..B
> which would translate into making `onto` be NULL, and mean that the
> first commit after A would be a root commit.
>
> Similarly the user could be allowed to do something like
> git replay --advance new-empty-branch A..B
> where new-empty-branch doesn't yet point to a commit, this would also
> result in `onto` being NULL, and start new-empty-branch by
> cherry-picking some commits into it.
Okay. With options from git-rev-list(1) like `--root` this mode makes sense.
>
>> if (prepare_revision_walk(&revs) < 0) {
>> ret = error(_("error preparing revisions"));
>> goto cleanup;
>> }
>>
>>
>> @@ -367,11 +366,11 @@ int cmd_replay(int argc,
>> while ((commit = get_revision(&revs))) {
>> const struct name_decoration *decoration;
>> khint_t pos;
>> int hr;
>>
>> - if (!commit->parents)
>> + if (!commit->parents) /* FIXME: Should handle replaying down to root commit */
>> die(_("replaying down to root commit is not supported yet!"));
>
> I wonder if I should have written s/to/from/ here ?
“replaying down from”? Not “replaying from”?
>
>
>>[snip]
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v2 4/5] replay: die if we cannot parse object
2025-12-30 15:01 ` [PATCH v2 0/5] " kristofferhaugsbakk
` (2 preceding siblings ...)
2025-12-30 15:01 ` [PATCH v2 3/5] replay: die descriptively when invalid commit-ish is given kristofferhaugsbakk
@ 2025-12-30 15:01 ` kristofferhaugsbakk
2025-12-30 15:01 ` [PATCH v2 5/5] t3650: add more regression tests for failure conditions kristofferhaugsbakk
` (2 subsequent siblings)
6 siblings, 0 replies; 35+ messages in thread
From: kristofferhaugsbakk @ 2025-12-30 15:01 UTC (permalink / raw)
To: git
Cc: Kristoffer Haugsbakk, christian.couder, newren, Siddharth Asthana,
Phillip Wood, Junio C Hamano
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
`parse_object` can return `NULL`. That will in turn make
`repo_peel_to_type` return the same.
Let’s die fast and descriptively with the `*_or_die` variant.
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Notes (series):
v2: [new]
See: https://lore.kernel.org/git/xmqqikdxriw3.fsf@gitster.g/
With the `*_or_die` function we don’t have to check it at the call site.
builtin/replay.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/builtin/replay.c b/builtin/replay.c
index 07a6767ade1..ca5a14de4c7 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -34,11 +34,11 @@ static struct commit *peel_committish(struct repository *repo,
struct object *obj;
struct object_id oid;
if (repo_get_oid(repo, name, &oid))
die(_("'%s' is not a valid commit-ish for %s"), name, mode);
- obj = parse_object(repo, &oid);
+ obj = parse_object_or_die(repo, &oid, name);
return (struct commit *)repo_peel_to_type(repo, name, 0, obj,
OBJ_COMMIT);
}
static char *get_author(const char *message)
--
2.52.0.10.g08704017180
^ permalink raw reply related [flat|nested] 35+ messages in thread* [PATCH v2 5/5] t3650: add more regression tests for failure conditions
2025-12-30 15:01 ` [PATCH v2 0/5] " kristofferhaugsbakk
` (3 preceding siblings ...)
2025-12-30 15:01 ` [PATCH v2 4/5] replay: die if we cannot parse object kristofferhaugsbakk
@ 2025-12-30 15:01 ` kristofferhaugsbakk
2025-12-30 22:53 ` [PATCH v2 0/5] replay: die descriptively when invalid commit-ish Elijah Newren
2026-01-05 19:53 ` [PATCH v3 0/6] " kristofferhaugsbakk
6 siblings, 0 replies; 35+ messages in thread
From: kristofferhaugsbakk @ 2025-12-30 15:01 UTC (permalink / raw)
To: git
Cc: Kristoffer Haugsbakk, christian.couder, newren, Siddharth Asthana,
Phillip Wood
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
There isn’t much test coverage for basic failure conditions. Let’s add
a few more since these are simple to write and remove if they become
obsolete.
Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Notes (series):
v2:
Improve test `option --onto or --advance is mandatory`. Phillip pointed out
that using a pipe loses the return value. Instead let’s test the whole
output by just appending `git replay -h` to `expect`.
Also “normalize” to just using echo/printf for the `expect` since these are
just oneliner errors.
Also add two more tests (at the end).
t/t3650-replay-basics.sh | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index d4399aa1662..c0c59ae6938 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -41,10 +41,17 @@ test_expect_success 'setup' '
git switch main &&
test_commit L &&
test_commit M &&
+ git switch --detach topic4 &&
+ test_commit N &&
+ test_commit O &&
+ git switch -c topic-with-merge topic4 &&
+ test_merge P O --no-ff &&
+ git switch main &&
+
git switch -c conflict B &&
test_commit C.conflict C.t conflict
'
test_expect_success 'setup bare' '
@@ -63,10 +70,43 @@ test_expect_success '--onto with invalid commit-ish' '
printf "a valid commit-ish for --onto\n" >>expect &&
test_must_fail git replay --onto=refs/not-valid topic1..topic2 2>actual &&
test_cmp expect actual
'
+test_expect_success 'option --onto or --advance is mandatory' '
+ echo "error: option --onto or --advance is mandatory" >expect &&
+ test_might_fail git replay -h >>expect &&
+ test_must_fail git replay topic1..topic2 2>actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'no base or negative ref gives no-replaying down to root error' '
+ echo "fatal: replaying down to root commit is not supported yet!" >expect &&
+ test_must_fail git replay --onto=topic1 topic2 2>actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'options --advance and --contained cannot be used together' '
+ printf "fatal: options ${SQ}--advance${SQ} " >expect &&
+ printf "and ${SQ}--contained${SQ} cannot be used together\n" >>expect &&
+ test_must_fail git replay --advance=main --contained \
+ topic1..topic2 2>actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'cannot advance target ... ordering would be ill-defined' '
+ echo "fatal: cannot advance target with multiple sources because ordering would be ill-defined" >expect &&
+ test_must_fail git replay --advance=main main topic1 topic2 2>actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'replaying merge commits is not supported yet' '
+ echo "fatal: replaying merge commits is not supported yet!" >expect &&
+ test_must_fail git replay --advance=main main..topic-with-merge 2>actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'using replay to rebase two branches, one on top of other' '
git replay --onto main topic1..topic2 >result &&
test_line_count = 1 result &&
--
2.52.0.10.g08704017180
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v2 0/5] replay: die descriptively when invalid commit-ish
2025-12-30 15:01 ` [PATCH v2 0/5] " kristofferhaugsbakk
` (4 preceding siblings ...)
2025-12-30 15:01 ` [PATCH v2 5/5] t3650: add more regression tests for failure conditions kristofferhaugsbakk
@ 2025-12-30 22:53 ` Elijah Newren
2026-01-05 19:53 ` [PATCH v3 0/6] " kristofferhaugsbakk
6 siblings, 0 replies; 35+ messages in thread
From: Elijah Newren @ 2025-12-30 22:53 UTC (permalink / raw)
To: kristofferhaugsbakk
Cc: git, Kristoffer Haugsbakk, christian.couder, Siddharth Asthana,
Phillip Wood
On Tue, Dec 30, 2025 at 7:02 AM <kristofferhaugsbakk@fastmail.com> wrote:
>
[...]
> § Changes in v2
>
> In this version we remove some dead code (see previous section), improve
> tests and add a couple more, and improve the main “die descriptively”
> part, with two additional patches/commits to that end:
>
> • replay: find *onto only after testing for ref name
> • replay: die if we cannot parse object
>
> See the notes on each patch/commit for change details.
I had a few minor comments on a few of the patches, but otherwise it
looked good to me.
^ permalink raw reply [flat|nested] 35+ messages in thread* [PATCH v3 0/6] replay: die descriptively when invalid commit-ish
2025-12-30 15:01 ` [PATCH v2 0/5] " kristofferhaugsbakk
` (5 preceding siblings ...)
2025-12-30 22:53 ` [PATCH v2 0/5] replay: die descriptively when invalid commit-ish Elijah Newren
@ 2026-01-05 19:53 ` kristofferhaugsbakk
2026-01-05 19:53 ` [PATCH v3 1/6] replay: remove dead code and rearrange kristofferhaugsbakk
` (6 more replies)
6 siblings, 7 replies; 35+ messages in thread
From: kristofferhaugsbakk @ 2026-01-05 19:53 UTC (permalink / raw)
To: git
Cc: Kristoffer Haugsbakk, christian.couder, newren, Siddharth Asthana,
Phillip Wood
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
You get this error when you for example mistype the argument to
`--onto`:
fatal: Replaying down to root commit is not supported yet!
Consider that you might not know yourself that you have mistyped
something; then this looks even more puzzling. You might have given a
range like `main..topic` but the command says that it would need to
replay down to the root commit.
The only thing that’s happened though is that `NULL` has been
interpreted in the wrong way.
Let’s instead die immediately when the real error happens, in other
words when we can’t find the commit for the given commit-ish.
Also:
• Add more regression tests
• Remove dead code
• Slightly more robust object parsing (parse_object_or_die)
§ Dropped section (see v1)
Somewhat unrelated to this change ...
§ Changes in v3
Apply review feedback from Elijah. See patches for details.
• Patch 1: More terse function name
• Patch 2: Improve commit message
• Patch 3: Improve commit message: fix outdated function name mention
• Patch 4: [new] Apply code comment/error message tweaks
§ Link to v2
https://lore.kernel.org/git/V2_CV_replay_die_descr.17b@msgid.xyz/
Kristoffer Haugsbakk (6):
replay: remove dead code and rearrange
replay: find *onto only after testing for ref name
replay: die descriptively when invalid commit-ish is given
replay: improve code comment and die message
replay: die if we cannot parse object
t3650: add more regression tests for failure conditions
builtin/replay.c | 87 ++++++++++++----------------------------
t/t3650-replay-basics.sh | 54 +++++++++++++++++++++++++
2 files changed, 79 insertions(+), 62 deletions(-)
Interdiff against v2:
diff --git a/builtin/replay.c b/builtin/replay.c
index ca5a14de4c7..dc46c921667 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -156,16 +156,16 @@ static void get_ref_information(struct repository *repo,
free(fullname);
}
}
-static void populate_for_onto_or_advance_mode(struct repository *repo,
- struct rev_cmdline_info *cmd_info,
- const char *onto_name,
- char **advance_name,
- struct commit **onto,
- struct strset **update_refs)
+static void set_up_replay_mode(struct repository *repo,
+ struct rev_cmdline_info *cmd_info,
+ const char *onto_name,
+ char **advance_name,
+ struct commit **onto,
+ struct strset **update_refs)
{
struct ref_info rinfo;
get_ref_information(repo, cmd_info, &rinfo);
if (!rinfo.positive_refexprs)
@@ -347,13 +347,15 @@ int cmd_replay(int argc,
"'%s' bit in 'struct rev_info' will be forced"),
"simplify_history");
revs.simplify_history = 0;
}
- populate_for_onto_or_advance_mode(repo, &revs.cmdline,
- onto_name, &advance_name,
- &onto, &update_refs);
+ set_up_replay_mode(repo, &revs.cmdline,
+ onto_name, &advance_name,
+ &onto, &update_refs);
+
+ /* FIXME: Should allow replaying commits with the first as a root commit */
if (prepare_revision_walk(&revs) < 0) {
ret = error(_("error preparing revisions"));
goto cleanup;
}
@@ -366,12 +368,12 @@ int cmd_replay(int argc,
while ((commit = get_revision(&revs))) {
const struct name_decoration *decoration;
khint_t pos;
int hr;
- if (!commit->parents) /* FIXME: Should handle replaying down to root commit */
- die(_("replaying down to root commit is not supported yet!"));
+ if (!commit->parents)
+ die(_("replaying down from root commit is not supported yet!"));
if (commit->parents->next)
die(_("replaying merge commits is not supported yet!"));
last_commit = pick_regular_commit(repo, commit, replayed_commits,
onto, &merge_opt, &result);
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index c0c59ae6938..d10c01506f1 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -78,11 +78,11 @@ test_expect_success 'option --onto or --advance is mandatory' '
test_must_fail git replay topic1..topic2 2>actual &&
test_cmp expect actual
'
test_expect_success 'no base or negative ref gives no-replaying down to root error' '
- echo "fatal: replaying down to root commit is not supported yet!" >expect &&
+ echo "fatal: replaying down from root commit is not supported yet!" >expect &&
test_must_fail git replay --onto=topic1 topic2 2>actual &&
test_cmp expect actual
'
test_expect_success 'options --advance and --contained cannot be used together' '
Range-diff against v2:
1: 314ba49dd2f ! 1: 6d785c6b45f replay: remove dead code and rearrange
@@ Commit message
*advance_name = xstrdup_or_null(last_key);
- Let’s also rename the function since we do not determine
- the replay mode here. We simply populate data structures.
+ Let’s also rename the function since we do not determine the
+ replay mode here. We just set up `*onto` and refs to update.
Note that there might be more dead code caused by this *guess mode*.
We only concern ourselves with this function for now.
@@ Commit message
## Notes (series) ##
+ v3:
+
+ Use more terse function rename.[1] Also tweak commit message based on
+ this change.
+
+ 🔗 1: https://lore.kernel.org/git/CABPp-BEJV1XG62_hn_OiZ9q9S3jsyTP0VdOEzS4pME2rrkKFrg@mail.gmail.com/
+
v2: [new]
See the link in the commit message.
@@ builtin/replay.c: static void get_ref_information(struct repository *repo,
- char **advance_name,
- struct commit **onto,
- struct strset **update_refs)
-+static void populate_for_onto_or_advance_mode(struct repository *repo,
-+ struct rev_cmdline_info *cmd_info,
-+ const char *onto_name,
-+ char **advance_name,
-+ struct commit **onto,
-+ struct strset **update_refs)
++static void set_up_replay_mode(struct repository *repo,
++ struct rev_cmdline_info *cmd_info,
++ const char *onto_name,
++ char **advance_name,
++ struct commit **onto,
++ struct strset **update_refs)
{
struct ref_info rinfo;
@@ builtin/replay.c: int cmd_replay(int argc,
- determine_replay_mode(repo, &revs.cmdline, onto_name, &advance_name,
- &onto, &update_refs);
-+ populate_for_onto_or_advance_mode(repo, &revs.cmdline,
-+ onto_name, &advance_name,
-+ &onto, &update_refs);
++ set_up_replay_mode(repo, &revs.cmdline,
++ onto_name, &advance_name,
++ &onto, &update_refs);
if (!onto) /* FIXME: Should handle replaying down to root commit */
die("Replaying down to root commit is not supported yet!");
2: 976d336adef ! 2: 7f9aac28792 replay: find *onto only after testing for ref name
@@ Commit message
Let’s try to find the ref and only after that try to peel to
as a commit-ish.
- Also add a regression test to protect this error-order from future
+ Also add a regression test to protect this error order from future
modifications.
Suggested-by: Junio C Hamano <gitster@pobox.com>
@@ Commit message
## Notes (series) ##
+ v3:
+
+ Don’t use a hyphen in “error-order” since that can be confusing.[1]
+
+ 🔗 1: https://lore.kernel.org/git/CABPp-BE13K1QB42YLv3mLzB9+jUgkMtHNmbs_XWoTsbv2zSYog@mail.gmail.com/
+
v2: [new]
Fallout of v1. Needs to be moved so that the new error message does not
@@ Notes (series)
See: https://lore.kernel.org/git/xmqqpl85pb7k.fsf@gitster.g/
## builtin/replay.c ##
-@@ builtin/replay.c: static void populate_for_onto_or_advance_mode(struct repository *repo,
+@@ builtin/replay.c: static void set_up_replay_mode(struct repository *repo,
if (!*advance_name)
BUG("expected either onto_name or *advance_name in this function");
@@ builtin/replay.c: static void populate_for_onto_or_advance_mode(struct repositor
if (repo_dwim_ref(repo, *advance_name, strlen(*advance_name),
&oid, &fullname, 0) == 1) {
free(*advance_name);
-@@ builtin/replay.c: static void populate_for_onto_or_advance_mode(struct repository *repo,
+@@ builtin/replay.c: static void set_up_replay_mode(struct repository *repo,
} else {
die(_("argument to --advance must be a reference"));
}
3: a7a7e1e720e ! 3: 88544dcad3e replay: die descriptively when invalid commit-ish is given
@@ Commit message
Going backwards from this point:
- 1. `onto` is `NULL` from `determine_replay_mode`;
+ 1. `onto` is `NULL` from `set_up_replay_mode`;
2. that function in turn calls `peel_committish`; and
3. here we return `NULL` if `repo_get_oid` fails.
@@ Commit message
## Notes (series) ##
+ v3:
+
+ Update commit message since it uses the outdated function name.[1]
+
+ 🔗 1: https://lore.kernel.org/git/CABPp-BH1b3rHi96qXLQwQRX6g7POmqYLKyAc=_1UsWmfiWsGFg@mail.gmail.com/#t
+
v2:
Let’s use a slightly longer subject line in the commit message so that it
@@ builtin/replay.c: static const char *short_commit_name(struct repository *repo,
obj = parse_object(repo, &oid);
return (struct commit *)repo_peel_to_type(repo, name, 0, obj,
OBJ_COMMIT);
-@@ builtin/replay.c: static void populate_for_onto_or_advance_mode(struct repository *repo,
+@@ builtin/replay.c: static void set_up_replay_mode(struct repository *repo,
die_for_incompatible_opt2(!!onto_name, "--onto",
!!*advance_name, "--advance");
if (onto_name) {
@@ builtin/replay.c: static void populate_for_onto_or_advance_mode(struct repositor
if (rinfo.positive_refexprs <
strset_get_size(&rinfo.positive_refs))
die(_("all positive revisions given must be references"));
-@@ builtin/replay.c: static void populate_for_onto_or_advance_mode(struct repository *repo,
+@@ builtin/replay.c: static void set_up_replay_mode(struct repository *repo,
} else {
die(_("argument to --advance must be a reference"));
}
@@ builtin/replay.c: static void populate_for_onto_or_advance_mode(struct repositor
die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
}
@@ builtin/replay.c: int cmd_replay(int argc,
- onto_name, &advance_name,
- &onto, &update_refs);
+ onto_name, &advance_name,
+ &onto, &update_refs);
- if (!onto) /* FIXME: Should handle replaying down to root commit */
- die("Replaying down to root commit is not supported yet!");
--
++ /* FIXME: Should handle replaying down to root commit */
+
if (prepare_revision_walk(&revs) < 0) {
ret = error(_("error preparing revisions"));
- goto cleanup;
-@@ builtin/replay.c: int cmd_replay(int argc,
- khint_t pos;
- int hr;
-
-- if (!commit->parents)
-+ if (!commit->parents) /* FIXME: Should handle replaying down to root commit */
- die(_("replaying down to root commit is not supported yet!"));
- if (commit->parents->next)
- die(_("replaying merge commits is not supported yet!"));
## t/t3650-replay-basics.sh ##
@@ t/t3650-replay-basics.sh: test_expect_success 'argument to --advance must be a reference' '
-: ----------- > 4: 2e149c41634 replay: improve code comment and die message
4: 9700630d95e = 5: 35cce78b469 replay: die if we cannot parse object
5: 5e1b9205df5 ! 6: cd87a11f96b t3650: add more regression tests for failure conditions
@@ t/t3650-replay-basics.sh: test_expect_success '--onto with invalid commit-ish' '
+'
+
+test_expect_success 'no base or negative ref gives no-replaying down to root error' '
-+ echo "fatal: replaying down to root commit is not supported yet!" >expect &&
++ echo "fatal: replaying down from root commit is not supported yet!" >expect &&
+ test_must_fail git replay --onto=topic1 topic2 2>actual &&
+ test_cmp expect actual
+'
base-commit: 9a2fb147f2c61d0cab52c883e7e26f5b7948e3ed
--
2.52.0.383.gb1c58d6b301
^ permalink raw reply related [flat|nested] 35+ messages in thread* [PATCH v3 1/6] replay: remove dead code and rearrange
2026-01-05 19:53 ` [PATCH v3 0/6] " kristofferhaugsbakk
@ 2026-01-05 19:53 ` kristofferhaugsbakk
2026-01-05 19:53 ` [PATCH v3 2/6] replay: find *onto only after testing for ref name kristofferhaugsbakk
` (5 subsequent siblings)
6 siblings, 0 replies; 35+ messages in thread
From: kristofferhaugsbakk @ 2026-01-05 19:53 UTC (permalink / raw)
To: git
Cc: Kristoffer Haugsbakk, christian.couder, newren, Siddharth Asthana,
Phillip Wood
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
22d99f01 (replay: add --advance or 'cherry-pick' mode, 2023-11-24) both
added `--advance` and made one of `--onto` or `--advance` mandatory.
But `determine_replay_mode` claims that there is a third alternative;
neither of `--onto` or `--advance` were given:
if (onto_name) {
...
} else if (*advance_name) {
...
} else {
...
}
But this is false—the fallthrough else-block is dead code.
Commit 22d99f01 was iterated upon by several people.[1] The initial
author wrote code for a sort of *guess mode*, allowing for shorter
commands when that was possible. But the next person instead made one
of the aforementioned options mandatory. In turn this code was dead on
arrival in git.git.
[1]: https://lore.kernel.org/git/CABPp-BEcJqjD4ztsZo2FTZgWT5ZOADKYEyiZtda+d0mSd1quPQ@mail.gmail.com/
Let’s remove this code. We can also join the if-block with the
condition `!*advance_name` into the `*onto` block since we do not set
`*advance_name` in this function. It only looked like we might set it
since the dead code has this line:
*advance_name = xstrdup_or_null(last_key);
Let’s also rename the function since we do not determine the
replay mode here. We just set up `*onto` and refs to update.
Note that there might be more dead code caused by this *guess mode*.
We only concern ourselves with this function for now.
Helped-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Notes (series):
v3:
Use more terse function rename.[1] Also tweak commit message based on
this change.
🔗 1: https://lore.kernel.org/git/CABPp-BEJV1XG62_hn_OiZ9q9S3jsyTP0VdOEzS4pME2rrkKFrg@mail.gmail.com/
v2: [new]
See the link in the commit message.
Not strictly needed for this series but I think it makes sense to fix it
here.
builtin/replay.c | 70 +++++++++++-------------------------------------
1 file changed, 16 insertions(+), 54 deletions(-)
diff --git a/builtin/replay.c b/builtin/replay.c
index 6172c8aacc9..6e0fedf1061 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -154,16 +154,16 @@ static void get_ref_information(struct repository *repo,
free(fullname);
}
}
-static void determine_replay_mode(struct repository *repo,
- struct rev_cmdline_info *cmd_info,
- const char *onto_name,
- char **advance_name,
- struct commit **onto,
- struct strset **update_refs)
+static void set_up_replay_mode(struct repository *repo,
+ struct rev_cmdline_info *cmd_info,
+ const char *onto_name,
+ char **advance_name,
+ struct commit **onto,
+ struct strset **update_refs)
{
struct ref_info rinfo;
get_ref_information(repo, cmd_info, &rinfo);
if (!rinfo.positive_refexprs)
@@ -174,69 +174,30 @@ static void determine_replay_mode(struct repository *repo,
if (onto_name) {
*onto = peel_committish(repo, onto_name);
if (rinfo.positive_refexprs <
strset_get_size(&rinfo.positive_refs))
die(_("all positive revisions given must be references"));
- } else if (*advance_name) {
+ *update_refs = xcalloc(1, sizeof(**update_refs));
+ **update_refs = rinfo.positive_refs;
+ memset(&rinfo.positive_refs, 0, sizeof(**update_refs));
+ } else {
struct object_id oid;
char *fullname = NULL;
+ if (!*advance_name)
+ BUG("expected either onto_name or *advance_name in this function");
+
*onto = peel_committish(repo, *advance_name);
if (repo_dwim_ref(repo, *advance_name, strlen(*advance_name),
&oid, &fullname, 0) == 1) {
free(*advance_name);
*advance_name = fullname;
} else {
die(_("argument to --advance must be a reference"));
}
if (rinfo.positive_refexprs > 1)
die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
- } else {
- int positive_refs_complete = (
- rinfo.positive_refexprs ==
- strset_get_size(&rinfo.positive_refs));
- int negative_refs_complete = (
- rinfo.negative_refexprs ==
- strset_get_size(&rinfo.negative_refs));
- /*
- * We need either positive_refs_complete or
- * negative_refs_complete, but not both.
- */
- if (rinfo.negative_refexprs > 0 &&
- positive_refs_complete == negative_refs_complete)
- die(_("cannot implicitly determine whether this is an --advance or --onto operation"));
- if (negative_refs_complete) {
- struct hashmap_iter iter;
- struct strmap_entry *entry;
- const char *last_key = NULL;
-
- if (rinfo.negative_refexprs == 0)
- die(_("all positive revisions given must be references"));
- else if (rinfo.negative_refexprs > 1)
- die(_("cannot implicitly determine whether this is an --advance or --onto operation"));
- else if (rinfo.positive_refexprs > 1)
- die(_("cannot advance target with multiple source branches because ordering would be ill-defined"));
-
- /* Only one entry, but we have to loop to get it */
- strset_for_each_entry(&rinfo.negative_refs,
- &iter, entry) {
- last_key = entry->key;
- }
-
- free(*advance_name);
- *advance_name = xstrdup_or_null(last_key);
- } else { /* positive_refs_complete */
- if (rinfo.negative_refexprs > 1)
- die(_("cannot implicitly determine correct base for --onto"));
- if (rinfo.negative_refexprs == 1)
- *onto = rinfo.onto;
- }
- }
- if (!*advance_name) {
- *update_refs = xcalloc(1, sizeof(**update_refs));
- **update_refs = rinfo.positive_refs;
- memset(&rinfo.positive_refs, 0, sizeof(**update_refs));
}
strset_clear(&rinfo.negative_refs);
strset_clear(&rinfo.positive_refs);
}
@@ -384,12 +345,13 @@ int cmd_replay(int argc,
"'%s' bit in 'struct rev_info' will be forced"),
"simplify_history");
revs.simplify_history = 0;
}
- determine_replay_mode(repo, &revs.cmdline, onto_name, &advance_name,
- &onto, &update_refs);
+ set_up_replay_mode(repo, &revs.cmdline,
+ onto_name, &advance_name,
+ &onto, &update_refs);
if (!onto) /* FIXME: Should handle replaying down to root commit */
die("Replaying down to root commit is not supported yet!");
if (prepare_revision_walk(&revs) < 0) {
--
2.52.0.383.gb1c58d6b301
^ permalink raw reply related [flat|nested] 35+ messages in thread* [PATCH v3 2/6] replay: find *onto only after testing for ref name
2026-01-05 19:53 ` [PATCH v3 0/6] " kristofferhaugsbakk
2026-01-05 19:53 ` [PATCH v3 1/6] replay: remove dead code and rearrange kristofferhaugsbakk
@ 2026-01-05 19:53 ` kristofferhaugsbakk
2026-01-05 19:53 ` [PATCH v3 3/6] replay: die descriptively when invalid commit-ish is given kristofferhaugsbakk
` (4 subsequent siblings)
6 siblings, 0 replies; 35+ messages in thread
From: kristofferhaugsbakk @ 2026-01-05 19:53 UTC (permalink / raw)
To: git
Cc: Kristoffer Haugsbakk, christian.couder, newren, Siddharth Asthana,
Phillip Wood, Junio C Hamano
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
We are about to make `peel_committish` die when it cannot find
a commit-ish instead of returning `NULL`. But that would make e.g.
`git replay --advance=refs/non-existent` die with a less descriptive
error message; the highest-level error message is that the name does
not exist as a ref, not that we cannot find a commit-ish based on
the name.
Let’s try to find the ref and only after that try to peel to
as a commit-ish.
Also add a regression test to protect this error order from future
modifications.
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Notes (series):
v3:
Don’t use a hyphen in “error-order” since that can be confusing.[1]
🔗 1: https://lore.kernel.org/git/CABPp-BE13K1QB42YLv3mLzB9+jUgkMtHNmbs_XWoTsbv2zSYog@mail.gmail.com/
v2: [new]
Fallout of v1. Needs to be moved so that the new error message does not
“shadow” this one.
See: https://lore.kernel.org/git/xmqqpl85pb7k.fsf@gitster.g/
builtin/replay.c | 2 +-
t/t3650-replay-basics.sh | 7 +++++++
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/builtin/replay.c b/builtin/replay.c
index 6e0fedf1061..8c33a15398d 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -184,18 +184,18 @@ static void set_up_replay_mode(struct repository *repo,
char *fullname = NULL;
if (!*advance_name)
BUG("expected either onto_name or *advance_name in this function");
- *onto = peel_committish(repo, *advance_name);
if (repo_dwim_ref(repo, *advance_name, strlen(*advance_name),
&oid, &fullname, 0) == 1) {
free(*advance_name);
*advance_name = fullname;
} else {
die(_("argument to --advance must be a reference"));
}
+ *onto = peel_committish(repo, *advance_name);
if (rinfo.positive_refexprs > 1)
die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
}
strset_clear(&rinfo.negative_refs);
strset_clear(&rinfo.positive_refs);
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index 58b37599357..7dea62f064f 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -49,10 +49,17 @@ test_expect_success 'setup' '
test_expect_success 'setup bare' '
git clone --bare . bare
'
+test_expect_success 'argument to --advance must be a reference' '
+ echo "fatal: argument to --advance must be a reference" >expect &&
+ oid=$(git rev-parse main) &&
+ test_must_fail git replay --advance=$oid topic1..topic2 2>actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'using replay to rebase two branches, one on top of other' '
git replay --onto main topic1..topic2 >result &&
test_line_count = 1 result &&
--
2.52.0.383.gb1c58d6b301
^ permalink raw reply related [flat|nested] 35+ messages in thread* [PATCH v3 3/6] replay: die descriptively when invalid commit-ish is given
2026-01-05 19:53 ` [PATCH v3 0/6] " kristofferhaugsbakk
2026-01-05 19:53 ` [PATCH v3 1/6] replay: remove dead code and rearrange kristofferhaugsbakk
2026-01-05 19:53 ` [PATCH v3 2/6] replay: find *onto only after testing for ref name kristofferhaugsbakk
@ 2026-01-05 19:53 ` kristofferhaugsbakk
2026-01-05 19:53 ` [PATCH v3 4/6] replay: improve code comment and die message kristofferhaugsbakk
` (3 subsequent siblings)
6 siblings, 0 replies; 35+ messages in thread
From: kristofferhaugsbakk @ 2026-01-05 19:53 UTC (permalink / raw)
To: git
Cc: Kristoffer Haugsbakk, christian.couder, newren, Siddharth Asthana,
Phillip Wood
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
Giving an invalid commit-ish to `--onto` makes git-replay(1) fail with:
fatal: Replaying down to root commit is not supported yet!
Going backwards from this point:
1. `onto` is `NULL` from `set_up_replay_mode`;
2. that function in turn calls `peel_committish`; and
3. here we return `NULL` if `repo_get_oid` fails.
Let’s die immediately with a descriptive error message instead.
Doing this also provides us with a descriptive error if we “forget” to
provide an argument to `--onto` (but we really do unintentionally):[1]
$ git replay --onto ^main topic1
fatal: '^main' is not a valid commit-ish
Note that the `--advance` case won’t be triggered in practice because
of the “argument to --advance must be a reference” check (see the
previous test, and commit).
† 1: The argument to `--onto` is mandatory and the option parser accepts
both `--onto=<name>` (stuck form) and `--onto name`. The latter
form makes it easy to unintentionally pass something to the option
when you really meant to pass a positional argument.
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Notes (series):
v3:
Update commit message since it uses the outdated function name.[1]
🔗 1: https://lore.kernel.org/git/CABPp-BH1b3rHi96qXLQwQRX6g7POmqYLKyAc=_1UsWmfiWsGFg@mail.gmail.com/#t
v2:
Let’s use a slightly longer subject line in the commit message so that it
looks more like a full sentence (no dropped/implied words).[1]
Also remove the test for `--advance` which is now wrong because of the
previous commit/patch. And reword the commit message now that only `--onto`
is relevant in practice.
There was also feedback about *where* to give this error:[2]
> How many callers use this function? I am wondering if it is better
> to give a better message at the caller(s), rather than here, where
> we lack context to tell something like "You gave string 'ource' as
> the argument to the '--onto' option, but 'ource' does not name any
> commit" (in other words, "for what our caller is trying to peel
> <name> to a commit").
But I opted to keep the check here by using the new `mode` parameter to
provide the context; it is either `--onto` or `--advance`.
Also remove the “not supported yet” now that `*onto` cannot be `NULL` at
this point. I wasn’t confident enough to pull the trigger on that in the
first round. But after Elijah’s comment[3] I feel like I understand the code
well enough.
Also change the test to use printf since it’s only one line. That will be
in line with the later commits/patches here.
🔗 1: https://lore.kernel.org/git/xmqqecolrip7.fsf@gitster.g/
🔗 2: https://lore.kernel.org/git/xmqqikdxriw3.fsf@gitster.g/
🔗 3: https://lore.kernel.org/git/CABPp-BEcJqjD4ztsZo2FTZgWT5ZOADKYEyiZtda+d0mSd1quPQ@mail.gmail.com/
builtin/replay.c | 13 +++++++------
t/t3650-replay-basics.sh | 7 +++++++
2 files changed, 14 insertions(+), 6 deletions(-)
diff --git a/builtin/replay.c b/builtin/replay.c
index 8c33a15398d..dfb98eb3a9c 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -25,17 +25,19 @@ static const char *short_commit_name(struct repository *repo,
{
return repo_find_unique_abbrev(repo, &commit->object.oid,
DEFAULT_ABBREV);
}
-static struct commit *peel_committish(struct repository *repo, const char *name)
+static struct commit *peel_committish(struct repository *repo,
+ const char *name,
+ const char *mode)
{
struct object *obj;
struct object_id oid;
if (repo_get_oid(repo, name, &oid))
- return NULL;
+ die(_("'%s' is not a valid commit-ish for %s"), name, mode);
obj = parse_object(repo, &oid);
return (struct commit *)repo_peel_to_type(repo, name, 0, obj,
OBJ_COMMIT);
}
@@ -170,11 +172,11 @@ static void set_up_replay_mode(struct repository *repo,
die(_("need some commits to replay"));
die_for_incompatible_opt2(!!onto_name, "--onto",
!!*advance_name, "--advance");
if (onto_name) {
- *onto = peel_committish(repo, 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;
@@ -191,11 +193,11 @@ static void set_up_replay_mode(struct repository *repo,
free(*advance_name);
*advance_name = fullname;
} else {
die(_("argument to --advance must be a reference"));
}
- *onto = peel_committish(repo, *advance_name);
+ *onto = peel_committish(repo, *advance_name, "--advance");
if (rinfo.positive_refexprs > 1)
die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
}
strset_clear(&rinfo.negative_refs);
strset_clear(&rinfo.positive_refs);
@@ -349,12 +351,11 @@ int cmd_replay(int argc,
set_up_replay_mode(repo, &revs.cmdline,
onto_name, &advance_name,
&onto, &update_refs);
- if (!onto) /* FIXME: Should handle replaying down to root commit */
- die("Replaying down to root commit is not supported yet!");
+ /* FIXME: Should handle replaying down to root commit */
if (prepare_revision_walk(&revs) < 0) {
ret = error(_("error preparing revisions"));
goto cleanup;
}
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index 7dea62f064f..d4399aa1662 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -56,10 +56,17 @@ test_expect_success 'argument to --advance must be a reference' '
oid=$(git rev-parse main) &&
test_must_fail git replay --advance=$oid topic1..topic2 2>actual &&
test_cmp expect actual
'
+test_expect_success '--onto with invalid commit-ish' '
+ printf "fatal: ${SQ}refs/not-valid${SQ} is not " >expect &&
+ printf "a valid commit-ish for --onto\n" >>expect &&
+ test_must_fail git replay --onto=refs/not-valid topic1..topic2 2>actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'using replay to rebase two branches, one on top of other' '
git replay --onto main topic1..topic2 >result &&
test_line_count = 1 result &&
--
2.52.0.383.gb1c58d6b301
^ permalink raw reply related [flat|nested] 35+ messages in thread* [PATCH v3 4/6] replay: improve code comment and die message
2026-01-05 19:53 ` [PATCH v3 0/6] " kristofferhaugsbakk
` (2 preceding siblings ...)
2026-01-05 19:53 ` [PATCH v3 3/6] replay: die descriptively when invalid commit-ish is given kristofferhaugsbakk
@ 2026-01-05 19:53 ` kristofferhaugsbakk
2026-01-05 19:53 ` [PATCH v3 5/6] replay: die if we cannot parse object kristofferhaugsbakk
` (2 subsequent siblings)
6 siblings, 0 replies; 35+ messages in thread
From: kristofferhaugsbakk @ 2026-01-05 19:53 UTC (permalink / raw)
To: git
Cc: Kristoffer Haugsbakk, christian.couder, newren, Siddharth Asthana,
Phillip Wood
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
Suggested-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Notes (series):
v3: [new]
https://lore.kernel.org/git/CABPp-BH1b3rHi96qXLQwQRX6g7POmqYLKyAc=_1UsWmfiWsGFg@mail.gmail.com/
| 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
--git a/builtin/replay.c b/builtin/replay.c
index dfb98eb3a9c..3dde20acfef 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -351,11 +351,11 @@ int cmd_replay(int argc,
set_up_replay_mode(repo, &revs.cmdline,
onto_name, &advance_name,
&onto, &update_refs);
- /* FIXME: Should handle replaying down to root commit */
+ /* FIXME: Should allow replaying commits with the first as a root commit */
if (prepare_revision_walk(&revs) < 0) {
ret = error(_("error preparing revisions"));
goto cleanup;
}
@@ -369,11 +369,11 @@ int cmd_replay(int argc,
const struct name_decoration *decoration;
khint_t pos;
int hr;
if (!commit->parents)
- die(_("replaying down to root commit is not supported yet!"));
+ die(_("replaying down from root commit is not supported yet!"));
if (commit->parents->next)
die(_("replaying merge commits is not supported yet!"));
last_commit = pick_regular_commit(repo, commit, replayed_commits,
onto, &merge_opt, &result);
--
2.52.0.383.gb1c58d6b301
^ permalink raw reply related [flat|nested] 35+ messages in thread* [PATCH v3 5/6] replay: die if we cannot parse object
2026-01-05 19:53 ` [PATCH v3 0/6] " kristofferhaugsbakk
` (3 preceding siblings ...)
2026-01-05 19:53 ` [PATCH v3 4/6] replay: improve code comment and die message kristofferhaugsbakk
@ 2026-01-05 19:53 ` kristofferhaugsbakk
2026-01-05 19:53 ` [PATCH v3 6/6] t3650: add more regression tests for failure conditions kristofferhaugsbakk
2026-01-06 23:12 ` [PATCH v3 0/6] replay: die descriptively when invalid commit-ish Elijah Newren
6 siblings, 0 replies; 35+ messages in thread
From: kristofferhaugsbakk @ 2026-01-05 19:53 UTC (permalink / raw)
To: git
Cc: Kristoffer Haugsbakk, christian.couder, newren, Siddharth Asthana,
Phillip Wood, Junio C Hamano
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
`parse_object` can return `NULL`. That will in turn make
`repo_peel_to_type` return the same.
Let’s die fast and descriptively with the `*_or_die` variant.
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Notes (series):
v2: [new]
See: https://lore.kernel.org/git/xmqqikdxriw3.fsf@gitster.g/
With the `*_or_die` function we don’t have to check it at the call site.
builtin/replay.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/builtin/replay.c b/builtin/replay.c
index 3dde20acfef..dc46c921667 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -34,11 +34,11 @@ static struct commit *peel_committish(struct repository *repo,
struct object *obj;
struct object_id oid;
if (repo_get_oid(repo, name, &oid))
die(_("'%s' is not a valid commit-ish for %s"), name, mode);
- obj = parse_object(repo, &oid);
+ obj = parse_object_or_die(repo, &oid, name);
return (struct commit *)repo_peel_to_type(repo, name, 0, obj,
OBJ_COMMIT);
}
static char *get_author(const char *message)
--
2.52.0.383.gb1c58d6b301
^ permalink raw reply related [flat|nested] 35+ messages in thread* [PATCH v3 6/6] t3650: add more regression tests for failure conditions
2026-01-05 19:53 ` [PATCH v3 0/6] " kristofferhaugsbakk
` (4 preceding siblings ...)
2026-01-05 19:53 ` [PATCH v3 5/6] replay: die if we cannot parse object kristofferhaugsbakk
@ 2026-01-05 19:53 ` kristofferhaugsbakk
2026-01-06 23:12 ` [PATCH v3 0/6] replay: die descriptively when invalid commit-ish Elijah Newren
6 siblings, 0 replies; 35+ messages in thread
From: kristofferhaugsbakk @ 2026-01-05 19:53 UTC (permalink / raw)
To: git
Cc: Kristoffer Haugsbakk, christian.couder, newren, Siddharth Asthana,
Phillip Wood
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
There isn’t much test coverage for basic failure conditions. Let’s add
a few more since these are simple to write and remove if they become
obsolete.
Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Notes (series):
v2:
Improve test `option --onto or --advance is mandatory`. Phillip pointed out
that using a pipe loses the return value. Instead let’s test the whole
output by just appending `git replay -h` to `expect`.
Also “normalize” to just using echo/printf for the `expect` since these are
just oneliner errors.
Also add two more tests (at the end).
t/t3650-replay-basics.sh | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index d4399aa1662..d10c01506f1 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -41,10 +41,17 @@ test_expect_success 'setup' '
git switch main &&
test_commit L &&
test_commit M &&
+ git switch --detach topic4 &&
+ test_commit N &&
+ test_commit O &&
+ git switch -c topic-with-merge topic4 &&
+ test_merge P O --no-ff &&
+ git switch main &&
+
git switch -c conflict B &&
test_commit C.conflict C.t conflict
'
test_expect_success 'setup bare' '
@@ -63,10 +70,43 @@ test_expect_success '--onto with invalid commit-ish' '
printf "a valid commit-ish for --onto\n" >>expect &&
test_must_fail git replay --onto=refs/not-valid topic1..topic2 2>actual &&
test_cmp expect actual
'
+test_expect_success 'option --onto or --advance is mandatory' '
+ echo "error: option --onto or --advance is mandatory" >expect &&
+ test_might_fail git replay -h >>expect &&
+ test_must_fail git replay topic1..topic2 2>actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'no base or negative ref gives no-replaying down to root error' '
+ echo "fatal: replaying down from root commit is not supported yet!" >expect &&
+ test_must_fail git replay --onto=topic1 topic2 2>actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'options --advance and --contained cannot be used together' '
+ printf "fatal: options ${SQ}--advance${SQ} " >expect &&
+ printf "and ${SQ}--contained${SQ} cannot be used together\n" >>expect &&
+ test_must_fail git replay --advance=main --contained \
+ topic1..topic2 2>actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'cannot advance target ... ordering would be ill-defined' '
+ echo "fatal: cannot advance target with multiple sources because ordering would be ill-defined" >expect &&
+ test_must_fail git replay --advance=main main topic1 topic2 2>actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'replaying merge commits is not supported yet' '
+ echo "fatal: replaying merge commits is not supported yet!" >expect &&
+ test_must_fail git replay --advance=main main..topic-with-merge 2>actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'using replay to rebase two branches, one on top of other' '
git replay --onto main topic1..topic2 >result &&
test_line_count = 1 result &&
--
2.52.0.383.gb1c58d6b301
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 0/6] replay: die descriptively when invalid commit-ish
2026-01-05 19:53 ` [PATCH v3 0/6] " kristofferhaugsbakk
` (5 preceding siblings ...)
2026-01-05 19:53 ` [PATCH v3 6/6] t3650: add more regression tests for failure conditions kristofferhaugsbakk
@ 2026-01-06 23:12 ` Elijah Newren
2026-01-07 3:56 ` Junio C Hamano
6 siblings, 1 reply; 35+ messages in thread
From: Elijah Newren @ 2026-01-06 23:12 UTC (permalink / raw)
To: kristofferhaugsbakk
Cc: git, Kristoffer Haugsbakk, christian.couder, Siddharth Asthana,
Phillip Wood
On Mon, Jan 5, 2026 at 11:53 AM <kristofferhaugsbakk@fastmail.com> wrote:
>
[...]
> § Changes in v3
>
> Apply review feedback from Elijah. See patches for details.
>
> • Patch 1: More terse function name
> • Patch 2: Improve commit message
> • Patch 3: Improve commit message: fix outdated function name mention
> • Patch 4: [new] Apply code comment/error message tweaks
This round looks good to me; thanks!
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [PATCH v3 0/6] replay: die descriptively when invalid commit-ish
2026-01-06 23:12 ` [PATCH v3 0/6] replay: die descriptively when invalid commit-ish Elijah Newren
@ 2026-01-07 3:56 ` Junio C Hamano
0 siblings, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2026-01-07 3:56 UTC (permalink / raw)
To: Elijah Newren
Cc: kristofferhaugsbakk, git, Kristoffer Haugsbakk, christian.couder,
Siddharth Asthana, Phillip Wood
Elijah Newren <newren@gmail.com> writes:
> On Mon, Jan 5, 2026 at 11:53 AM <kristofferhaugsbakk@fastmail.com> wrote:
>>
> [...]
>> § Changes in v3
>>
>> Apply review feedback from Elijah. See patches for details.
>>
>> • Patch 1: More terse function name
>> • Patch 2: Improve commit message
>> • Patch 3: Improve commit message: fix outdated function name mention
>> • Patch 4: [new] Apply code comment/error message tweaks
>
> This round looks good to me; thanks!
Thanks, both. Let's mark it for 'next'.
^ permalink raw reply [flat|nested] 35+ messages in thread