git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[RFC v2] Rebasing merges: a jorney to the ultimate solution (Road Clear)

From
Sergey Organov <sorganov@gmail.com>
Date
Mar 6, 2018, 13:26 UTC
Message-ID
<87r2oxe3o1.fsf@javad.com>
In-Reply-To
<87y3jtqdyg.fsf@javad.com>
Hi,
This is v2 of my "Rebasing merges" proposal.
Significant changes are:
1. Fixed mistake in the final merge step in the original proposal: wrong
   merge base was used. Thanks everybody who provided test-cases, and
   special thanks to Igor Djordjevic <igor.d.djordjevic@gmail.com> for
   implementing and testing a few variants of the method.
2. Added discussion of the exact place where handling of special
   frequent cases such as "git merge -ours", if any, should go.
3. I now use "True Merge" name instead of former "Trivial Merge", to
   avoid confusion with what Git documentation calls "trivial merge",
   thanks to Junio C Hamano <gitster@pobox.com> for pointing this out.

During discussion of the original proposal, yet another way of implementing a true rebase of a merge commit has been suggested by Phillip Wood <phillip.wood@dunelm.org.uk>[1]. His method also gathers the changes on both sides of the merge and then merges them back to the original merge, so both methods have similar concept and differ in implementation. It looks like both implementations bring the same result, at least it was so in the limited testing that Igor performed.

[1] https://public-inbox.org/git/6c8749ca-ec5d-b4b7-f1a0-50d9ad2949a5@talktalk.net/
-------8<-------------8<------

By accepting the challenges raised in recent discussion of advanced support for history rebasing and editing in Git, I hopefully figured out a clean and elegant method of rebasing merges that I think is "The Right Way (TM)" to perform this so far troublesome operation. ["(TM)" here has second meaning: "True Merge" (TM), see below.]

Let me begin with quick outline of the method, to illustrate the simplicity of the approach, and special thanks here must go to "Johannes Sixt" <j6t@kdbg.org> for his original bright idea to use "cherry-pick -m1" to rebase merge commits.

Given 2 original branches, b1 and b2, and a merge commit M that joins them, suppose we've already rebased b1 to b1', and b2 to b2'. Suppose also that B1' and B2' happen to be the tip commits on b1' and b2', respectively.

To produce merge commit M' that joins b1' and b2', the following operations will suffice:

1. Checkout b2' and cherry-pick -m2 M, to produce U2' (and new b2').
2. Checkout b1' and cherry-pick -m1 M, to produce U1' (and new b1').
3. Perform 3-way merge of U1' and U2' using original M as merge base,
   to get UM'.
4. Get rid of U1' and U2' by re-writing parent references of UM' from
   U1' and U2' to  B1' and B2', respectively, to produce M'.
5. Mission complete.

Let's now turn to the method itself and see why and how it actually works.

First off, let me introduce you to my new friend, the True Merge, or (TM) for short. By definition, (TM) is a merge that brings absolutely no differences to the sides of the merge. (I also like to call him "Angel Merge" (AM), both as being the most beautiful of all merges, and as direct antithesis to "[d]evil merge"; or even "Perfect Merge" (PM), but the latter goes after lunch time.)

Being trivial history joint and nothing else, (TM)/(AM)/(PM) is safe and easy to be rebased (see below). However, since most of us have never met (TM) in practice, you probably wonder how (TM) can actually help us handle general case of rebasing of some random merge.

Let's start with this history:
    M
   / \
  B1  B2

where B1 and B2 are tip commits of 2 branches, and M is the merge commit that joins them. Let's transform this history to the following one, contextually equivalent to the original, by introducing 2 non-merge utility commits U1 and U2, and a new utility merge commit UM:

    UM
   /  \
  U1   U2
  |    |
  B1   B2

were contents of all the created commits match, and are exact copies of the original content of M. I.e., provided [A] denotes "content of commit A", we have:

  [UM] = [U1] = [U2] = [M]

Stress again how these changes to the history preserve the exact content of the original merge ([UM] = [M]), how U1 an U2 represent content changes due to the merge on either side[*], and how content of neither preceding nor subsequent commits is affected by the change of representation.

Now observe that as [U1] = [UM], and [U2] = [UM], the UM happens to be exactly our new friend -- the "True Merge (TM)" his true self, introducing exactly zero changes to content. Pure history joint.

Next, we separately rebase both branches of our new representation of the history to whatever new base we need, and we get:

  U1'  U2'
  |    |
  B1'  B2'

where U1' and U2' are rebased versions of U1 and U2, obtained by usual rebasing methods for non-merge commits.

Finally, let's merge back our branches.

To perform the right kind of merge, notice that U1' and U2' have diverged from U1 and U2, respectively. Further, provided [U1] = [U2] = [UM] = [M], they have both diverged from the original merge commit M. Therefore, to merge U1' and U2' into UM', it suffices to use 3-way merge using original M as the merge base:

    UM'
   /  \
  U1'  U2'
   \  /
    M

Note that unlike merging of B1' and B2' that current "git rebase --preserve-merges/--recreate-merges" performs, merging of U1' and U2' is safe, as original UM, being (TM), doesn't itself carry any content changes, and thus none could be missed by ignoring the UM. Essentially, reproducing a (TM) is just joining corresponding histories back, and that's what merge operation is all about.

If the resulting U1' and U2' happen to match ([U1'] = [U2']), we have got our lovely (TM) back and we are clear to proceed automatically.

OTOH, if the resulting U1' and U2' differ, we should better stop for user inspection and possible amendment of the resulting UM'. At this point special treatment of any rather frequent cases (such as "git merge -ours") could be applied before amendment, if need to be. Amendment by replacing UM' with re-merge of B2' and B1' could be suggested to the user as well. Options are plenty.

Finally, to get to our required merge commit M', we get the content of UM' (possibly amended by the user), and record two actual parents of the merge:

    M'
   / \
  B1' B2'
where [M'] = [UM'].
That's it. Mission complete.
The method is expected to have the following nice features:
- it carefully preserves user changes by rebasing the merge commit
itself, in a way that is semantically similar to rebasing simple
(non-merge) commits, yet it allows changes made to branches during
history editing to propagate over corresponding merge commit that joins
the branches, even automatically when the changes don't conflict, as
expected.
- it has provision for detection of even slightest chances of ending up
with surprising merge (just check if UM' is still (TM)), so that
implementation could stop for user inspection and amendment when
appropriate, yet it is capable of handling trivial cases smoothly and
automatically.
- it never falls back to simple invocation of merge operation on rebased
original branches themselves, thus avoiding the problem of lack of
knowledge of how the merge at hand has been performed in the first
place.
- it allows implementation to let user manually perform whatever merge
she wishes when suspect result is automatically detected.
- it extends trivially to octopus merges.
- it appears to be shiny to the point that it will likely be able to
handle even darkest [d]evil merges nicely, no special treatment
required.
Footnote:

[*] We may as well consider the (UM,U1,U2) trio to be semantically split representation of git merge commit, where U1 and U2 represent content changes to the sides, and UM represents pure history joint. Or, the other way around, we may consider git merge commit to be optimized representation of this trio. I think this split representation could help to simplify reasoning about git merges in general.

-- Sergey
Previous: Sergey OrganovNext: Johannes Schindelin
Message 140 of 173 in “[RFC] Rebasing merges: a jorney to the ultimate solution (Road Clear)”
  1. Sergey OrganovFeb 16, 2018
  2. Jacob KellerFeb 18, 2018
  3. Sergey OrganovFeb 19, 2018
  4. Igor DjordjevicFeb 19, 2018
  5. Sergey OrganovFeb 20, 2018
  6. Johannes SchindelinFeb 27, 2018
  7. Sergey OrganovFeb 27, 2018
  8. Jacob KellerFeb 27, 2018
  9. Johannes SchindelinFeb 27, 2018
  10. Igor DjordjevicFeb 27, 2018
  11. Igor DjordjevicFeb 27, 2018
  12. Johannes SchindelinFeb 27, 2018
  13. Igor DjordjevicFeb 28, 2018
  14. Igor DjordjevicFeb 28, 2018
  15. Sergey OrganovFeb 28, 2018
  16. Igor DjordjevicFeb 28, 2018
  17. Sergey OrganovFeb 28, 2018
  18. Igor DjordjevicFeb 28, 2018
  19. Igor DjordjevicFeb 27, 2018
  20. Junio C HamanoFeb 28, 2018
  21. Igor DjordjevicFeb 28, 2018
  22. Sergey OrganovFeb 28, 2018
  23. Jacob KellerFeb 28, 2018
  24. Igor DjordjevicFeb 28, 2018
  25. Igor DjordjevicFeb 28, 2018
  26. Sergey OrganovFeb 28, 2018
  27. Igor DjordjevicFeb 28, 2018
  28. Sergey OrganovMar 1, 2018
  29. Sergey OrganovFeb 28, 2018
  30. Igor DjordjevicFeb 28, 2018
  31. Igor DjordjevicFeb 28, 2018
  32. Sergey OrganovMar 1, 2018
  33. Sergey OrganovMar 1, 2018
  34. Igor DjordjevicMar 2, 2018
  35. Sergey OrganovMar 2, 2018
  36. Igor DjordjevicMar 2, 2018
  37. Phillip WoodMar 2, 2018
  38. Phillip WoodMar 2, 2018
  39. Jacob KellerMar 2, 2018
  40. Igor DjordjevicMar 2, 2018
  41. Phillip WoodMar 6, 2018
  42. Johannes SchindelinMar 6, 2018
  43. Igor DjordjevicMar 6, 2018
  44. Johannes SchindelinMar 7, 2018
  45. Phillip WoodMar 8, 2018
  46. Phillip WoodMar 8, 2018
  47. Igor DjordjevicMar 8, 2018
  48. Johannes SchindelinMar 11, 2018
  49. Igor DjordjevicMar 11, 2018
  50. Johannes SchindelinMar 12, 2018
  51. Sergey OrganovMar 12, 2018
  52. Igor DjordjevicMar 13, 2018
  53. Johannes SchindelinMar 26, 2018
  54. Sergey OrganovMar 27, 2018
  55. Johannes SchindelinMar 27, 2018
  56. Sergey OrganovApr 2, 2018
  57. Igor DjordjevicMar 12, 2018
  58. Sergey OrganovMar 13, 2018
  59. Igor DjordjevicMar 8, 2018
  60. Johannes SchindelinMar 11, 2018
  61. Igor DjordjevicMar 11, 2018
  62. Johannes SchindelinMar 12, 2018
  63. Igor DjordjevicMar 13, 2018
  64. Johannes SchindelinMar 26, 2018
  65. Sergey OrganovMar 27, 2018
  66. Johannes SchindelinMar 27, 2018
  67. Sergey OrganovMar 28, 2018
  68. Johannes SchindelinMar 30, 2018
  69. Sergey OrganovMar 30, 2018
  70. Sergey OrganovMar 28, 2018
  71. Jacob KellerMar 8, 2018
  72. Johannes SchindelinMar 11, 2018
  73. Igor DjordjevicMar 11, 2018
  74. Igor DjordjevicMar 8, 2018
  75. Igor DjordjevicMar 8, 2018
  76. Johannes SchindelinMar 11, 2018
  77. Sergey OrganovMar 14, 2018
  78. Igor DjordjevicMar 14, 2018
  79. Sergey OrganovMar 15, 2018
  80. Igor DjordjevicMar 15, 2018
  81. Igor DjordjevicMar 17, 2018
  82. Sergey OrganovMar 19, 2018
  83. Igor DjordjevicMar 19, 2018
  84. Sergey OrganovMar 20, 2018
  85. Johannes SchindelinMar 26, 2018
  86. Junio C HamanoMar 6, 2018
  87. Johannes SchindelinMar 7, 2018
  88. Junio C HamanoMar 7, 2018
  89. Johannes SchindelinMar 8, 2018
  90. Junio C HamanoMar 8, 2018
  91. Johannes SchindelinMar 9, 2018
  92. Jacob KellerMar 2, 2018
  93. Igor DjordjevicMar 2, 2018
  94. Igor DjordjevicMar 3, 2018
  95. Sergey OrganovMar 5, 2018
  96. Phillip WoodMar 2, 2018
  97. Igor DjordjevicMar 3, 2018
  98. Sergey OrganovMar 5, 2018
  99. Phillip WoodMar 6, 2018
  100. Junio C HamanoMar 6, 2018
  101. Johannes SchindelinMar 8, 2018
  102. Junio C HamanoMar 8, 2018
  103. Johannes SchindelinMar 11, 2018
  104. Junio C HamanoMar 13, 2018
  105. Johannes SchindelinMar 26, 2018
  106. Johannes SchindelinMar 5, 2018
  107. Igor DjordjevicMar 6, 2018
  108. Johannes SchindelinMar 7, 2018
  109. Phillip WoodMar 6, 2018
  110. Sergey OrganovMar 6, 2018
  111. Igor DjordjevicMar 8, 2018
  112. Johannes SchindelinMar 6, 2018
  113. Sergey OrganovMar 7, 2018
  114. Johannes SchindelinMar 7, 2018
  115. Sergey OrganovMar 7, 2018
  116. Johannes SchindelinMar 8, 2018
  117. Sergey OrganovMar 12, 2018
  118. Johannes SchindelinMar 26, 2018
  119. Sergey OrganovMar 27, 2018
  120. Johannes SchindelinMar 27, 2018
  121. Sergey OrganovMar 28, 2018
  122. Phillip WoodMar 8, 2018
  123. Johannes SchindelinMar 5, 2018
  124. Sergey OrganovMar 13, 2018
  125. Igor DjordjevicMar 14, 2018
  126. Sergey OrganovMar 14, 2018
  127. Igor DjordjevicMar 15, 2018
  128. Sergey OrganovMar 15, 2018
  129. Igor DjordjevicMar 15, 2018
  130. Sergey OrganovMar 16, 2018
  131. Igor DjordjevicMar 17, 2018
  132. Sergey OrganovMar 19, 2018
  133. Johannes SchindelinMar 26, 2018
  134. Jacob KellerFeb 28, 2018
  135. Sergey OrganovFeb 27, 2018
  136. Junio C HamanoFeb 27, 2018
  137. Jacob KellerFeb 28, 2018
  138. Sergey OrganovFeb 28, 2018
  139. Sergey OrganovFeb 28, 2018
  140. [RFC v2] Rebasing merges: a jorney to the ultimate solution (Road Clear)Sergey Organov, Mar 6, 2018
  141. Johannes SchindelinMar 7, 2018
  142. Sergey OrganovMar 7, 2018
  143. Johannes SchindelinMar 7, 2018
  144. Sergey OrganovMar 7, 2018
  145. Johannes SchindelinMar 8, 2018
  146. Sergey OrganovMar 12, 2018
  147. Johannes SchindelinMar 26, 2018
  148. Igor DjordjevicMar 8, 2018
  149. Igor DjordjevicMar 8, 2018
  150. Igor DjordjevicMar 8, 2018
  151. Igor DjordjevicMar 8, 2018
  152. Johannes SchindelinMar 11, 2018
  153. Igor DjordjevicMar 11, 2018
  154. Johannes SchindelinMar 12, 2018
  155. Sergey OrganovMar 12, 2018
  156. Johannes SchindelinMar 26, 2018
  157. Sergey OrganovMar 27, 2018
  158. Igor DjordjevicMar 13, 2018
  159. Johannes SchindelinMar 26, 2018
  160. Sergey OrganovMar 12, 2018
  161. Johannes SchindelinMar 11, 2018
  162. Igor DjordjevicMar 11, 2018
  163. Sergey OrganovMar 12, 2018
  164. Johannes SchindelinMar 26, 2018
  165. Sergey OrganovMar 27, 2018
  166. Igor DjordjevicMar 12, 2018
  167. Sergey OrganovMar 28, 2018
  168. Sergey OrganovMar 28, 2018
  169. Sergey OrganovMar 29, 2018
  170. Johannes SchindelinMar 30, 2018
  171. Sergey OrganovMar 30, 2018
  172. Johannes SchindelinMar 30, 2018
  173. Sergey OrganovMar 30, 2018

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.