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

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

From
IDIgor Djordjevic <igor.d.djordjevic@gmail.com>
Date
Feb 19, 2018, 23:44 UTC
Message-ID
<bbe64321-4d3a-d3fe-8bb9-58b600fabf35@gmail.com>
In-Reply-To
<87y3jtqdyg.fsf@javad.com>
Hi Sergey,
On 16/02/2018 14:08, Sergey Organov wrote:
Show 140 quoted lines
> 
> 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: a "Trivial Merge (TM)", see below.]
> 
> Let me begin by outlining the method in git terms, 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.
> 
> End of preface -- here we go.
> 
> 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. Merge --no-ff new b2' to current new b1', to produce 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 see why and how the method actually works.
> 
> Firs off, let me introduce you to my new friend, the Trivial Merge, or
> (TM) for short. By definition, (TM) is a merge that introduces
> absolutely no differences to the sides of the merge. (I also like to
> sometimes call him "Angel Merge", both as the most beautiful of all
> merges, and as direct antithesis to "evil merge".)
> 
> One very nice thing about (TM) is that to safely rebase it, it suffices
> to merge its (rebased) parents. It is safe in this case, as (TM) itself
> doesn't posses any content changes, and thus none could be missed by
> replacing it with another merge commit.
> 
> I bet most of us have never seen (TM) in practice though, so let's see
> how (TM) can help us handle general case of some random merge. What I'm
> going to do is to create a virtual (TM) and see how it goes from there.
> 
> Let's start with this history:
> 
>   M
>  / \
> B1  B2
> 
> And let's transform it to the following one, contextually equivalent to
> the original, by introducing 2 simple utility commits U1 and U2, and a
> new utility merge commit UM:
> 
>   UM
>  /  \
> U1   U2
> |    |
> B1   B2
> 
> Here content of any of the created UM, U1, and U2 is the same, and is
> exact copy of 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]), and how U1 an U2 represent content
> changes due to merge on either side[*], and how neither preceding nor
> subsequent commits content would be 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 "Trivial Merge (TM)" his true self,
> introducing zero changes to content.
> 
> Next we rebase our new representation of the history and we get:
> 
>   UM'
>  /  \
> U1'  U2'
> |    |
> B1'  B2'
> 
> Here UM' is bare merge of U1' and U2', in exact accordance with the
> method of rebasing a (TM) we've already discussed above, and U1' and U2'
> are rebased versions of U1 and U2, obtained by usual rebasing methods
> for non-merge commits.
> 
> (Note, however, that at this point UM' is not necessarily a (TM)
> anymore, so in real implementation it may make sense to check if UM' is
> not a (TM) and stop for possible user amendment.)
> 
> Finally, to get to our required merge commit M', we get the content of
> UM' and record two actual parents of the merge:
> 
>   M'
>  / \
> B1' B2'
> 
> Where [M'] = [UM'].
> 
> That's it. Mission complete.
> 
> I expect the method 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 doesn't prevent implementation from letting user to manually
> perform whatever merge she wishes when suspect result is automatically
> detected though.
> 
> - it extends trivially to octopus merges.
> 
> - it appears shiny to the point that it will likely be able to handle
> even darkest 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.

I`m far from a Git expert, but merely a bit advanced user, so please take my opinion with a grain of salt.

That said, I`m really interested in this topic, which seems to (try to) address the only "bad feeling" I had with rebasing merges - being afraid of silently losing amendments by actually trying to "replay" the merge (where additional and possibly important context is missing), instead of really "rebasing" it (somehow).

Even though this behavior is known and documented, it still left some to be desired.

Might be I`m missing something, but so far I like how described approach just "feels right" (to me, for now), being really simple, yet robust :)

As my humble contribution to the cause and discussion itself, I`m providing possibly naive, yet simple demonstration script[1], and clearly showing where current `git rebase --preserve-merges` is lacking (in my opinion, even though being expected and documented), and how your proposal seems to improve the situation here.

Thanks for your thoughts, and hoping to see this going somewhere :)
Regards, Buga

[1] Demonstration script: --- 8< --- #!/bin/sh

# rm -rf ./.git # rm -f ./test.txt

git init

touch ./test.txt git add -- test.txt

for i in {1..20}
do
	echo A$i >>test.txt
	git commit -am "A$i"
done

git checkout -b b1 sed -i '6iB1' test.txt git commit -am "B1"

git checkout -b b2 HEAD^ sed -i '16iB2' test.txt git commit -am "B2"

git checkout -b merge b1 git merge --no-commit b2 sed -i '12iX' test.txt # amend merge commit git commit -am "M" git tag original-merge

git checkout master
for i in {1..5}
do
	j=`expr "$i" + 20`
	sed -i "${i}iA${j}" test.txt
	git commit -am "A$j"
done

# (1) current merge rebasing logic, # not preserving merge commit manual amendment, as documented git rebase --preserve-merges master merge git tag preserve-merges

# (2) simple/naive demonstration of proposed merge rebasing logic # using described "Trivial Merge" (TM, or "Angel Merge"), # preserving merge commit manual amendment :) git checkout b1 git rebase master git cherry-pick -m1 original-merge

git checkout b2 git rebase master git cherry-pick -m2 original-merge

git branch -f merge b1 git checkout merge git merge b2 --no-commit git commit -a --reuse-message original-merge git tag angel-merge

git reset --hard b1^ git read-tree --reset angel-merge git update-ref refs/heads/merge "$(git show -s --format=%B original-merge | git commit-tree "$(git write-tree)" -p "$(git rev-parse b1^)" -p "$(git rev-parse b2^)")" git tag -f angel-merge git branch -f b1 b1^ git branch -f b2 b2^

# show resulting graph echo git log --all --decorate --oneline --graph

# comparison between original merge and rebased merge (1), # showing merge commit amendment "X" being silently lost during rebase echo echo 'diff original-merge..preserve-merges:' git diff original-merge..preserve-merges

# comparison between original merge and rebased merge (2), # showing merge commit amendment "X" being preserved during rebase # (not shown in diff) echo echo 'diff original-merge..angel-merge:' git diff original-merge..angel-merge

# direct comparison between two merge rebasing approaches, # merge commit amendment difference showing up echo echo 'diff preserve-merges angel-merge:' git diff preserve-merges angel-merge

Previous: Sergey OrganovNext: Sergey Organov
Message 4 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.