So 3-way-merge just takes three inputs, "base, left, right", it says diff(base, left), diff(base, right), merge those two diffs together into one bigger diff.
If you were to actually ask, "how do I get from Y2 to Head?" the answer commit-wise would require some conceptual rollbacks, the resulting conceptual commit-tree looking like
B -- Y1 -- Y2 -- inv(Y2) -- inv(Y1) -- X1 -- X2 -- X3 -- Head
\
Cherry
but, because 3-way-merge is a crude heuristic, it doesn't actually commute Cherry through inv(Y2), inv(Y1), X1, X2, X3, and Head the way that, say, a `git rebase` would. It just doesn't have to.And in terms of what I was writing above, what I was missing was that the two merges can be coalesced into one merge.
The most mind-blowing thing about this is that if you did a real rebase it'd actually protect against the problems with associativity that 3-way-merge has, by forcing you to commute through each individual intermediate patch. Like, you don't need Darcs or Pijul if you'll just write some wrapper scripts around Git.
Careful here, associativity is a completely different property from commutativity. Also, Pijul doesn't "commute through each individual intermediate patch" at all, its algorithmic complexity is actually lower than Git's.
The effect of this operation is that it compares what it would take to go from Y1 -> X3 with what it would take to go from Y1 -> Y2 -- it doesn't care about the path. If Y1 added a new function, for example, then when computing the three-way diff it would say "well, clearly in the X3 side of the merge that function was removed".
This is astonishingly clever.
If conflict-free, it will have all the content from X3 and all the changes from Y1->Y2, but none of the changes from B->Y1. Once again git has blown my mind.