Patch Graphs (Multiple Dependencies)

pbranch allows the patches to form an arbitrary DAG.

Fork / Join

We start with a new repo:

$ hg init graph
$ cd graph
$ echo a >def-0
$ hg commit --add --message "def-0" 
adding def-0

and clone it away for later:

$ hg clone . ../graph-upstream
updating to branch default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved

Then we add some patches:

$ echo a >p-root
$ hg add p-root
$ hg pnew p-root
marked working directory as branch p-root
$ echo a >p-fork1
$ hg add p-fork1
$ hg pnew p-fork1
marked working directory as branch p-fork1
$ echo a >p-join
$ hg add p-join
$ hg pnew p-join
marked working directory as branch p-join

This is linear so far:

$ hg pgraph --status
@  p-join
|
o  p-fork1
|
o  p-root
|
o  default

Forking

So let’s make it fork. We go back to p-root:

$ hg update p-root
0 files updated, 0 files merged, 4 files removed, 0 files unresolved

and create another patch depending only on p-root:

$ echo a >p-fork2
$ hg add p-fork2
$ hg pnew p-fork2
marked working directory as branch p-fork2
created new head

So we have:

$ hg pgraph
o  p-join
|
| @  p-fork2
| |
o |  p-fork1
|/
o  p-root
|
o  default

Join

Now we make p-join depend on both of the p-forkN patches:

$ cat >.hg/pgraph <<-eof
    p-root: default
    p-fork1: p-root
    p-fork2: p-root
    p-join: p-fork1, p-fork2
eof

yielding:

$ hg pgraph --status
o    p-join
|\    * needs merge with p-fork1 (through .p-join)
| |   * needs merge with p-fork2 (through .p-join)
| |   * needs merge with .p-join
| |   * needs update of diff base to tip of .p-join
o |  p-fork1
| |
| @  p-fork2
|/
o  p-root
|
o  default

Base Branches

When we merge, pbranch introduces a base branch that forms the union (merge) of all the dependencies of a patch. This base branch is then used as the diff-base for the patch, meaning when we run hg pdiff, it diffs the patch’s tip against this base. For single dependencies, there is no base branch, because for them hg pdiff is basically the same as:

$ hg diff --rev p-fork1:p-join -X .hgpatchinfo | diffstat
 p-join |    1 +
 1 file changed, 1 insertion(+)

But once we merge two dependencies into a third, joining patch, which of the two dependencies should we diff against? Whichever we pick, the diff will always show the changes of the third patch plus those of the other dependency. We don’t want that, hence the base branch forming the union of the dependencies.

The base branch of a patch called foo is called .foo. When it is created the first time, pbranch creates it from the first dependency, and then merges in all other dependencies:

$ hg pmerge p-join
updating to p-fork1
2 files updated, 0 files merged, 2 files removed, 0 files unresolved
marked working directory as branch .p-join
created new head
updating to p-fork2
2 files updated, 0 files merged, 3 files removed, 0 files unresolved
.p-join: merging from p-fork2
marked working directory as branch .p-join
3 files updated, 0 files merged, 0 files removed, 0 files unresolved
p-join: merging from .p-join
marked working directory as branch p-join
2 files updated, 0 files merged, 0 files removed, 0 files unresolved

And the patch diff now properly shows only the file introduced by p-join:

$ hg pdiff | diffstat
 p-join |    1 +
 1 file changed, 1 insertion(+)

which, as noted above, is basically the same as:

$ hg diff --rev .p-join:p-join -X .hgpatchinfo | diffstat
 p-join |    1 +
 1 file changed, 1 insertion(+)

Here’s the story as seen by hg glog:

$ hg glog
@    7    p-join: merge of .p-join - john
|\
| o    6    .p-join: merge of p-fork2 - john
| |\
| | o  5    .p-join: update patch dependencies - john
| | |
| o |  4    p-fork2: start new patch on p-root - john
| | |
o---+  3    p-join: start new patch on p-fork1 - john
 / /
| o  2    p-fork1: start new patch on p-root - john
|/
o  1    p-root: start new patch on default - john
|
o  0    : def-0 - john

We keep this for later:

$ hg clone . ../graph-forkjoin
updating to branch default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved

Flow of Updates

Update One Fork

If we update one of the forks again:

$ hg update p-fork1
0 files updated, 0 files merged, 5 files removed, 0 files unresolved
$ echo b >>p-fork1
$ hg commit --message "p-fork1 b" 
created new head

the join needs to merge this new head:

$ hg pgraph --status
o    p-join
|\    * needs merge with p-fork1 (through .p-join)
@ |  p-fork1
| |
| o  p-fork2
|/
o  p-root
|
o  default

So we go there:

$ hg update p-join
6 files updated, 0 files merged, 0 files removed, 0 files unresolved
needs merge with p-fork1 (through .p-join)
use 'hg pmerge'

and merge. Again, this first merges the new head into the base branch, .p-join, and then merges from the base branch into the the actual patch branch, p-join:

$ hg pmerge
updating to p-fork1
1 files updated, 0 files merged, 5 files removed, 0 files unresolved
.p-join: merging from p-fork1
marked working directory as branch .p-join
3 files updated, 0 files merged, 0 files removed, 0 files unresolved
p-join: merging from .p-join
marked working directory as branch p-join
2 files updated, 0 files merged, 0 files removed, 0 files unresolved

Looking at the merge graph, we sure are glad we don’t have to run all these merges correctly by hand:

$ hg glog --rev p-root:tip
@    10    p-join: merge of .p-join - john
|\
| o    9    .p-join: merge of p-fork1 - john
| |\
| | o  8    p-fork1: p-fork1 b - john
| | |
o | |  7    p-join: merge of .p-join - john
|\| |
| o |    6    .p-join: merge of p-fork2 - john
| |\ \
| | o |  5    .p-join: update patch dependencies - john
| | |/
| o |  4    p-fork2: start new patch on p-root - john
| | |
o---+  3    p-join: start new patch on p-fork1 - john
 / /
| o  2    p-fork1: start new patch on p-root - john
|/
o  1    p-root: start new patch on default - john
|

Update Both Forks

Now we update both forks in parallel before merging up:

$ hg update p-fork1
0 files updated, 0 files merged, 5 files removed, 0 files unresolved
$ echo c >>p-fork1
$ hg commit --message "p-fork1 c" 
created new head
$ hg update p-fork2
2 files updated, 0 files merged, 2 files removed, 0 files unresolved
$ echo b >>p-fork2
$ hg commit --message "p-fork2 b" 
created new head

So p-join needs two merges:

$ hg pgraph --status
o    p-join
|\    * needs merge with p-fork1 (through .p-join)
| |   * needs merge with p-fork2 (through .p-join)
o |  p-fork1
| |
| @  p-fork2
|/
o  p-root
|
o  default

We go there:

$ hg update p-join
6 files updated, 0 files merged, 0 files removed, 0 files unresolved
needs merge with p-fork1 (through .p-join)
needs merge with p-fork2 (through .p-join)
use 'hg pmerge'

and merge. This first merges both new heads into the base branch, .p-join, and then merges the combined result into p-join. Ensuring a proper diff base:

$ hg pmerge
updating to p-fork1
1 files updated, 0 files merged, 5 files removed, 0 files unresolved
.p-join: merging from p-fork1
marked working directory as branch .p-join
3 files updated, 0 files merged, 0 files removed, 0 files unresolved
updating to p-fork2
1 files updated, 0 files merged, 3 files removed, 0 files unresolved
.p-join: merging from p-fork2
marked working directory as branch .p-join
3 files updated, 0 files merged, 0 files removed, 0 files unresolved
p-join: merging from .p-join
marked working directory as branch p-join
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg pdiff | diffstat
 p-join |    1 +
 1 file changed, 1 insertion(+)

The final merge graph is rather intimidating, but should be quite logical now you understand the operation of hg pmerge:

$ hg glog --rev p-root:tip
@    15    p-join: merge of .p-join - john
|\
| o    14    .p-join: merge of p-fork2 - john
| |\
| | o    13    .p-join: merge of p-fork1 - john
| | |\
| o | |  12    p-fork2: p-fork2 b - john
| | | |
| | | o  11    p-fork1: p-fork1 c - john
| | | |
o---+ |  10    p-join: merge of .p-join - john
| | | |
| | o |  9    .p-join: merge of p-fork1 - john
| | |\|
| | | o  8    p-fork1: p-fork1 b - john
| | | |
o---+ |  7    p-join: merge of .p-join - john
| | | |
| | o |  6    .p-join: merge of p-fork2 - john
| |/| |
| | o |  5    .p-join: update patch dependencies - john
| | |/
| o |  4    p-fork2: start new patch on p-root - john
| | |
o---+  3    p-join: start new patch on p-fork1 - john
 / /
| o  2    p-fork1: start new patch on p-root - john
|/
o  1    p-root: start new patch on default - john
|