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
(branches are permanent and global, did you want a bookmark?)
$ echo a >p_fork1
$ hg add p_fork1
$ hg pnew p_fork1
marked working directory as branch p_fork1
(branches are permanent and global, did you want a bookmark?)
$ echo a >p_join
$ hg add p_join
$ hg pnew p_join
marked working directory as branch p_join
(branches are permanent and global, did you want a bookmark?)

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
(branches are permanent and global, did you want a bookmark?)
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
(branches are permanent and global, did you want a bookmark?)
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
(branches are permanent and global, did you want a bookmark?)
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
(branches are permanent and global, did you want a bookmark?)
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" 

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
(branches are permanent and global, did you want a bookmark?)
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
(branches are permanent and global, did you want a bookmark?)
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" 
$ 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" 

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
(branches are permanent and global, did you want a bookmark?)
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
(branches are permanent and global, did you want a bookmark?)
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
(branches are permanent and global, did you want a bookmark?)
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
|