[PATCH 1 of 2] rebase: add upto option for cherry picking

Stefano Tortarolo stefano.tortarolo at gmail.com
Sun Mar 29 15:28:13 CDT 2009


# HG changeset patch
# User Stefano Tortarolo <stefano.tortarolo at gmail.com>
# Date 1238332125 -7200
# Node ID 1b0dfe2121f22ca65e78541a0132f00940a9401c
# Parent  915d6b44eed31eed187f0770e185a27158a215cb
rebase: add upto option for cherry picking

Rebase now allows rebasing only some revisions.
It's very important to note that cherry picking intermediate
revisions will rebase also their parents up to the --base
revision.
That's why --source y is transformed to --base y.

Inspired by Tomasz Barszczak

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -59,6 +59,7 @@
         # Validate input and define rebasing points
         destf = opts.get('dest', None)
         srcf = opts.get('source', None)
+        uptof = opts.get('upto', None)
         basef = opts.get('base', None)
         contf = opts.get('continue')
         abortf = opts.get('abort')
@@ -74,22 +75,30 @@
             if collapsef:
                 raise error.ParseError(
                     'rebase', _('cannot use collapse with continue or abort'))
-
+            if uptof:
+                raise error.ParseError(
+                    'rebase', _('cannot use upto with continue or abort'))
             if (srcf or basef or destf):
                 raise error.ParseError('rebase',
                     _('abort and continue do not allow specifying revisions'))
 
-            (originalwd, target, state, collapsef, keepf,
-                                keepbranchesf, external) = restorestatus(repo)
+            (originalwd, target, state, collapsef, keepf, keepbranchesf,
+                                            external) = restorestatus(repo)
             if abortf:
                 abort(repo, originalwd, target, state)
                 return
         else:
+            if uptof:
+                keepf = True
+                if srcf and not basef:
+                    ui.warn(_('warning: assuming base instead of source\n'))
+                    basef = srcf
+                    srcf = None
             if srcf and basef:
                 raise error.ParseError('rebase', _('cannot specify both a '
                                                    'revision and a base'))
             cmdutil.bail_if_changed(repo)
-            result = buildstate(repo, destf, srcf, basef, collapsef)
+            result = buildstate(repo, destf, srcf, uptof, basef, collapsef)
             if result:
                 originalwd, target, state, external = result
             else: # Empty state built, nothing to rebase
@@ -210,7 +219,6 @@
     else: # we have an interrupted rebase
         repo.ui.debug(_('resuming interrupted rebase\n'))
 
-
     newrev = concludenode(repo, rev, p1, p2, state, collapse,
                           extrafn=extrafn)
 
@@ -345,7 +353,7 @@
         clearstatus(repo)
         repo.ui.status(_('rebase aborted\n'))
 
-def buildstate(repo, dest, src, base, collapse):
+def buildstate(repo, dest, src, upto, base, collapse):
     'Define which revisions are going to be rebased and where'
     targetancestors = util.set()
 
@@ -387,7 +395,14 @@
         source = min(rebasingbranch)
 
     repo.ui.debug(_('rebase onto %d starting from %d\n') % (dest, source))
-    state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
+    descendants = repo.changelog.descendants(source)
+    if upto:
+        upto = repo[upto].rev()
+        descendants = filter(lambda x: x <= upto, descendants)
+        if upto != source and upto not in descendants:
+            raise util.Abort(_('upto revision is not a descendant of source '
+                                                    'revision'))
+    state = dict.fromkeys(descendants, nullrev)
     external = nullrev
     if collapse:
         if not targetancestors:
@@ -444,9 +459,10 @@
         ('', 'collapse', False, _('collapse the rebased revisions')),
         ('', 'keep', False, _('keep original revisions')),
         ('', 'keepbranches', False, _('keep original branches')),
+        ('', 'upto', '', _('rebase up to a given revision')),
         ('c', 'continue', False, _('continue an interrupted rebase')),
         ('a', 'abort', False, _('abort an interrupted rebase')),] +
          templateopts,
         _('hg rebase [-s rev | -b rev] [-d rev] [--collapse] [--keep] '
-                            '[--keepbranches] | [-c] | [-a]')),
+                            '[--keepbranches] [--upto] | [-c] | [-a]')),
 }
diff --git a/tests/test-rebase-check-restore b/tests/test-rebase-check-restore
--- a/tests/test-rebase-check-restore
+++ b/tests/test-rebase-check-restore
@@ -30,11 +30,12 @@
     echo "D" >> A
     commit "D" 3
     addcommit "E" 4
+    addcommit "F" 5
 
     hg update -C 0
     hg branch 'notdefault'
-    echo "F" >> A
-    commit "F" 5
+    echo "G" >> A
+    commit "G" 6
 }
 
 echo
@@ -52,10 +53,10 @@
 hg glog  --template '{rev}:{desc}:{branches}\n'
 
 echo
-echo "% - Rebase F onto E - check keepbranches"
+echo "% - Rebase G onto F - check keepbranches"
 createrepo > /dev/null 2>&1
 hg glog  --template '{rev}:{desc}:{branches}\n'
-hg rebase -s 5 -d 4 --keepbranches 2>&1 | sed 's/\(saving bundle to \).*/\1/'
+hg rebase -s 6 -d 5 --keepbranches 2>&1 | sed 's/\(saving bundle to \).*/\1/'
 
 echo
 echo "% - Solve the conflict and go on"
@@ -65,4 +66,18 @@
 hg rebase --continue 2>&1 | sed 's/\(saving bundle to \).*/\1/'
 hg glog  --template '{rev}:{desc}:{branches}\n'
 
+echo
+echo "% - Cherrypick base E onto C - conflict"
+createrepo > /dev/null 2>&1
+hg glog  --template '{rev}:{desc}:{branches}\n'
+hg rebase --upto 4 -b 4 -d 2 2>&1 | sed 's/\(saving bundle to \).*/\1/'
+
+echo
+echo "% - Solve the conflict and go on - must stop at E"
+echo 'conflict solved' > A
+rm A.orig
+hg resolve -m A
+hg rebase --continue 2>&1 | sed 's/\(saving bundle to \).*/\1/'
+hg glog  --template '{rev}:{desc}:{branches}\n'
+
 exit 0
diff --git a/tests/test-rebase-check-restore.out b/tests/test-rebase-check-restore.out
--- a/tests/test-rebase-check-restore.out
+++ b/tests/test-rebase-check-restore.out
@@ -1,7 +1,9 @@
 
 % - Rebasing B onto E - check keep
-@  5:F:notdefault
+@  6:G:notdefault
 |
+| o  5:F:
+| |
 | o  4:E:
 | |
 | o  3:D:
@@ -19,11 +21,13 @@
 
 % - Solve the conflict and go on
 rebase completed
-@  7:C:
+@  8:C:
 |
-o  6:B:
+o  7:B:
 |
-| o  5:F:notdefault
+| o  6:G:notdefault
+| |
++---o  5:F:
 | |
 o |  4:E:
 | |
@@ -36,9 +40,11 @@
 o  0:A:
 
 
-% - Rebase F onto E - check keepbranches
-@  5:F:notdefault
+% - Rebase G onto F - check keepbranches
+@  6:G:notdefault
 |
+| o  5:F:
+| |
 | o  4:E:
 | |
 | o  3:D:
@@ -62,7 +68,9 @@
 adding file changes
 added 1 changesets with 1 changes to 1 files
 rebase completed
-@  5:F:notdefault
+@  6:G:notdefault
+|
+o  5:F:
 |
 o  4:E:
 |
@@ -74,3 +82,44 @@
 |/
 o  0:A:
 
+
+% - Cherrypick base E onto C - conflict
+@  6:G:notdefault
+|
+| o  5:F:
+| |
+| o  4:E:
+| |
+| o  3:D:
+|/
+| o  2:C:
+| |
+| o  1:B:
+|/
+o  0:A:
+
+merging A
+warning: conflicts during merge.
+merging A failed!
+abort: fix unresolved conflicts with hg resolve then run hg rebase --continue
+
+% - Solve the conflict and go on - must stop at E
+rebase completed
+@  8:E:
+|
+o  7:D:
+|
+| o  6:G:notdefault
+| |
+| | o  5:F:
+| | |
+| | o  4:E:
+| | |
+| | o  3:D:
+| |/
+o |  2:C:
+| |
+o |  1:B:
+|/
+o  0:A:
+
diff --git a/tests/test-rebase-cherrypicking b/tests/test-rebase-cherrypicking
new file mode 100755
--- /dev/null
+++ b/tests/test-rebase-cherrypicking
@@ -0,0 +1,62 @@
+#!/bin/sh
+
+echo "[extensions]" >> $HGRCPATH
+echo "graphlog=" >> $HGRCPATH
+echo "rebase=" >> $HGRCPATH
+
+BASE=`pwd`
+
+addcommit () {
+    echo $1 > $1
+    hg add $1
+    hg commit -d "${2} 0" -u test -m $1
+}
+
+commit () {
+    hg commit -d "${2} 0" -u test -m $1
+}
+
+createrepo () {
+    cd $BASE
+    rm -rf a
+    hg init a
+    cd a
+    addcommit "A" 0
+    addcommit "B" 1
+    addcommit "C" 2
+
+    hg update -C 0
+    addcommit "D" 3
+    addcommit "E" 4
+    addcommit "F" 5
+    addcommit "G" 6
+}
+
+echo '% Cherry picking D onto C'
+createrepo > /dev/null 2>&1
+hg glog  --template '{rev}: {desc}\n'
+hg rebase --upto 3 --base 3 -d 2 2>&1 | sed 's/\(saving bundle to \).*/\1/'
+hg glog  --template '{rev}: {desc}\n'
+echo "Expected A, B, C, D"
+hg manifest
+
+echo
+echo '% Cherry picking D, E onto C'
+echo '% (using --source, converted to --base)'
+createrepo > /dev/null 2>&1
+hg glog  --template '{rev}: {desc}\n'
+hg rebase --upto 4 -s 3 -d 2 2>&1 | sed 's/\(saving bundle to \).*/\1/'
+hg glog  --template '{rev}: {desc}\n'
+echo "Expected A, B, C, D, E"
+hg manifest
+
+echo
+echo '% Cherry picking F onto C'
+createrepo > /dev/null 2>&1
+hg glog  --template '{rev}: {desc}\n'
+hg rebase --upto 5 -s 5 -d 2 2>&1 | sed 's/\(saving bundle to \).*/\1/'
+hg glog  --template '{rev}: {desc}\n'
+echo "Expected A, B, C, D, E, F"
+hg manifest
+
+exit 0
diff --git a/tests/test-rebase-cherrypicking.out b/tests/test-rebase-cherrypicking.out
new file mode 100644
--- /dev/null
+++ b/tests/test-rebase-cherrypicking.out
@@ -0,0 +1,125 @@
+% Cherry picking D onto C
+@  6: G
+|
+o  5: F
+|
+o  4: E
+|
+o  3: D
+|
+| o  2: C
+| |
+| o  1: B
+|/
+o  0: A
+
+rebase completed
+@  7: D
+|
+| o  6: G
+| |
+| o  5: F
+| |
+| o  4: E
+| |
+| o  3: D
+| |
+o |  2: C
+| |
+o |  1: B
+|/
+o  0: A
+
+Expected A, B, C, D
+A
+B
+C
+D
+
+% Cherry picking D, E onto C
+% (using --source, converted to --base)
+@  6: G
+|
+o  5: F
+|
+o  4: E
+|
+o  3: D
+|
+| o  2: C
+| |
+| o  1: B
+|/
+o  0: A
+
+warning: assuming base instead of source
+rebase completed
+@  8: E
+|
+o  7: D
+|
+| o  6: G
+| |
+| o  5: F
+| |
+| o  4: E
+| |
+| o  3: D
+| |
+o |  2: C
+| |
+o |  1: B
+|/
+o  0: A
+
+Expected A, B, C, D, E
+A
+B
+C
+D
+E
+
+% Cherry picking F onto C
+@  6: G
+|
+o  5: F
+|
+o  4: E
+|
+o  3: D
+|
+| o  2: C
+| |
+| o  1: B
+|/
+o  0: A
+
+warning: assuming base instead of source
+rebase completed
+@  9: F
+|
+o  8: E
+|
+o  7: D
+|
+| o  6: G
+| |
+| o  5: F
+| |
+| o  4: E
+| |
+| o  3: D
+| |
+o |  2: C
+| |
+o |  1: B
+|/
+o  0: A
+
+Expected A, B, C, D, E, F
+A
+B
+C
+D
+E
+F
diff --git a/tests/test-rebase-parameters.out b/tests/test-rebase-parameters.out
--- a/tests/test-rebase-parameters.out
+++ b/tests/test-rebase-parameters.out
@@ -2,7 +2,7 @@
 
 % Use continue and abort
 hg rebase: cannot use both abort and continue
-hg rebase [-s rev | -b rev] [-d rev] [--collapse] [--keep] [--keepbranches] | [-c] | [-a]
+hg rebase [-s rev | -b rev] [-d rev] [--collapse] [--keep] [--keepbranches] [--upto] | [-c] | [-a]
 
 move changeset (and descendants) to a different branch
 
@@ -21,6 +21,7 @@
     --collapse      collapse the rebased revisions
     --keep          keep original revisions
     --keepbranches  keep original branches
+    --upto          rebase up to a given revision
  -c --continue      continue an interrupted rebase
  -a --abort         abort an interrupted rebase
     --style         display using template map file
@@ -30,7 +31,7 @@
 
 % Use continue and collapse
 hg rebase: cannot use collapse with continue or abort
-hg rebase [-s rev | -b rev] [-d rev] [--collapse] [--keep] [--keepbranches] | [-c] | [-a]
+hg rebase [-s rev | -b rev] [-d rev] [--collapse] [--keep] [--keepbranches] [--upto] | [-c] | [-a]
 
 move changeset (and descendants) to a different branch
 
@@ -49,6 +50,7 @@
     --collapse      collapse the rebased revisions
     --keep          keep original revisions
     --keepbranches  keep original branches
+    --upto          rebase up to a given revision
  -c --continue      continue an interrupted rebase
  -a --abort         abort an interrupted rebase
     --style         display using template map file
@@ -58,7 +60,7 @@
 
 % Use continue/abort and dest/source
 hg rebase: abort and continue do not allow specifying revisions
-hg rebase [-s rev | -b rev] [-d rev] [--collapse] [--keep] [--keepbranches] | [-c] | [-a]
+hg rebase [-s rev | -b rev] [-d rev] [--collapse] [--keep] [--keepbranches] [--upto] | [-c] | [-a]
 
 move changeset (and descendants) to a different branch
 
@@ -77,6 +79,7 @@
     --collapse      collapse the rebased revisions
     --keep          keep original revisions
     --keepbranches  keep original branches
+    --upto          rebase up to a given revision
  -c --continue      continue an interrupted rebase
  -a --abort         abort an interrupted rebase
     --style         display using template map file
@@ -86,7 +89,7 @@
 
 % Use source and base
 hg rebase: cannot specify both a revision and a base
-hg rebase [-s rev | -b rev] [-d rev] [--collapse] [--keep] [--keepbranches] | [-c] | [-a]
+hg rebase [-s rev | -b rev] [-d rev] [--collapse] [--keep] [--keepbranches] [--upto] | [-c] | [-a]
 
 move changeset (and descendants) to a different branch
 
@@ -105,6 +108,7 @@
     --collapse      collapse the rebased revisions
     --keep          keep original revisions
     --keepbranches  keep original branches
+    --upto          rebase up to a given revision
  -c --continue      continue an interrupted rebase
  -a --abort         abort an interrupted rebase
     --style         display using template map file


More information about the Mercurial-devel mailing list