Re: [PATCH v7 3/3] replay: offer an option to linearize the commit topology
- From
Elijah Newren <newren@gmail.com>
- Date
- Jul 10, 2026, 03:47 UTC
- Message-ID
- <CABPp-BGzU9KHGF1nipi2HZaa1AiikMKGGaapQzHVH06wO4V1ww@mail.gmail.com>
- In-Reply-To
- <20260707-toon-git-replay-drop-merges-v7-3-808ab9b4afa6@iotcl.com>
Hi Toon!
Thanks for continuing to work on the series. Sorry that I've been out on vacation for 3+ weeks and then playing catch up. You addressed all my v2 feedback, and most things in this latest v7 look good. I do have one substantive concern with this patch, which I'll cover in detail below.
On Tue, Jul 7, 2026 at 12:07 PM Toon Claes <toon@iotcl.com> wrote:
Show 10 quoted lines
> > 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 > linearizes the history into a sequence of regular (single-parent) > commits. > > Add option `--linearize` to git-replay(1) to do the same.
Right, `--linearize` exists to change how merges are handled. I'd argue that if there are no merges, then you should get the same behavior whether or not --linearize appears on your command line.
Show 7 quoted lines
> Each replayed > commit is stacked on top of the previously replayed one. When a merge is > encountered, the commits reachable from all of its sides are replayed > into the single line and the merge itself is dropped. > > If a ref was pointing to a merge commit, that ref is updated to the > merge's last replayed ancestor.
This is a good description of the net effect of linearizing a single branch. I think it describes rebasing multiple branches at once much less well -- see below.
> git-replay(1) accepts multiple revision ranges, for example:
I think I know what you mean, but this isn't quite right: git-replay(1) only ever accepts a single revision range. From gitrevisions(7) (also in git-rev-parse(1)):
Commands that are specifically designed to take two distinct ranges
(e.g. "git range-diff R1 R2" to compare two ranges) do exist, but they
are exceptions. Unless otherwise noted, all "git" commands that operate
on a set of commits work on a single revision range. In other words,
writing two "two-dot range notation" next to each other, e.g.$ git log A..B C..D
does not specify two revision ranges for most commands. Instead it will
name a single connected set of commits, i.e. those that are reachable
from either B or D but are reachable from neither A or C.You could say that replay accepts multiple branches (references) within its revision range -- but even then that comes with an "in some cases" qualifier: `--advance` (and more recently, `--revert`) specifically reject multiple positive refs, precisely because (a) simply concatenating branches is surprising, and (b) the resulting order is ill-defined (or at least looks arbitrary to the user).
> $ git replay --onto main topic1 topic2 > > Without `--linearize` this replays 'topic1' and 'topic2' onto 'main' > independently and updates both refs.
And, if there are no merges anywhere in the range, I'd argue that adding --linearize either ought to do the same thing -- or else error out that multiple positive refs are not allowed with `--linearize`, the way `--advance` and `--revert` already do.
> With `--linearize` the whole set is flattened into one line: the ranges > are stacked on top of each other rather than replayed side by side, so > both refs end up pointing at different points along that single history.
To me, this is a significant principle of least astonishment violation.
> Replaying all revision ranges into one single linear history is > intentional and it's the only way to ensure predictable results.
I have to push back on both "only" and "predictable".
Regarding "only", there are at least two other choices: * make --linearize incompatible with multiple positive refs * More involved implementation (quick sketch): (a) Track a last_commit per branch specified on the command line, (b) Make the revision walk keep track of which branches each walked commit is reachable from, (c) for each commit to be replayed, for each branch it's reachable from, update the appropriate last_commit[branch]. (Except that when last_commit[branchA] == last_commit[branchB] and a commit is reachable from both branchA & branchB, you only replay the commit once.)
Regarding "predictable", I'd like to split predictability into two
pieces: guessable by the user, and consistent with other replay
commands. This behavior gives us neither:
* guessable by the user:
* which of the multiple branches specified on the command line is
first in your concatenated linearization? It's decided by rev-walk,
not what the user wrote.
* consistent:
* why does a merge-free topology behave differently with
--linearize than without it?
* why do `--advance` and `--revert` both refuse multiple positive
refs to avoid exactly this "which branch first" concatenation, while
`--onto --linearize` embraces it?For what it's worth, looking back at the v5 thread, it seems the `base = last_commit` rule came in to fix the real bug Junio and Phillip pointed out there -- that without it, only one side of a linearized merge survived. That fix is clearly correct for the single-branch case. My worry is only that applying it unconditionally reintroduces the multiple-positive-refs ordering problem we deliberately avoid elsewhere. Making `--linearize` reject multiple positive refs would keep the merge-flattening fix while sidestepping this entirely.
> A user > who wants to linearize ranges independently is advised to use separate > git-replay(1) invocations.
Which, to me, is another argument for just disallowing multiple positive refs under `--linearize`: if the recommended way to do it is separate invocations anyway, we may as well require them.
> Linearizing is a distinct operation, and flattening merge commits is > just one aspect of that. Recreating merges would be a separate mode, so > rather than mirror git-rebase(1)'s `--rebase-merges[=<mode>]` interface, > git-replay(1) uses its own `--linearize` option.
No disagreement here on this point.
Thanks, Elijah