rebase: fix selection of base used when rebasing merge (
issue4041)
Prior this changeset, rebasing a merge whose first parent was not in
the rebase lead to wrong and highly conflicting merge. See the in-line
comment for details.
Test have been updated with the data provided by the reported.
--- a/hgext/rebase.py Tue Oct 29 21:54:49 2013 +0200
+++ b/hgext/rebase.py Wed Oct 30 19:45:14 2013 +0100
@@ -447,9 +447,44 @@
repo.ui.debug(" already in target\n")
repo.dirstate.write()
repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
- base = None
- if repo[rev].rev() != repo[min(state)].rev():
+ if repo[rev].rev() == repo[min(state)].rev():
+ # Case (1) initial changeset of a non-detaching rebase.
+ # Let the merge mechanism find the base itself.
+ base = None
+ elif not repo[rev].p2():
+ # Case (2) detaching the node with a single parent, use this parent
base = repo[rev].p1().node()
+ else:
+ # In case of merge, we need to pick the right parent as merge base.
+ #
+ # Imagine we have:
+ # - M: currently rebase revision in this step
+ # - A: one parent of M
+ # - B: second parent of M
+ # - D: destination of this merge step (p1 var)
+ #
+ # If we are rebasing on D, D is the successors of A or B. The right
+ # merge base is the one D succeed to. We pretend it is B for the rest
+ # of this comment
+ #
+ # If we pick B as the base, the merge involves:
+ # - changes from B to M (actual changeset payload)
+ # - changes from B to D (induced by rebase) as D is a rebased
+ # version of B)
+ # Which exactly represent the rebase operation.
+ #
+ # If we pick the A as the base, the merge involves
+ # - changes from A to M (actual changeset payload)
+ # - changes from A to D (with include changes between unrelated A and B
+ # plus changes induced by rebase)
+ # Which does not represent anything sensible and creates a lot of
+ # conflicts.
+ for p in repo[rev].parents():
+ if state.get(p.rev()) == repo[p1].rev():
+ base = p.node()
+ break
+ if base is not None:
+ repo.ui.debug(" detach base %d:%s\n" % (repo[base].rev(), repo[base]))
# When collapsing in-place, the parent is the common ancestor, we
# have to allow merging with it.
return merge.update(repo, rev, True, True, False, base, collapse)
Binary file tests/bundles/issue4041.hg has changed
--- a/tests/test-rebase-conflicts.t Tue Oct 29 21:54:49 2013 +0200
+++ b/tests/test-rebase-conflicts.t Wed Oct 30 19:45:14 2013 +0100
@@ -124,3 +124,184 @@
* mybook 5:d67b21408fc0
$ cd ..
+
+Check that the right ancestors is used while rebasing a merge (issue4041)
+
+ $ hg clone "$TESTDIR/bundles/issue4041.hg" issue4041
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 11 changesets with 8 changes to 3 files (+1 heads)
+ updating to branch default
+ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ cd issue4041
+ $ hg phase --draft --force 9
+ $ hg log -G
+ o changeset: 10:2f2496ddf49d
+ |\ branch: f1
+ | | tag: tip
+ | | parent: 7:4c9fbe56a16f
+ | | parent: 9:e31216eec445
+ | | user: szhang
+ | | date: Thu Sep 05 12:59:39 2013 -0400
+ | | summary: merge
+ | |
+ | o changeset: 9:e31216eec445
+ | | branch: f1
+ | | user: szhang
+ | | date: Thu Sep 05 12:59:10 2013 -0400
+ | | summary: more changes to f1
+ | |
+ | o changeset: 8:8e4e2c1a07ae
+ | |\ branch: f1
+ | | | parent: 2:4bc80088dc6b
+ | | | parent: 6:400110238667
+ | | | user: szhang
+ | | | date: Thu Sep 05 12:57:59 2013 -0400
+ | | | summary: bad merge
+ | | |
+ o | | changeset: 7:4c9fbe56a16f
+ |/ / branch: f1
+ | | parent: 2:4bc80088dc6b
+ | | user: szhang
+ | | date: Thu Sep 05 12:54:00 2013 -0400
+ | | summary: changed f1
+ | |
+ | o changeset: 6:400110238667
+ | | branch: f2
+ | | parent: 4:12e8ec6bb010
+ | | user: szhang
+ | | date: Tue Sep 03 13:58:02 2013 -0400
+ | | summary: changed f2 on f2
+ | |
+ | | @ changeset: 5:d79e2059b5c0
+ | | | parent: 3:8a951942e016
+ | | | user: szhang
+ | | | date: Tue Sep 03 13:57:39 2013 -0400
+ | | | summary: changed f2 on default
+ | | |
+ | o | changeset: 4:12e8ec6bb010
+ | |/ branch: f2
+ | | user: szhang
+ | | date: Tue Sep 03 13:57:18 2013 -0400
+ | | summary: created f2 branch
+ | |
+ | o changeset: 3:8a951942e016
+ | | parent: 0:24797d4f68de
+ | | user: szhang
+ | | date: Tue Sep 03 13:57:11 2013 -0400
+ | | summary: added f2.txt
+ | |
+ o | changeset: 2:4bc80088dc6b
+ | | branch: f1
+ | | user: szhang
+ | | date: Tue Sep 03 13:56:20 2013 -0400
+ | | summary: added f1.txt
+ | |
+ o | changeset: 1:ef53c9e6b608
+ |/ branch: f1
+ | user: szhang
+ | date: Tue Sep 03 13:55:26 2013 -0400
+ | summary: created f1 branch
+ |
+ o changeset: 0:24797d4f68de
+ user: szhang
+ date: Tue Sep 03 13:55:08 2013 -0400
+ summary: added default.txt
+
+ $ hg rebase -s9 -d2 --debug # use debug to really check merge base used
+ rebase onto 2 starting from [<changectx e31216eec445>]
+ rebasing: 9:e31216eec445 5/6 changesets (83.33%)
+ future parents are 2 and -1
+ rebase status stored
+ update to 2:4bc80088dc6b
+ resolving manifests
+ branchmerge: False, force: True, partial: False
+ ancestor: d79e2059b5c0+, local: d79e2059b5c0+, remote: 4bc80088dc6b
+ f2.txt: other deleted -> r
+ f1.txt: remote created -> g
+ removing f2.txt
+ updating: f2.txt 1/2 files (50.00%)
+ getting f1.txt
+ updating: f1.txt 2/2 files (100.00%)
+ merge against 9:e31216eec445
+ detach base 8:8e4e2c1a07ae
+ searching for copies back to rev 3
+ resolving manifests
+ branchmerge: True, force: True, partial: False
+ ancestor: 8e4e2c1a07ae, local: 4bc80088dc6b+, remote: e31216eec445
+ f1.txt: remote is newer -> g
+ getting f1.txt
+ updating: f1.txt 1/1 files (100.00%)
+ f1.txt
+ rebasing: 10:2f2496ddf49d 6/6 changesets (100.00%)
+ future parents are 11 and 7
+ rebase status stored
+ already in target
+ merge against 10:2f2496ddf49d
+ detach base 9:e31216eec445
+ searching for copies back to rev 3
+ resolving manifests
+ branchmerge: True, force: True, partial: False
+ ancestor: e31216eec445, local: 19c888675e13+, remote: 2f2496ddf49d
+ f1.txt: remote is newer -> g
+ getting f1.txt
+ updating: f1.txt 1/1 files (100.00%)
+ f1.txt
+ rebase merging completed
+ update back to initial working directory parent
+ resolving manifests
+ branchmerge: False, force: False, partial: False
+ ancestor: 2a7f09cac94c, local: 2a7f09cac94c+, remote: d79e2059b5c0
+ f1.txt: other deleted -> r
+ f2.txt: remote created -> g
+ removing f1.txt
+ updating: f1.txt 1/2 files (50.00%)
+ getting f2.txt
+ updating: f2.txt 2/2 files (100.00%)
+ 3 changesets found
+ list of changesets:
+ 4c9fbe56a16f30c0d5dcc40ec1a97bbe3325209c
+ e31216eec445e44352c5f01588856059466a24c9
+ 2f2496ddf49d69b5ef23ad8cf9fb2e0e4faf0ac2
+ bundling: 1/3 changesets (33.33%)
+ bundling: 2/3 changesets (66.67%)
+ bundling: 3/3 changesets (100.00%)
+ bundling: 1/3 manifests (33.33%)
+ bundling: 2/3 manifests (66.67%)
+ bundling: 3/3 manifests (100.00%)
+ bundling: f1.txt 1/1 files (100.00%)
+ saved backup bundle to $TESTTMP/issue4041/.hg/strip-backup/e31216eec445-backup.hg (glob)
+ 3 changesets found
+ list of changesets:
+ 4c9fbe56a16f30c0d5dcc40ec1a97bbe3325209c
+ 19c888675e133ab5dff84516926a65672eaf04d9
+ 2a7f09cac94c7f4b73ebd5cd1a62d3b2e8e336bf
+ bundling: 1/3 changesets (33.33%)
+ bundling: 2/3 changesets (66.67%)
+ bundling: 3/3 changesets (100.00%)
+ bundling: 1/3 manifests (33.33%)
+ bundling: 2/3 manifests (66.67%)
+ bundling: 3/3 manifests (100.00%)
+ bundling: f1.txt 1/1 files (100.00%)
+ adding branch
+ adding changesets
+ changesets: 1 chunks
+ add changeset 4c9fbe56a16f
+ changesets: 2 chunks
+ add changeset 19c888675e13
+ changesets: 3 chunks
+ add changeset 2a7f09cac94c
+ adding manifests
+ manifests: 1/2 chunks (50.00%)
+ manifests: 2/2 chunks (100.00%)
+ manifests: 3/2 chunks (150.00%)
+ adding file changes
+ adding f1.txt revisions
+ files: 1/1 chunks (100.00%)
+ added 2 changesets with 2 changes to 1 files
+ removing unknown node e31216eec445 from 1-phase boundary
+ invalid branchheads cache (served): tip differs
+ rebase completed
+ updating the branch cache