Distributing Pending Changes To Patches

New Patch

Scenario

First, we pick up the base scenario:

$ hg clone scenario-base newpatch
updating to branch default
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ cd newpatch
$ hg pgraph
created graph description from current tips
o  patchC
|
o  patchB
|
o  patchA
|
@  default
$ hg update patchC
10 files updated, 0 files merged, 0 files removed, 0 files unresolved

Now we add two more lines to main-file:

$ echo "Null" | cat - main-file-1 >x ; mv x main-file-1
$ echo "Vier" | cat main-file-1 -  >x ; mv x main-file-1
$ hg stat
M main-file-1
$ hg diff
diff --git a/main-file-1 b/main-file-1
--- a/main-file-1
+++ b/main-file-1
@@ -0,0 +1,1 @@
+Null
@@ -5,0 +7,1 @@
+Vier

Multiple Patches

We’d like to commit these two lines to two new patches, patchD and patchE. We’d like to use the hg record or some such for this. But if we just do hg pnew now, the new lines will both get committed to the new patch immediately. This is where the --preserve option comes into play:

hg pnew—preserve
$ hg pnew --preserve patchD
marked working directory as branch patchD
(branches are permanent and global, did you want a bookmark?)

This preserves our changes in the working copy:

$ hg stat
M main-file-1
$ hg diff
diff --git a/main-file-1 b/main-file-1
--- a/main-file-1
+++ b/main-file-1
@@ -0,0 +1,1 @@
+Null
@@ -5,0 +7,1 @@
+Vier

But it does start the new patch:

$ hg log -l1
8    patchD: start new patch on patchC - john
$ hg pgraph
@  patchD
|
o  patchC
|
o  patchB
|
o  patchA
|
o  default

Now we can use hg record to commit only the first line, then use plain hg pnew again to commit the second to patchE (not shown here).

$ cd ..

Distributing Hunks

Scenario

First, we pick up the base scenario:

$ hg clone scenario-base hunks
updating to branch default
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ cd hunks
$ hg pgraph
created graph description from current tips
o  patchC
|
o  patchB
|
o  patchA
|
@  default
$ hg update patchC
10 files updated, 0 files merged, 0 files removed, 0 files unresolved

Then we realize that main-file-1 should really be in French, not German. So we do:

$ sed -i main-file-1 -e s/Eins/Une/ -e s/Zwei/Deux/ -e s/Drei/Trois/
$ cat main-file-1
Une
_
Deux
_
Trois

But then we realize these changes should really go into the separate patches, one number translation per patch. Since they all live in the same file, we have to do a bit of work to properly distribute them.

Backporting

Remember that with pbranch, a patch is just a diff between two branches. So a fairly easy and safe way to distribute these changes is to first simply commit them to the highest patch they belong to, patchC in our example. Then we go down to the lower patches and cherry-pick the parts of the change that really belong there.

This is safe because we can immediately commit our desired changes and then tackle their proper distribution in a second step, which we can always undo and try again. Another advantage of this scheme is that it works even if we already committed the changes, maybe even as multiple commits.

Commit to C

So we start by committing in patchC:

$ hg commit --message "German to French" 

Backport to B from C

Then we backport the change to patchB. We first just go there:

$ hg update patchB
1 files updated, 0 files merged, 3 files removed, 0 files unresolved
hg reapply

We now want to reapply all of our original changes and then simply throw out the parts that don’t belong here. pbranch supports reapplying a particular changeset to the current working dir. By “reapplying” I mean that all added or changed files are copied over verbatim, all removed files are deleted. So this is no merge or application of a diff. It is far simpler, but effective:

$ hg reapply patchC
$ hg diff
diff --git a/main-file-1 b/main-file-1
--- a/main-file-1
+++ b/main-file-1
@@ -1,1 +1,1 @@
-Eins
+Une
@@ -3,1 +3,1 @@
-Zwei
+Deux
@@ -5,1 +5,1 @@
-Three
+Trois

Since we’re at patchB now, the change to “Three” does not belong here. We get rid of it. I would normally use a visual differ to compare my working copy against its base revision to do this (hg meld or some such). In the tutorial script, we simulate it by manual reverts:

$ sed -i main-file-1 -e s/Trois/Three/
$ hg diff
diff --git a/main-file-1 b/main-file-1
--- a/main-file-1
+++ b/main-file-1
@@ -1,1 +1,1 @@
-Eins
+Une
@@ -3,1 +3,1 @@
-Zwei
+Deux

Looks good, so we commit:

$ hg commit --message "backported German to French to patchB" 

Backport to A from B

Now we move down to patchA. The new head created above does not get merged yet. It will get merged once we move back up from patchB to patchC at the end. So let’s move down:

$ hg update patchA
1 files updated, 0 files merged, 3 files removed, 0 files unresolved

Now we backport from patchB. This is better than backporting from patchC again, because otherwise we would have to get rid of the change to “Three” again:

$ hg reapply patchB
$ hg diff
diff --git a/main-file-1 b/main-file-1
--- a/main-file-1
+++ b/main-file-1
@@ -1,1 +1,1 @@
-Eins
+Une
@@ -3,1 +3,1 @@
-Two
+Deux
$ sed -i main-file-1 -e s/Deux/Two/
$ hg diff
diff --git a/main-file-1 b/main-file-1
--- a/main-file-1
+++ b/main-file-1
@@ -1,1 +1,1 @@
-Eins
+Une

Again, this looks good:

$ hg commit --message "backported German to French to patchA" 

Later, I want to show you what happens if we modify the backported changes (we shall correct “Une” to “Un”). So we keep the current repo state:

$ hg clone . ../hunksfixed
updating to branch default
2 files updated, 0 files merged, 0 files removed, 0 files unresolved

Return to C (merging heads)

For the moment, though, we just move back up to patchC, merging the new heads:

$ hg update patchC
7 files updated, 0 files merged, 0 files removed, 0 files unresolved
needs merge with patchA (through patchB)
needs merge with patchB
needs update of diff base to tip of patchB
use 'hg pmerge'
$ hg pmerge
updating to patchA
1 files updated, 0 files merged, 6 files removed, 0 files unresolved
patchB: merging from patchA
marked working directory as branch patchB
(branches are permanent and global, did you want a bookmark?)
merging main-file-1
3 files updated, 1 files merged, 0 files removed, 0 files unresolved
patchC: merging from patchB
marked working directory as branch patchC
(branches are permanent and global, did you want a bookmark?)
merging main-file-1
3 files updated, 1 files merged, 0 files removed, 0 files unresolved

Big picture

Here’s what happened in full:

$ hg glog
@    12    patchC: merge of patchB - john
|\
| o    11    patchB: merge of patchA - john
| |\
| | o  10    patchA: backported German to French to patchA - john
| | |
| o |  9    patchB: backported German to French to patchB - john
| | |
o | |  8    patchC: German to French - john
| | |
o | |  7    patchC: update patch description - john
| | |
o | |  6    patchC: update patch dependencies - john
| | |
o | |  5    patchC: changes for C - john
|/ /
o |  4    patchB: second try in B - john
| |
o |  3    patchB: start new patch on patchA - john
|/
o  2    patchA: update patch description - john
|
o  1    patchA: start new patch on default - john
|
o  0    : base - john

Check result

The patches now look as expected:

$ hg pdiff patchA
# HG changeset patch
# User john
# Date 0 0
a nifty patch
_
diff --git a/file-from-A b/file-from-A
new file mode 100644
--- /dev/null
+++ b/file-from-A
@@ -0,0 +1,1 @@
+One
diff --git a/main-file-1 b/main-file-1
--- a/main-file-1
+++ b/main-file-1
@@ -1,1 +1,1 @@
-One
+Une
$ hg pdiff patchB
# HG changeset patch
# User john
# Date 0 0
another patch
_
diff --git a/file-from-B b/file-from-B
new file mode 100644
--- /dev/null
+++ b/file-from-B
@@ -0,0 +1,1 @@
+Two
diff --git a/main-file-1 b/main-file-1
--- a/main-file-1
+++ b/main-file-1
@@ -3,1 +3,1 @@
-Two
+Deux
$ hg pdiff patchC
# HG changeset patch
# User john
# Date 0 0
yet another patch
_
diff --git a/file-from-C b/file-from-C
new file mode 100644
--- /dev/null
+++ b/file-from-C
@@ -0,0 +1,1 @@
+Three
diff --git a/main-file-1 b/main-file-1
--- a/main-file-1
+++ b/main-file-1
@@ -5,1 +5,1 @@
-Three
+Trois

Fixing Backports

Scenario

Now we’ll do something more complex. While backporting, we shall also fix something. Let’s restore the saved scenario from above and go about correct the typo in patchA before merging heads:

$ cd ../hunksfixed
$ hg pgraph # ensure the patch graph is up to date
created graph description from current tips
o  patchC
|
o  patchB
|
o  patchA
|
@  default
$ hg update patchA
4 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ cat main-file-1
Une
_
Two
_
Three
$ sed -i main-file-1 -e s/Une/Un/
$ cat main-file-1
Un
_
Two
_
Three
$ hg commit --message "fixed typo in patchA" 

Merge fix into B

When we now try to merge back into patchC, the attempt to merge into patchB in between already fails:

$ hg update patchC
7 files updated, 0 files merged, 0 files removed, 0 files unresolved
needs merge with patchA (through patchB)
needs merge with patchB
needs update of diff base to tip of patchB
use 'hg pmerge'
$ hg pmerge
updating to patchA
1 files updated, 0 files merged, 6 files removed, 0 files unresolved
patchB: merging from patchA
marked working directory as branch patchB
(branches are permanent and global, did you want a bookmark?)
merging main-file-1
warning: conflicts during merge.
merging main-file-1 incomplete! (edit conflicts, then use 'hg resolve --mark')
3 files updated, 0 files merged, 0 files removed, 1 files unresolved
abort: use 'hg resolve' to handle unresolved file merges, then do 'hg pmerge' again

What happened? The fix in patchA is not related usefully to patchB, so the merge cannot handle it automatically:

$ hg glog
@  11    patchA: fixed typo in patchA - john
|
o  10    patchA: backported German to French to patchA - john
|
| @  9    patchB: backported German to French to patchB - john
| |
| | o  8    patchC: German to French - john
| | |
| | o  7    patchC: update patch description - john
| | |
| | o  6    patchC: update patch dependencies - john
| | |
| | o  5    patchC: changes for C - john
| |/
| o  4    patchB: second try in B - john
| |
| o  3    patchB: start new patch on patchA - john
|/
o  2    patchA: update patch description - john
|
o  1    patchA: start new patch on default - john
|
o  0    : base - john

We get a merge conflict:

$ cat main-file-1
<<<<<<< local
Un
=======
Une
>>>>>>> other
_
Deux
_
Three

which we resolve (again, I simulate work in a visual diff/merge by command-line edits here):

$ sed -i main-file-1 -e /^CONTENT GOES HERElt;CONTENT GOES HERElt;.*/,+4cUn
$ cat main-file-1
Un
_
Deux
_
Three

Good:

$ hg resolve --mark main-file-1
$ hg pmerge
patchB: committing current merge from patchA

Merge fix into C

This took care of patchB. Now, unfortunately, we have to repeat this for patchC. The merge in patchB is not usefully related to patchC:

$ hg glog
@    12    patchB: merge of patchA - john
|\
| o  11    patchA: fixed typo in patchA - john
| |
| o  10    patchA: backported German to French to patchA - john
| |
o |  9    patchB: backported German to French to patchB - john
| |
| | o  8    patchC: German to French - john
| | |
| | o  7    patchC: update patch description - john
| | |
| | o  6    patchC: update patch dependencies - john
| | |
+---o  5    patchC: changes for C - john
| |
o |  4    patchB: second try in B - john
| |
o |  3    patchB: start new patch on patchA - john
|/
o  2    patchA: update patch description - john
|
o  1    patchA: start new patch on default - john
|
o  0    : base - john

So indeed we get:

$ hg update patchC
5 files updated, 0 files merged, 0 files removed, 0 files unresolved
needs merge with patchB
needs update of diff base to tip of patchB
use 'hg pmerge'
$ hg pmerge
updating to patchB
2 files updated, 0 files merged, 3 files removed, 0 files unresolved
patchC: merging from patchB
marked working directory as branch patchC
(branches are permanent and global, did you want a bookmark?)
merging main-file-1
warning: conflicts during merge.
merging main-file-1 incomplete! (edit conflicts, then use 'hg resolve --mark')
3 files updated, 0 files merged, 0 files removed, 1 files unresolved
abort: use 'hg resolve' to handle unresolved file merges, then do 'hg pmerge' again

and again:

$ cat main-file-1
<<<<<<< local
Un
=======
Une
>>>>>>> other
_
Deux
_
Trois
$ sed -i main-file-1 -e /^CONTENT GOES HERElt;CONTENT GOES HERElt;.*/,+4cUn
$ hg resolve --mark main-file-1
$ hg pmerge
patchC: committing current merge from patchB

Big picture

Here’s what happened in full:

$ hg glog
@    13    patchC: merge of patchB - john
|\
| o    12    patchB: merge of patchA - john
| |\
| | o  11    patchA: fixed typo in patchA - john
| | |
| | o  10    patchA: backported German to French to patchA - john
| | |
| o |  9    patchB: backported German to French to patchB - john
| | |
o | |  8    patchC: German to French - john
| | |
o | |  7    patchC: update patch description - john
| | |
o | |  6    patchC: update patch dependencies - john
| | |
o | |  5    patchC: changes for C - john
|/ /
o |  4    patchB: second try in B - john
| |
o |  3    patchB: start new patch on patchA - john
|/
o  2    patchA: update patch description - john
|
o  1    patchA: start new patch on default - john
|
o  0    : base - john

Check result

And our patches look just like we would like them to:

$ hg pdiff patchA
# HG changeset patch
# User john
# Date 0 0
a nifty patch
_
diff --git a/file-from-A b/file-from-A
new file mode 100644
--- /dev/null
+++ b/file-from-A
@@ -0,0 +1,1 @@
+One
diff --git a/main-file-1 b/main-file-1
--- a/main-file-1
+++ b/main-file-1
@@ -1,1 +1,1 @@
-One
+Un
$ hg pdiff patchB
# HG changeset patch
# User john
# Date 0 0
another patch
_
diff --git a/file-from-B b/file-from-B
new file mode 100644
--- /dev/null
+++ b/file-from-B
@@ -0,0 +1,1 @@
+Two
diff --git a/main-file-1 b/main-file-1
--- a/main-file-1
+++ b/main-file-1
@@ -3,1 +3,1 @@
-Two
+Deux
$ hg pdiff patchC
# HG changeset patch
# User john
# Date 0 0
yet another patch
_
diff --git a/file-from-C b/file-from-C
new file mode 100644
--- /dev/null
+++ b/file-from-C
@@ -0,0 +1,1 @@
+Three
diff --git a/main-file-1 b/main-file-1
--- a/main-file-1
+++ b/main-file-1
@@ -5,1 +5,1 @@
-Three
+Trois