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 patch-C
|
o patch-B
|
o patch-A
|
@ default
$ hg update patch-C
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
@@ -1,0 +1,1 @@
+Null
@@ -6,0 +7,1 @@
+Vier
Multiple Patches
We’d like to commit these two lines to two new patches, patch-D and patch-E. 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 patch-D
marked working directory as branch patch-D
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
@@ -1,0 +1,1 @@
+Null
@@ -6,0 +7,1 @@
+Vier
But it does start the new patch:
$ hg log -l1
8 patch-D: start new patch on patch-C - john
$ hg pgraph
@ patch-D
|
o patch-C
|
o patch-B
|
o patch-A
|
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 patch-E (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 patch-C
|
o patch-B
|
o patch-A
|
@ default
$ hg update patch-C
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, patch-C 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 patch-C:
$ hg commit --message "German to French"
Backport to B from C
Then we backport the change to patch-B. We first just go there:
$ hg update patch-B
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 patch-C
$ 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 patch-B 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 patch-B"
created new head
Backport to A from B
Now we move down to patch-A. The new head created above does not get merged yet. It will get merged once we move back up from patch-B to patch-C at the end. So let’s move down:
$ hg update patch-A
1 files updated, 0 files merged, 3 files removed, 0 files unresolved
Now we backport from patch-B. This is better than backporting from patch-C again, because otherwise we would have to get rid of the change to “Three” again:
$ hg reapply patch-B
$ 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 patch-A"
created new head
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 patch-C, merging the new heads:
$ hg update patch-C
7 files updated, 0 files merged, 0 files removed, 0 files unresolved
needs merge with patch-A (through patch-B)
needs merge with patch-B
needs update of diff base to tip of patch-B
use 'hg pmerge'
$ hg pmerge
updating to patch-A
1 files updated, 0 files merged, 6 files removed, 0 files unresolved
patch-B: merging from patch-A
marked working directory as branch patch-B
merging main-file-1
3 files updated, 1 files merged, 0 files removed, 0 files unresolved
patch-C: merging from patch-B
marked working directory as branch patch-C
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 patch-C: merge of patch-B - john
|\
| o 11 patch-B: merge of patch-A - john
| |\
| | o 10 patch-A: backported German to French to patch-A - john
| | |
| o | 9 patch-B: backported German to French to patch-B - john
| | |
o | | 8 patch-C: German to French - john
| | |
o | | 7 patch-C: update patch description - john
| | |
o | | 6 patch-C: update patch dependencies - john
| | |
o | | 5 patch-C: changes for C - john
|/ /
o | 4 patch-B: second try in B - john
| |
o | 3 patch-B: start new patch on patch-A - john
|/
o 2 patch-A: update patch description - john
|
o 1 patch-A: start new patch on default - john
|
o 0 : base - john
Check result
The patches now look as expected:
$ hg pdiff patch-A
# 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 patch-B
# 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 patch-C
# 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 patch-A before merging heads:
$ cd ../hunksfixed
$ hg pgraph # ensure the patch graph is up to date
created graph description from current tips
o patch-C
|
o patch-B
|
o patch-A
|
@ default
$ hg update patch-A
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 patch-A"
Merge fix into B
When we now try to merge back into patch-C, the attempt to merge into patch-B in between already fails:
$ hg update patch-C
7 files updated, 0 files merged, 0 files removed, 0 files unresolved
needs merge with patch-A (through patch-B)
needs merge with patch-B
needs update of diff base to tip of patch-B
use 'hg pmerge'
$ hg pmerge
updating to patch-A
1 files updated, 0 files merged, 6 files removed, 0 files unresolved
patch-B: merging from patch-A
marked working directory as branch patch-B
merging main-file-1
warning: conflicts during merge.
merging main-file-1 failed!
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 patch-A is not related usefully to patch-B, so the merge cannot handle it automatically:
$ hg glog
@ 11 patch-A: fixed typo in patch-A - john
|
o 10 patch-A: backported German to French to patch-A - john
|
| @ 9 patch-B: backported German to French to patch-B - john
| |
| | o 8 patch-C: German to French - john
| | |
| | o 7 patch-C: update patch description - john
| | |
| | o 6 patch-C: update patch dependencies - john
| | |
| | o 5 patch-C: changes for C - john
| |/
| o 4 patch-B: second try in B - john
| |
| o 3 patch-B: start new patch on patch-A - john
|/
o 2 patch-A: update patch description - john
|
o 1 patch-A: 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
patch-B: committing current merge from patch-A
Merge fix into C
This took care of patch-B. Now, unfortunately, we have to repeat this for patch-C. The merge in patch-B is not usefully related to patch-C:
$ hg glog
@ 12 patch-B: merge of patch-A - john
|\
| o 11 patch-A: fixed typo in patch-A - john
| |
| o 10 patch-A: backported German to French to patch-A - john
| |
o | 9 patch-B: backported German to French to patch-B - john
| |
| | o 8 patch-C: German to French - john
| | |
| | o 7 patch-C: update patch description - john
| | |
| | o 6 patch-C: update patch dependencies - john
| | |
+---o 5 patch-C: changes for C - john
| |
o | 4 patch-B: second try in B - john
| |
o | 3 patch-B: start new patch on patch-A - john
|/
o 2 patch-A: update patch description - john
|
o 1 patch-A: start new patch on default - john
|
o 0 : base - john
So indeed we get:
$ hg update patch-C
5 files updated, 0 files merged, 0 files removed, 0 files unresolved
needs merge with patch-B
needs update of diff base to tip of patch-B
use 'hg pmerge'
$ hg pmerge
updating to patch-B
2 files updated, 0 files merged, 3 files removed, 0 files unresolved
patch-C: merging from patch-B
marked working directory as branch patch-C
merging main-file-1
warning: conflicts during merge.
merging main-file-1 failed!
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
patch-C: committing current merge from patch-B
Big picture
Here’s what happened in full:
$ hg glog
@ 13 patch-C: merge of patch-B - john
|\
| o 12 patch-B: merge of patch-A - john
| |\
| | o 11 patch-A: fixed typo in patch-A - john
| | |
| | o 10 patch-A: backported German to French to patch-A - john
| | |
| o | 9 patch-B: backported German to French to patch-B - john
| | |
o | | 8 patch-C: German to French - john
| | |
o | | 7 patch-C: update patch description - john
| | |
o | | 6 patch-C: update patch dependencies - john
| | |
o | | 5 patch-C: changes for C - john
|/ /
o | 4 patch-B: second try in B - john
| |
o | 3 patch-B: start new patch on patch-A - john
|/
o 2 patch-A: update patch description - john
|
o 1 patch-A: 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 patch-A
# 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 patch-B
# 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 patch-C
# 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