changeset 7278:45495d784ad6

rebase: avoid redundant merges (issue1301)
author Stefano Tortarolo <stefano.tortarolo@gmail.com>
date Tue, 16 Sep 2008 17:51:14 +0200
parents 3e000e2bf5f6
children ddab62b2fec4
files hgext/rebase.py tests/test-rebase-collapse tests/test-rebase-collapse.out tests/test-rebase-conflicts tests/test-rebase-conflicts.out
diffstat 5 files changed, 298 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/rebase.py	Mon Oct 06 14:41:22 2008 +0200
+++ b/hgext/rebase.py	Tue Sep 16 17:51:14 2008 +0200
@@ -13,12 +13,31 @@
 http://www.selenic.com/mercurial/wiki/index.cgi/RebaseProject
 '''
 
-from mercurial import util, repair, merge, cmdutil, dispatch, commands, extensions
+from mercurial import util, repair, merge, cmdutil, dispatch, commands
+from mercurial import extensions, ancestor
 from mercurial.commands import templateopts
 from mercurial.node import nullrev
 from mercurial.i18n import _
 import os, errno
 
+def rebasemerge(repo, rev, first=False):
+    'return the correct ancestor'
+    oldancestor = ancestor.ancestor
+    
+    def newancestor(a, b, pfunc):
+        ancestor.ancestor = oldancestor
+        anc = ancestor.ancestor(a, b, pfunc)
+        if b == rev:
+            return repo[rev].parents()[0].rev()
+        return ancestor.ancestor(a, b, pfunc)
+
+    if not first:
+        ancestor.ancestor = newancestor
+    else:
+        repo.ui.debug(_("First revision, do not change ancestor\n"))
+    stats = merge.update(repo, rev, True, True, False)
+    return stats
+
 def rebase(ui, repo, **opts):
     """move changeset (and descendants) to a different branch
 
@@ -116,10 +135,12 @@
     """Skip commit if collapsing has been required and rev is not the last
     revision, commit otherwise
     """
-    repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
+    repo.ui.debug(_(" set parents\n"))
+    if collapse and not last:
+        repo.dirstate.setparents(repo[p1].node())
+        return None
 
-    if collapse and not last:
-        return None
+    repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
 
     # Commit, record the old nodeid
     m, a, r = repo.status()[:3]
@@ -147,16 +168,25 @@
 
 def rebasenode(repo, rev, target, state, skipped, targetancestors, collapse):
     'Rebase a single revision'
-    repo.ui.debug(_("rebasing %d:%s\n") % (rev, repo[rev].node()))
+    repo.ui.debug(_("rebasing %d:%s\n") % (rev, repo[rev]))
 
     p1, p2 = defineparents(repo, rev, target, state, targetancestors)
 
+    repo.ui.debug(_(" future parents are %d and %d\n") % (repo[p1].rev(), 
+                                                            repo[p2].rev()))
+    
     # Merge phase
     if len(repo.parents()) != 2:
         # Update to target and merge it with local
-        merge.update(repo, p1, False, True, False)
+        if repo['.'].rev() != repo[p1].rev():
+            repo.ui.debug(_(" update to %d:%s\n") % (repo[p1].rev(), repo[p1]))
+            merge.update(repo, p1, False, True, False)
+        else:
+            repo.ui.debug(_(" already in target\n"))
         repo.dirstate.write()
-        stats = merge.update(repo, rev, True, False, False)
+        repo.ui.debug(_(" merge against %d:%s\n") % (repo[rev].rev(), repo[rev]))
+        first = repo[rev].rev() == repo[min(state)].rev()
+        stats = rebasemerge(repo, rev, first)
 
         if stats[3] > 0:
             raise util.Abort(_('fix unresolved conflicts with hg resolve then '
--- a/tests/test-rebase-collapse	Mon Oct 06 14:41:22 2008 +0200
+++ b/tests/test-rebase-collapse	Tue Sep 16 17:51:14 2008 +0200
@@ -41,15 +41,20 @@
 
 createrepo > /dev/null 2>&1
 hg glog  --template '{rev}: {desc}\n'
-echo '% Rebasing'
+echo '% Rebasing B onto H'
 hg up -C 3
 hg rebase --collapse 2>&1 | sed 's/\(saving bundle to \).*/\1/'
 hg glog  --template '{rev}: {desc}\n'
+echo "Expected A, B, C, D, F, H"
+hg manifest
 
 createrepo > /dev/null 2>&1
-echo '% Rebasing'
+echo
+echo '% Rebasing G onto H'
 hg rebase --base 6 --collapse 2>&1 | sed 's/\(saving bundle to \).*/\1/'
 hg glog  --template '{rev}: {desc}\n'
+echo "Expected A, E, F, H"
+hg manifest
 
 createrepocomplex () {
     cd $BASE
@@ -76,6 +81,7 @@
     addcommit "H" 7
 }
 
+echo
 createrepocomplex > /dev/null 2>&1
 hg glog  --template '{rev}: {desc}\n'
 
@@ -84,8 +90,91 @@
 hg rebase -s 2 --collapse
 
 echo
-echo '% Rebase and collapse'
+echo '% Rebase and collapse - E onto H'
 hg rebase -s 4 --collapse 2>&1 | sed 's/\(saving bundle to \).*/\1/'
 hg glog  --template '{rev}: {desc}\n'
+echo "Expected A, B, C, E, F, H"
+hg manifest
 
+createrepocomplex () {
+    cd $BASE
+    rm -rf a
+    hg init a
+    cd a
+    addcommit "A" 0
+    addcommit "B" 1
+
+    hg up 0
+    addcommit "C" 2
+    hg merge
+    commit "D" 3
+
+    hg up 1
+    addcommit "E" 4
+    
+    echo "F" > E
+    commit "F" 5
+
+    addcommit "G" 6
+
+    hg merge
+    commit "H" 7
+
+    hg up 0
+    addcommit "I" 8
+}
+
+echo
+createrepocomplex > /dev/null 2>&1
+hg glog  --template '{rev}: {desc}\n'
+
+echo
+echo '% Rebase and collapse - E onto I'
+hg rebase -s 4 --collapse 
+
+echo '% Fix conflict and continue'
+echo 'Resolved merge' > E
+hg resolve -m E
+hg rebase -c 2>&1 | sed 's/\(saving bundle to \).*/\1/'
+
+hg glog  --template '{rev}: {desc}\n'
+
+echo "Expected A, B, C, E, G, I"
+hg manifest
+
+echo 'Cat E:'
+cat E
+
+createrepocomplex () {
+    cd $BASE
+    rm -rf a
+    hg init a
+    cd a
+    addcommit "A" 0
+    addcommit "B" 1
+
+    addcommit "C" 2
+    hg up 1
+
+    addcommit "D" 3
+    
+    hg merge
+    commit "E" 4
+
+    hg up 0
+    addcommit "F" 5
+}
+
+echo
+createrepocomplex > /dev/null 2>&1
+hg glog  --template '{rev}: {desc}\n'
+
+echo
+echo '% Rebase and collapse - B onto F'
+hg rebase -s 1 --collapse 2>&1 | sed 's/\(saving bundle to \).*/\1/'
+
+hg glog  --template '{rev}: {desc}\n'
+
+echo "Expected A, B, C, D, F"
+hg manifest
 exit 0
--- a/tests/test-rebase-collapse.out	Mon Oct 06 14:41:22 2008 +0200
+++ b/tests/test-rebase-collapse.out	Tue Sep 16 17:51:14 2008 +0200
@@ -14,7 +14,7 @@
 |/
 o  0: A
 
-% Rebasing
+% Rebasing B onto H
 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
 saving bundle to 
 adding branch
@@ -37,7 +37,15 @@
 |/
 o  0: A
 
-% Rebasing
+Expected A, B, C, D, F, H
+A
+B
+C
+D
+F
+H
+
+% Rebasing G onto H
 saving bundle to 
 adding branch
 adding changesets
@@ -60,6 +68,12 @@
 |/
 o  0: A
 
+Expected A, E, F, H
+A
+E
+F
+H
+
 @  7: H
 |
 | o    6: G
@@ -80,7 +94,7 @@
 % Rebase and collapse - more than one external (fail)
 abort: unable to collapse, there is more than one external parent
 
-% Rebase and collapse
+% Rebase and collapse - E onto H
 saving bundle to 
 adding branch
 adding changesets
@@ -102,3 +116,104 @@
 |/
 o  0: A
 
+Expected A, B, C, E, F, H
+A
+B
+C
+E
+F
+H
+
+@  8: I
+|
+| o    7: H
+| |\
+| | o  6: G
+| | |
+| | o  5: F
+| | |
+| | o  4: E
+| | |
+| o |  3: D
+| |\|
+| o |  2: C
+|/ /
+| o  1: B
+|/
+o  0: A
+
+
+% Rebase and collapse - E onto I
+merging E
+warning: conflicts during merge.
+merging E failed!
+abort: fix unresolved conflicts with hg resolve then run hg rebase --continue
+% Fix conflict and continue
+saving bundle to 
+adding branch
+adding changesets
+adding manifests
+adding file changes
+added 2 changesets with 3 changes to 3 files
+rebase completed
+@    5: Collapsed revision
+|\   * E
+| |  * F
+| |  * G
+| |  * H
+| o  4: I
+| |
+o |    3: D
+|\ \
+| o |  2: C
+| |/
+o /  1: B
+|/
+o  0: A
+
+Expected A, B, C, E, G, I
+A
+B
+C
+E
+G
+I
+Cat E:
+Resolved merge
+
+@  5: F
+|
+| o    4: E
+| |\
+| | o  3: D
+| | |
+| o |  2: C
+| |/
+| o  1: B
+|/
+o  0: A
+
+
+% Rebase and collapse - B onto F
+saving bundle to 
+adding branch
+adding changesets
+adding manifests
+adding file changes
+added 2 changesets with 4 changes to 4 files
+rebase completed
+@  2: Collapsed revision
+|  * B
+|  * C
+|  * D
+|  * E
+o  1: F
+|
+o  0: A
+
+Expected A, B, C, D, F
+A
+B
+C
+D
+F
--- a/tests/test-rebase-conflicts	Mon Oct 06 14:41:22 2008 +0200
+++ b/tests/test-rebase-conflicts	Tue Sep 16 17:51:14 2008 +0200
@@ -31,6 +31,10 @@
 mv common.new common
 hg commit -d '4 0' -u test -m "L2"
 
+echo 'l3' >> extra2
+hg add extra2
+hg commit -d '5 0' -u test -m "L3"
+
 hg glog  --template '{rev}: {desc}\n'
 
 echo
@@ -47,9 +51,28 @@
 
 echo
 echo '% Conclude rebase'
-echo 'solved merge' >common
+echo 'resolved merge' >common
 hg resolve -m common
 hg rebase --continue 2>&1 | cleanoutput
 
+hg glog  --template '{rev}: {desc}\n'
 
-hg glog  --template '{rev}: {desc}\n'
+echo
+echo '% Check correctness'
+echo '  - Rev. 0'
+hg cat -r 0 common
+
+echo '  - Rev. 1'
+hg cat -r 1 common
+
+echo '  - Rev. 2'
+hg cat -r 2 common
+
+echo '  - Rev. 3'
+hg cat -r 3 common
+
+echo '  - Rev. 4'
+hg cat -r 4 common
+
+echo '  - Rev. 5'
+hg cat -r 5 common
--- a/tests/test-rebase-conflicts.out	Mon Oct 06 14:41:22 2008 +0200
+++ b/tests/test-rebase-conflicts.out	Tue Sep 16 17:51:14 2008 +0200
@@ -1,6 +1,8 @@
 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 created new head
-@  4: L2
+@  5: L3
+|
+o  4: L2
 |
 o  3: L1
 |
@@ -29,9 +31,11 @@
 adding changesets
 adding manifests
 adding file changes
-added 2 changesets with 2 changes to 2 files
+added 3 changesets with 3 changes to 3 files
 rebase completed
-@  4: L2
+@  5: L3
+|
+o  4: L2
 |
 o  3: L1
 |
@@ -41,3 +45,22 @@
 |
 o  0: C1
 
+
+% Check correctness
+  - Rev. 0
+c1
+  - Rev. 1
+c1
+c2
+  - Rev. 2
+c1
+c2
+c3
+  - Rev. 3
+c1
+c2
+c3
+  - Rev. 4
+resolved merge
+  - Rev. 5
+resolved merge