There've been several questions recently about skipping changes when maintaining release branches in Mercurial. For example:
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'
transplant would have generated patches out of F::J, and then applied them onto D, but graft 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.
D, J' and M1?And finally...
When you update to D and graft F::J, Mercurial runs a number of merges. It will start with this merge:
M = three_way_merge(local=D, other=F, base=E)
If we write +d for the delta between the states C and D, then we start with:
        +d     +e     +f
---- C ---- D ---- E ---- F ----
Turn the graph 90 degrees clockwise and the above three-way merge looks like this:
    -e  
  .---- D
 /
E
 \
  '---- F
    +f
That is, we pretend that we started with E and applied the opposite of -e to get to D.  I think of as the reverse patch of +e. Starting in E we also went to state F with the normal delta +f. There's nothing strange here — we have all the states (D, E, and F) in the repository already. So seen like this, it's clear that we can merge D and F.
Merging is a matter of "completing the diamond". So we find a new state M that is a mix of D and F and where the difference from D to M is similar to +f and the difference from F to M is similar to -e. It looks like this:
    -e     +f'
  .---- D ----.
 /             \
E               M
 \             /
  '---- F ----'
    +f     -e'
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 applied F onto D instead of E!
After the merge, the second parent of M to F is dropped:
    -e     +f'
  .---- D ----.
 /             \
E               M
 \
  '---- F
    +f
To reiterate: We have copied the "effect" of F onto D, that is, we have found a delta (+f') that applied to D give the same effect as when +f was applied to E. We can straighten the graph a bit to get:
       +f'
--- D ---- M
     \
      '---- E ---- F
        +e     +f
The result is that F is grafted onto D 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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With