changeset 15270:6cb6064f1d50

rebase: add --rev option to rebase This option allow a strict set of revision to be specified instead of using -s or -b. Rebase will refuse start if striping rebased changeset will strip non rebased changeset. Rebase will refuse to work on set with multiple root.
author Pierre-Yves David <pierre-yves.david@ens-lyon.org>
date Sat, 15 Oct 2011 20:12:32 +0200
parents b12362ab13e7
children 84d4a4ce45fd
files hgext/rebase.py tests/bundles/rebase-revset.hg tests/test-rebase-parameters.t tests/test-rebase-scenario-global.t
diffstat 4 files changed, 269 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/rebase.py	Sat Oct 15 12:57:47 2011 -0500
+++ b/hgext/rebase.py	Sat Oct 15 20:12:32 2011 +0200
@@ -15,7 +15,7 @@
 '''
 
 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
-from mercurial import extensions, patch
+from mercurial import extensions, patch, scmutil
 from mercurial.commands import templateopts
 from mercurial.node import nullrev
 from mercurial.lock import release
@@ -34,6 +34,9 @@
      _('rebase from the base of the specified changeset '
        '(up to greatest common ancestor of base and dest)'),
      _('REV')),
+    ('r', 'rev', [],
+     _('rebase these revisions'),
+     _('REV')),
     ('d', 'dest', '',
      _('rebase onto the specified changeset'), _('REV')),
     ('', 'collapse', False, _('collapse the rebased changesets')),
@@ -119,6 +122,7 @@
         destf = opts.get('dest', None)
         srcf = opts.get('source', None)
         basef = opts.get('base', None)
+        revf = opts.get('rev', [])
         contf = opts.get('continue')
         abortf = opts.get('abort')
         collapsef = opts.get('collapse', False)
@@ -156,7 +160,13 @@
         else:
             if srcf and basef:
                 raise util.Abort(_('cannot specify both a '
+                                   'source and a base'))
+            if revf and basef:
+                raise util.Abort(_('cannot specify both a'
                                    'revision and a base'))
+            if revf and srcf:
+                raise util.Abort(_('cannot specify both a'
+                                   'revision and a source'))
             if detachf:
                 if not srcf:
                     raise util.Abort(
@@ -167,24 +177,39 @@
             cmdutil.bailifchanged(repo)
 
             if not destf:
-                # Destination defaults to the latest revision in the current branch
+                # Destination defaults to the latest revision in the
+                # current branch
                 branch = repo[None].branch()
                 dest = repo[branch]
             else:
                 dest = repo[destf]
 
+            rebaseset = None
             if srcf:
                 revsetargs = ('(%r)::', srcf)
+            elif revf:
+                rebaseset = scmutil.revrange(repo, revf)
+                if not keepf and rebaseset:
+                    try:
+                        repo.set('children(%ld) - %ld',
+                                 rebaseset, rebaseset).next()
+                    except StopIteration:
+                        pass # empty revset is what we look for
+                    else:
+                        msg = _("can't remove original changesets with"
+                                " unrebased descendants")
+                        hint = _('use --keep to keep original changesets')
+                        raise util.Abort(msg, hint=hint)
             else:
                 base = basef or '.'
                 revsetargs = ('(children(ancestor(%r, %d)) and ::(%r))::',
                              base, dest, base)
-
-            rebaseset = [c.rev() for c in repo.set(*revsetargs)]
+            if rebaseset is None:
+                rebaseset = [c.rev() for c in repo.set(*revsetargs)]
             if rebaseset:
                 result = buildstate(repo, dest, rebaseset, detachf)
             else:
-                repo.ui.debug(_('base is ancestor of destination'))
+                repo.ui.debug('base is ancestor of destination')
                 result = None
             if not result:
                 # Empty state built, nothing to rebase
@@ -545,9 +570,9 @@
     detachset = set()
     roots = list(repo.set('roots(%ld)', rebaseset))
     if not roots:
-        raise util.Abort( _('no matching revisions'))
+        raise util.Abort(_('no matching revisions'))
     if len(roots) > 1:
-        raise util.Abort( _("can't rebase multiple roots"))
+        raise util.Abort(_("can't rebase multiple roots"))
     root = roots[0]
 
     commonbase = root.ancestor(dest)
Binary file tests/bundles/rebase-revset.hg has changed
--- a/tests/test-rebase-parameters.t	Sat Oct 15 12:57:47 2011 -0500
+++ b/tests/test-rebase-parameters.t	Sat Oct 15 20:12:32 2011 +0200
@@ -67,7 +67,7 @@
   [255]
 
   $ hg rebase --base 5 --source 4
-  abort: cannot specify both a revision and a base
+  abort: cannot specify both a source and a base
   [255]
 
   $ hg rebase
--- a/tests/test-rebase-scenario-global.t	Sat Oct 15 12:57:47 2011 -0500
+++ b/tests/test-rebase-scenario-global.t	Sat Oct 15 20:12:32 2011 +0200
@@ -269,4 +269,240 @@
   |/
   o  0: 'A'
   
+  $ cd ..
 
+Test for revset
+
+We need a bit different graph
+All destination are B
+
+  $ hg init ah
+  $ cd ah
+  $ hg unbundle $TESTDIR/bundles/rebase-revset.hg
+  adding changesets
+  adding manifests
+  adding file changes
+  added 9 changesets with 9 changes to 9 files (+2 heads)
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+  $ hg tglog
+  o  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'
+  
+  $ cd ..
+
+
+Simple case with keep:
+
+Source on have two descendant heads but ask for one
+
+  $ hg clone -q -u . ah ah1
+  $ cd ah1
+  $ hg rebase -r '2::8' -d 1
+  abort: can't remove original changesets with unrebased descendants
+  (use --keep to keep original changesets)
+  [255]
+  $ hg rebase -r '2::8' -d 1 --keep
+  $ hg tglog
+  @  13: 'I'
+  |
+  o  12: 'H'
+  |
+  o  11: 'G'
+  |
+  o  10: 'D'
+  |
+  o  9: 'C'
+  |
+  | o  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'
+  
+
+  $ cd ..
+
+Base on have one descendant heads we ask for but common ancestor have two
+
+  $ hg clone -q -u . ah ah2
+  $ cd ah2
+  $ hg rebase -r '3::8' -d 1
+  abort: can't remove original changesets with unrebased descendants
+  (use --keep to keep original changesets)
+  [255]
+  $ hg rebase -r '3::8' -d 1 --keep
+  $ hg tglog
+  @  12: 'I'
+  |
+  o  11: 'H'
+  |
+  o  10: 'G'
+  |
+  o    9: 'D'
+  |\
+  | | o  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'
+  
+
+  $ cd ..
+
+rebase subset
+
+  $ hg clone -q -u . ah ah3
+  $ cd ah3
+  $ hg rebase -r '3::7' -d 1
+  abort: can't remove original changesets with unrebased descendants
+  (use --keep to keep original changesets)
+  [255]
+  $ hg rebase -r '3::7' -d 1 --keep
+  $ hg tglog
+  @  11: 'H'
+  |
+  o  10: 'G'
+  |
+  o    9: 'D'
+  |\
+  | | o  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'
+  
+
+  $ cd ..
+
+rebase subset with multiple head
+
+  $ hg clone -q -u . ah ah4
+  $ cd ah4
+  $ hg rebase -r '3::(7+5)' -d 1
+  abort: can't remove original changesets with unrebased descendants
+  (use --keep to keep original changesets)
+  [255]
+  $ hg rebase -r '3::(7+5)' -d 1 --keep
+  $ hg tglog
+  @  13: 'H'
+  |
+  o  12: 'G'
+  |
+  | o  11: 'F'
+  | |
+  | o  10: 'E'
+  |/
+  o    9: 'D'
+  |\
+  | | o  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'
+  
+
+  $ cd ..
+
+More advanced tests
+
+rebase on ancestor with revset
+
+  $ hg clone -q -u . ah ah5
+  $ cd ah5
+  $ hg rebase -r '6::' -d 2
+  saved backup bundle to $TESTTMP/ah5/.hg/strip-backup/3d8a618087a7-backup.hg
+  $ hg tglog
+  @  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'
+  
+  $ cd ..
+
+
+rebase with multiple root.
+We rebase E and G on B
+We would expect heads are I, F if it was supported
+
+  $ hg clone -q -u . ah ah6
+  $ cd ah6
+  $ hg rebase -r '(4+6)::' -d 1
+  abort: can't rebase multiple roots
+  [255]
+  $ cd ..