Re: [PATCH v7 3/3] replay: offer an option to linearize the commit topology
- From
Elijah Newren <newren@gmail.com>
- Date
- Jul 15, 2026, 07:34 UTC
- Message-ID
- <CABPp-BGxO0bd3UzDYNnhNUgDSKYwcFVCFsJ9rCzmNX7Q0xBrow@mail.gmail.com>
- In-Reply-To
- <xmqqbjcawnhp.fsf@gitster.g>
On Mon, Jul 13, 2026 at 3:09 PM Junio C Hamano <gitster@pobox.com> wrote:
Show 38 quoted lines
> > Elijah Newren <newren@gmail.com> writes: > > > 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. > > Hmph. To me, this is slightly different. It acts more like an > escape hatch: "if you really do not want to mix unrelated things > into a single linear history, you can do this other thing." > > Stepping back, the unpredictable order of multiple merged lines of > history exists even without multiple positive refs. If you have > independent lines of development that were merged and you linearize > them, someone must choose which line comes first. If you let the > machinery make that decision, the resulting commit order may not > reflect your preferences. > > While I rarely perform octopus merges anymore, in situations where an > octopus merge is appropriate (e.g., when you have N independent > branches and their merge order does not matter), linearizing such > a history into a random sequence of N segments, built on top of > one another in an unspecified order, could actually be considered a > feature. You do not have to make a decision about something that is > inconsequential.
You're right that when flattening merges within a single branch, the machinery must pick an order, and that's fine — unavoidable, even. My objection isn't that; it's primarily the concatenation of distinct branches named on the command line into one chain, and, as a secondary point, the ignoring of the order of branches explicitly specified by the user on the command line.
Concretely: I have three branches to rebase onto master; one of them happens to contain a merge I'd like flattened. I add --linearize for that one merge — and now all three branches are silently concatenated into a single chain. That makes no sense to me, and I think won't to most users.
Anyway, I think I must have explained my position rather poorly; your response suggests I buried my main points, so let me try to restate them:
TL;DR version; my problems with the current implementation of `--linearize` are that it: * Makes the rare usecase easy, and ignores the common usecase * Makes it asymmetrically difficult to recover for those that wanted the common usecase instead of the easy * Makes `--linearize` mean something other than "remove non-linearity" * Turns multiple branches into one, but updates several branches anyway * Ignores order specified by the user on the command line * Introduces an inconsistency within git-replay between `--advance` and `--linearize --onto` (The last three items being minor compared to the first three.)
Longer version:
Consider the following history
M1 M2 M3 M4 M5
*---*---*---*---* <- master
\ \
\ \ A1 A2 A3 A4
\ \-*---*---*---* <- branchA
\ \
\ -*---* <- branchC
\ C1 C2
\
\-*---*---* <- branchB
B1 B2 B3git replay was designed to allow you to update all your branches at once.
For example, with this above history, running
git replay --onto master branchA branchB branchC
will rebase all three branches onto master (and handles the shared portion
of history between branchA and branchC in the obvious way):M1 M2 M3 M4 M5
*---*---*---*---* <- master
|
| A1 A2 A3 A4
|--*---*---*---* <- branchA
| \
| -*---* <- branchC
| C1 C2
|
\-*---*---* <- branchB
B1 B2 B3With the current implementation of --linearize, adding that flag, i.e.
git replay --linearize --onto master branchA branchB branchC
would instead give something like:M1 M2 M3 M4 M5 B1 B2 B3 A1 A2 C1 C2 A3 A4
*---*---*---*---*---*---*---*---*---*---*---*---*---*
^ ^ ^ ^
| | | |
master branchB branchC branchAThis topology strikes me as something that users would very rarely ever
want. Further, it:
* Makes one question why branchB and branchC were kept instead of
deleted; if the whole point is to concatenate the branches, then
since whichever branch lands on top contains the other two, why not
just get rid of the others?
* Makes the command behave differently on *already linear* history
when --linearize is added, which makes no sense to me.
* (Minor point, but still confusing to me) Ignores the order of
branches the user employed on the command lineOf course, the above involves no merges, so let's introduce one; consider the following alternate initial history:
M1 M2 M3 M4 M5
*---*---*---*---* <- master
| \
| \ A1 A2 A4 A6 A7 A8
| \-*---*---*---*---*---* <- branchA
\ \ / \
\ *---* -*---* <- branchC
\ A3 A5 C1 C2
\
\-*---* <- branchB
B1 B2Replaying the three branches,
git replay --onto master branchA branchB branchC
we would expect the base of the branches to simply be updated to current
master:M1 M2 M3 M4 M5
*---*---*---*---* <- master
|
| A1 A2 A4 A6 A7 A8
|---*---*---*---*---*---* <- branchA
| \ / \
| *---* -*---* <- branchC
| A3 A5 C1 C2
|
\-*---* <- branchB
B1 B2If you were to add --linearize, i.e.
git replay --linearize --onto master branchA branchB branchC
I personally would expect:M1 M2 M3 M4 M5
*---*---*---*---* <- master
|
| A1 A2 A4 A3 A5 A7 A8
|---*---*---*---*---*---*---* <- branchA
| \
| -*---* <- branchC
| C1 C2
|
\-*---* <- branchB
B1 B2In other words, `--linearize` should remove the non-linearity in the graph. Instead, the current implementation will return something like:
M1 M2 M3 M4 M5 A1 A2 A4 A3 A5 A7 C1 C2 B1 B2 A8
*---*---*---*---*---*---*---*---*---*---*---*---*---*---*---*
^ ^ ^ ^
| | | \
master branchC branchB branchAI can only imagine this rarely being useful to the user.
But to make it worse, please consider the difficulty of someone who
wanted the bottom graph but got the top one, vs. the difficulty of
someone who wanted the top graph but got the bottom one:
* (wanted bottom, got top) Just rebase branchB and branchA again; easy
* (wanted top, got bottom) You need to meticulously figure out the common
points of history and which sets of commits belong to each branch in
order to sequentially rebase each branch into the expected result.
In particular, the need to meticulously track start and endpoints with
individual
rebases was one of the reasons that led to `git replay` rather than improvements
to `git rebase`; the latter was so focused on single branches, that it
wasn't really
possible to extend to multiple branches. It's thus rather
disappointing to see new
flags for `git replay` that make handling multiple branches more painful.There's actually one more (admittedly minor) issue as well: it creates an inconsistency within git-replay itself. The `--advance` flag has a check to error out when multiple positive refs are specified solely because I thought it was weird to override the order of branches the user specified on the command line (and didn't want to implement something that could force the ordering of the revision walk); the error message even states "because the ordering would be ill-defined". For consistency, either both should be fine with ignoring the order of revisions specified by the user, or neither should be.
So, what to do?
Both paths I have in mind end at the same place; the only real question is whether the desired behavior lands in this series or as follow-up.
The minimal move is to make --linearize reject multiple positive refs for now (exactly as --advance and --revert already do), unblocking this series so it can merge down nearly as-is, and leave per-branch linearization as future work.
The complete move is to implement that desired behavior now, by tracking a last_commit per command-line branch so each branch is linearized independently.
The reason I am comfortable with erroring out as a stopgap: turning an error into working behavior later never breaks anyone, whereas letting the current concatenation semantics reach 'master' risks users coming to depend on them, which would make switching to the better behavior a compatibility break. Erroring now keeps our options open; merging as-is quietly closes them. (git-replay is still EXPERIMENTAL, so this is not fatal either way, but it seems better not to paint ourselves into a corner.)
For this series I would be perfectly happy with just the error; the per-branch last_commit tracking can come later.