Re: rebase-with-history -- a technique for rebasing without trashing your repo history

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Björn Steinbrink wrote:
> On 2009.08.13 14:46:07 +0200, Michael Haggerty wrote:
>> (Please note that this technique only works for the typical "tracking
>> upstream" type of rebase; it doesn't help with rebases whose goals are
>> changing the order of commits, moving only part of a branch, rewriting
>> commits, etc.)
> 
> Hm, so that pretty much doesn't work at all for creating a clean patch
> series, which usually involves rewriting commits, squasing bug fixes
> into the original commits that introduced the bug etc.

Now that you mention it, there are some other uses of rebase whose
history could be recorded correctly, or at least better, in the DAG.  I
am not ready to advocate any of these changes, but I think they are
worth discussing.


A squash of two adjacent commits currently transforms this:

A---B1---B2---C

to this

A---B12---C'

(C' has the same contents and commit message as C but a different
history and therefore a different SHA1.)

But B12 includes both B1 and B2, so the correct history is this:

A---B1---B2----C
 \         \    \
  ---------B12---C'

The fact that B12 has B1 and B2 as ancestors tells git that it
incorporates both of their changes, which correctly describes reality.


Splitting a commit (not really an elementary rebase operation, but
achievable with "edit") transforms this:

A---B12---C

to this:

A---B1---B2---C'

It is not possible to represent this correctly in the DAG, because there
is no way to express that "B1" includes part of "B12" as a parent.  But
the following would be more accurate than discarding all history:

A---B12---C
 \     \   \
  B1---B2---C'

It would of course be difficult for the user-interface layer to be
confident that the changes in B1 and B2 are really equivalent to B12
unless the content of B12 and B2 are identical.


Inserting a new commit into the history (for example as part of
reordering later commits) transforms this:

A---C

to this:

A---B---C'

In this case C' includes everything that is in C, so the correct history is

A---C
 \   \
  B---C'


However, deleting a commit, which transforms this:

A---B---C

to this:

A---C'

cannot be represented in the DAG, because there is no way to express
that C' includes the changes from C without also implying that it
includes the changes from B.


Rewriting a single commit, under the assumption that the rewritten
commit is the logical equivalent of the original, transforms this:

A---B1---C

to this:

A---B2---C'

where B2 is a hand-rewritten version of B1, and C' is the version of C
produced by the rebase.  In this case, the history could be recorded as:

A---B1---C
 \    \   \
  ----B2---C'

but again, it is impossible for the user-interface layer to ascertain
that B2 is equivalent to B1 without help from the user.


All of this extra history would currently create far more clutter than
it is worth, but if there were a way to suppress the display of rebased
commits (as discussed in the third article I quoted), then the extra
information would be there to help git without overwhelming users.

> And even for just continously forward porting a series of commits, a
> common case might be that upstream applied some patches, but not all.
> Can you deal with that?
> 
> Example:
> 
> A---B---C (upstream)
>      \
>       H---I---J---K (yours)
> 
> Upstream takes some changes:
> 
> A---B---C---I'--K'--D (upstream)
>      \
>       H---I---J---K (yours)
> 
> rebase leads to:
> 
> A---B---C---I'--K'--D (upstream)
>                      \
>                       H'--J' (yours)
> 
> What would your approach generate in that case?

There *is no way* to represent this history in a DAG, and therefore the
history of this operation will necessarily be lost.  (Well, of course it
could be recorded in metadata supplemental to the DAG, but since the
history would not affect future merges it would be pointless.)  The
problem is that there is no way to claim that I' is derived from I
without also implying that I' includes the change in H (which it
doesn't).  I discuss this sort of thing in another article [1].

>> For more information, please see the full articles: [...]
> 
> In this one you have two DAGs:
> (I fixed the second one to also have the merge commit in "subsystem"
> instead of "topic", so they only differ WRT to the rebased stuff)
> 
> A)
> m---N---m---m---m---m---m---M  (master)
>      \                       \
>       o---o---O---o---o       o'--o'--o'--o'--o'--S  (subsystem)
>                        \                         /
>                         *---*---*-..........-*--T (topic)
> 
> 
> B)
> m---N---m---m---m---m---m---M  (master)
>      \                       \
>       \                       o'--o'--o'--o'--o'----------S  (subsystem)
>        \                     /   /   /   /   /           /
>         --------------------o---o---O---o---o---*---*---T (topic)
> 
> 
> And you say that the former creates problems when you want to merge
> again. How so?

As you very clearly showed (thanks!), the merge problems that I claimed
only occur in some obscure edge cases.

What I *should* have emphasized is that the merge S itself is much more
prone to conflicts in case A) (with merge base N) than in case B) (with
the last "o" as merge base).  That is the first advantage of
rebase-with-history.

And please note that I really advocate C), not B):

C)
m---N---m---m---m---m---m---M  master
     \                       \
      \                       o'--o'--o'--o'--o'  subsystem
       \                     /   /   /   /   / \
        --------------------o---o---O---o---o   \
                                             \   \
                                              \   *'--*'--*'  topic
                                               \ /   /   /
                                                *---*---*

where the topic branch is not merged into the subsystem branch but
rather rebased-with-history.  C) has the significant advantage over A)
or B) that the topic branch can be converted to a series of patches (the
*' patches) that apply cleanly to the rebased subsystem branch and can
therefore be submitted upstream.  In the case of A) or B), the only
available patch that applies cleanly to the rebased subsystem branch is
S, which is a single commit that squashes together the entire topic
branch and is therefore difficult to review.

So rebasing in a public repository makes it difficult for downstream
developers to apply their work to the rebased branch (because they have
to repeat the conflict resolution that was done in the upstream rebase),
and merging in a topic branch makes it more difficult to create an
easily-reviewable patch series.  rebase-with-history has neither of
these problems.

Michael

[1] "Git, Mercurial, and Bazaar—simplicity through inflexibility",
http://softwareswirl.blogspot.com/2009/08/git-mercurial-and-bazaarsimplicity.html

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]