There've been several questions recently about skipping changes when maintaining release branches in Mercurial. For example:
- Mercurial: Branch specific changes keep coming back after dummy merge
- Why are Mercurial backouts in one branch affecting other branches?
Since it was introduced in 2.0, I've wondered about using graft
to avoid this problem. Given a revision tree like this:
A---B---C---D---E---F---G---H---I---J
Suppose we need to create a release branch that skips the Evil change E
.
hg update -r D
hg graft "F::J"
giving us:
A---B---C---D---E---F---G---H---I---J
\
--F'--G'--H'--I'--J'
- Q1: What just happened here? I can understand that
transplant
would have generated patches out ofF::J
, and then applied them ontoD
, butgraft
is said to use the 3-way merge rather than patches. So....... how does that work? Why is it better?
Lets say I now fix E
, and merge that into my release branch.
--E2-----------------
/ \
A---B---C---D---E---F---G---H---I---J---M1
\ \
--F'--G'--H'--I'--J'---------M2--
M1 is a straight merge; nothing special there. M2 is merging branches which have "the same" (or at least equivalent) changes on.
- Q2: Is this merge just a normal 3-way merge using
D
,J'
andM1
? - Q3: Has mercurial stored/used extra information about the graft operation to help it with the merge?
And finally...
- Q4: What are the potential problems with a flow like this?
Q1: It helps when there are conflicts. You can use your usual merge tool then (for me it's inline conflict markers, which I edit with Emacs' smerge-mode).
Q2: It's a normal merge.
Q3: No.
Q4: I think it's ugly to have two almost identical branches.
When you update to
D
and graftF::J
, Mercurial runs a number of merges. It will start with this merge:If we write
+d
for the delta between the statesC
andD
, then we start with:Turn the graph 90 degrees clockwise and the above three-way merge looks like this:
That is, we pretend that we started with
E
and applied the opposite of-e
to get toD
. I think of as the reverse patch of+e
. Starting inE
we also went to stateF
with the normal delta+f
. There's nothing strange here — we have all the states (D
,E
, andF
) in the repository already. So seen like this, it's clear that we can mergeD
andF
.Merging is a matter of "completing the diamond". So we find a new state
M
that is a mix ofD
andF
and where the difference fromD
toM
is similar to+f
and the difference fromF
toM
is similar to-e
. It looks like this:The
+f
delta became+f'
and the-e
delta became-e'
. This is just a normal three-way merge, but the effect is interesting: we've appliedF
ontoD
instead ofE
!After the merge, the second parent of
M
toF
is dropped:To reiterate: We have copied the "effect" of
F
ontoD
, that is, we have found a delta (+f'
) that applied toD
give the same effect as when+f
was applied toE
. We can straighten the graph a bit to get:The result is that
F
is grafted ontoD
using the full three-way machinery.Q1: What just happened here? So....... how does that work? Why is it better?
A1: Using merges is better than patches since the merge machinery takes things like renames into account.
Q2: Is this merge just a normal 3-way merge using D, J' and M1?
A2: Yes, grafting does not alter the topology of the graph.
Q3: Has mercurial stored/used extra information about the graft operation to help it with the merge?
A3: No.
Q4: What are the potential problems with a flow like this?
A4: From a merge perspective it should work okay. It will duplicate some history which might be confusing for people.