From: Junio C Hamano Date: Mon, 08 Jun 2026 19:29:07 GMT Subject: Re: [PATCH 3/3] replay: offer an option to linearize the commit topology Message-ID: In-Reply-To: <20260608-toon-git-replay-drop-merges-v1-3-e3ee71fce7b4@iotcl.com> Toon Claes writes: > From: Johannes Schindelin > > One of the stated goals of git-replay(1) is to allow implementing the > git-rebase(1) functionality on the server side. > > The default mode of git-rebase(1) is to act as if `--no-rebase-merges` > was given. This mode drops merge commits instead of replaying them, and > linearized the commit history into a sequence of the > regular (single-parent) commits. "linearized" -> "linearizes"? > > Add option `--linearize` to git-replay(1) do the same. "do the same" -> "to do the same"? > Co-authored-by: Toon Claes There is no sign-off by any of the authors? > @@ -430,12 +435,20 @@ int replay_revisions(struct rev_info *revs, > while ((commit = get_revision(revs))) { > const struct name_decoration *decoration; > > - if (commit->parents && commit->parents->next) > + if (opts->linearize && (!commit->parents || commit->parents->next)) > + ; /* map current commit to the same as the previous commit */ This uses the same treatment on either root commits or merge commits? If this were a mistake and this wants to handle merges but not roots, shouldn't it be more like if (opts->linearize && (commit->parents && commit->parents->next)) ; /* map the merge to the previous */ > + else if (commit->parents && commit->parents->next) > die(_("replaying merge commits is not supported yet!")); And because the next one is also about merges, perhaps the early part of this if/else if cascade can be written if (commit->parents && commit->parents->next) { /* We have a merge */ if (!opts->linearize) die(_("can't replay a merge (yet)")); ; /* map current to the previous */ } else { ... wouldn't it? If the "map current to prev" is applicable to root, any root are mapped to the last_commit in the above, and if we saw a root as the first thing in the loop, last_commit is NULL, we do not do anything here, and after the if/else if/else cascade, we see last_commit is NULL and break out of the loop. > + else { > + struct commit *to_pick = reverse ? last_commit : onto; > + last_commit = > + pick_regular_commit(revs->repo, commit, > + replayed_commits, to_pick, > + &merge_opt, &result, > + opts->linearize ? last_commit : NULL, > + reverse, opts->empty); > + } > > - last_commit = pick_regular_commit(revs->repo, commit, replayed_commits, > - reverse ? last_commit : onto, > - &merge_opt, &result, reverse, opts->empty); > if (!last_commit) > break; > diff --git a/replay.h b/replay.h > index 1851a07705..07e6fdcca3 100644 > --- a/replay.h > +++ b/replay.h > @@ -62,6 +62,11 @@ struct replay_revisions_options { > * Defaults to REPLAY_EMPTY_COMMIT_DROP. > */ > enum replay_empty_commit_action empty; > + > + /* > + * Whether to linearize the commits (i.e. drop merge commits). > + */ > + int linearize; > }; > > /* This struct is used as an out-parameter by `replay_revisions()`. */ > diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh > index 3353bc4a4d..c781a3bb1b 100755 > --- a/t/t3650-replay-basics.sh > +++ b/t/t3650-replay-basics.sh > @@ -565,4 +565,26 @@ test_expect_success '--onto with --ref rejects multiple revision ranges' ' > test_grep "cannot be used with multiple revision ranges" err > ' > > +test_expect_success 'linearize the commit topology' ' > + test_tick && > + N=$(git commit-tree -m N -p L -p I L:) && > + N=$(git commit-tree -m N-child -p $N L:) && > + git update-ref refs/heads/N $N && > + > + git replay --ref-action=print --linearize \ > + --onto A B..refs/heads/N >out && > + > + test_line_count = 1 out && > + read N1 N2 N3 N4 + > + cat >expect <<-EOF && > + * N-child > + * I > + * L > + o A > + EOF > + git log --format=%s --graph --boundary A...$N3 >actual && > + test_cmp expect actual > +' Perhaps we would want to have a test that replays all the way down to the root commit?