Thread (61 messages) 61 messages, 7 authors, 6d ago

Re: [PATCH v7 3/3] replay: offer an option to linearize the commit topology

From: Elijah Newren <hidden>
Date: 2026-07-15 07:34:15

On Mon, Jul 13, 2026 at 3:09 PM Junio C Hamano [off-list ref] wrote:
Elijah Newren [off-list ref] writes:
quoted
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.
quoted
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  B3

git 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  B3

With 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  branchA

This 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 line

Of 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  B2

Replaying 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  B2

If 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  B2

In 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 branchA

I 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.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help