[PATCH 2 of 2] rebase: add --cherrypick to cherrypick single revisions

Stefano Tortarolo stefano.tortarolo at gmail.com
Wed Dec 30 19:50:17 CST 2009


# HG changeset patch
# User Stefano Tortarolo <stefano.tortarolo at gmail.com>
# Date 1262222630 -3600
# Node ID 4643de06fb4d42ea22055d42ce9ff46762f30555
# Parent  fcedd4d4282f33be5f86cd3a311bf937fe07528e
rebase: add --cherrypick to cherrypick single revisions

Using the idea behind --detach, rebase can now 'copy' single revisions from
a branch to another one.
Basically, it ignores every changeset up to the requested one, by means of null
merges, and then rebases just the specified changeset.

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -76,6 +76,7 @@
         keepf = opts.get('keep', False)
         keepbranchesf = opts.get('keepbranches', False)
         detachf = opts.get('detach', False)
+        cherryf = opts.get('cherrypick', False)
 
         if contf or abortf:
             if contf and abortf:
@@ -89,12 +90,16 @@
                 raise error.ParseError(
                     'rebase', _('cannot use detach with continue or abort'))
 
+            if cherryf:
+                raise error.ParseError(
+                    'rebase', _('cannot use cherrypick 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, detach, external) = restorestatus(repo)
+            (originalwd, target, state, collapsef, keepf, keepbranchesf, 
+                        detach, cherryf, external) = restorestatus(repo)
             if abortf:
                 abort(repo, originalwd, target, state)
                 return
@@ -113,8 +118,22 @@
                     raise error.ParseError(
                         'rebase', _('cannot specify a base with detach'))
 
+            if cherryf:
+                if not srcf:
+                    raise error.ParseError(
+                        'rebase', _('cherrypick requires a revision to be '
+                                        'specified'))
+                if collapsef:
+                    raise error.ParseError(
+                        'rebase', _('cannot use collapse with cherrypick'))
+                if basef:
+                    raise error.ParseError(
+                        'rebase', _('cannot specify a base with cherrypick'))
+                detachf = True
+
             cmdutil.bail_if_changed(repo)
-            result = buildstate(repo, destf, srcf, basef, collapsef, detachf)
+            result = buildstate(repo, destf, srcf, basef, collapsef, detachf,
+                                                                cherryf)
             if result:
                 originalwd, target, state, detach, external = result
             else: # Empty state built, nothing to rebase
@@ -135,7 +154,7 @@
         for rev in sorted(state):
             if state[rev] == -1:
                 storestatus(repo, originalwd, target, state, collapsef, keepf,
-                                               keepbranchesf, detach, external)
+                                   keepbranchesf, detach, cherryf, external)
                 rebasenode(repo, rev, target, state, skipped, targetancestors,
                                                    collapsef, detach, extrafn)
         ui.note(_('rebase merging completed\n'))
@@ -149,7 +168,7 @@
         if 'qtip' in repo.tags():
             updatemq(repo, state, skipped, **opts)
 
-        if not keepf:
+        if not keepf and not cherryf:
             # Remove no more useful revisions
             if set(repo.changelog.descendants(min(state))) - set(state):
                 ui.warn(_("warning: new changesets detected on source branch, "
@@ -326,7 +345,7 @@
         repo.mq.save_dirty()
 
 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
-                                                        detach, external):
+                                                detach, cherry, external):
     'Store the current status to allow recovery'
     f = repo.opener("rebasestate", "w")
     f.write(repo[originalwd].hex() + '\n')
@@ -336,6 +355,7 @@
     f.write('%d\n' % int(collapse))
     f.write('%d\n' % int(keep))
     f.write('%d\n' % int(keepbranches))
+    f.write('%d\n' % int(cherry))
     for d, v in state.iteritems():
         oldrev = repo[d].hex()
         newrev = repo[v].hex()
@@ -371,12 +391,14 @@
                 keep = bool(int(l))
             elif i == 6:
                 keepbranches = bool(int(l))
+            elif i == 7:
+                cherry = bool(int(l))
             else:
                 oldrev, newrev = l.split(':')
                 state[repo[oldrev].rev()] = repo[newrev].rev()
         repo.ui.debug('rebase status resumed\n')
         return (originalwd, target, state, collapse, keep, keepbranches, 
-                    detach, external)
+                    detach, cherry, external)
     except IOError, err:
         if err.errno != errno.ENOENT:
             raise
@@ -397,7 +419,7 @@
         clearstatus(repo)
         repo.ui.status(_('rebase aborted\n'))
 
-def buildstate(repo, dest, src, base, collapse, detach):
+def buildstate(repo, dest, src, base, collapse, detach, cherry):
     'Define which revisions are going to be rebased and where'
     targetancestors = set()
     detachrev = nullrev
@@ -454,11 +476,20 @@
         source = min(rebasingbranch)
 
     if detachrev:
-        repo.ui.debug('rebase onto %d starting from %d\n' % (dest, detachrev))
+        if cherry:
+            repo.ui.debug('rebase %d onto %d\n' % (detachrev, dest))
+        else:
+            repo.ui.debug('rebase onto %d starting from %d\n' % (dest, detachrev))
     else:
         repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
-    state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
     external = nullrev
+    if cherry:
+        srcdescendants = set(repo.changelog.descendants(source))
+        cherrydescendants = set(repo.changelog.descendants(detachrev))
+        staterevs = srcdescendants - cherrydescendants
+        state = dict.fromkeys(staterevs, nullrev)
+    else:
+        state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
     if collapse:
         if not targetancestors:
             targetancestors = set(repo.changelog.ancestors(dest))
@@ -515,9 +546,10 @@
         ('', 'keep', False, _('keep original changesets')),
         ('', 'keepbranches', False, _('keep original branch names')),
         ('', 'detach', False, _('force detaching of source from its original branch')),
+        ('', 'cherrypick', False, _('cherrypick the revision specified')),
         ('c', 'continue', False, _('continue an interrupted rebase')),
         ('a', 'abort', False, _('abort an interrupted rebase')),] +
          templateopts,
-        _('hg rebase [-s REV | -b REV] [-d REV] [--collapse | --detach] [--keep] '
+        _('hg rebase [-s REV | -b REV] [-d REV] [--collapse | --detach | --cherrypick] [--keep] '
                             '[--keepbranches] | [-c] | [-a]')),
 }
diff --git a/tests/test-rebase-detach b/tests/test-rebase-detach
--- a/tests/test-rebase-detach
+++ b/tests/test-rebase-detach
@@ -56,4 +56,31 @@
 echo "Expected A, B, C, D, E"
 hg manifest
 
+echo
+createrepo > /dev/null 2>&1
+hg glog  --template '{rev}: {desc}\n'
+echo '% Cherry picking C onto E'
+hg rebase --cherrypick -s 2 -d 4 2>&1 | sed 's/\(saving bundle to \).*/\1/'
+hg glog  --template '{rev}: {desc}\n'
+echo "Expected A, C, E"
+hg manifest
+
+echo
+createrepo > /dev/null 2>&1
+hg glog  --template '{rev}: {desc}\n'
+echo '% Cherry picking B onto E'
+hg rebase --cherrypick -s 1 -d 4 2>&1 | sed 's/\(saving bundle to \).*/\1/'
+hg glog  --template '{rev}: {desc}\n'
+echo "Expected A, B, E"
+hg manifest
+
+echo
+createrepo > /dev/null 2>&1
+hg glog  --template '{rev}: {desc}\n'
+echo '% Cherry picking D onto E'
+hg rebase --cherrypick -s 3 -d 4 2>&1 | sed 's/\(saving bundle to \).*/\1/'
+hg glog  --template '{rev}: {desc}\n'
+echo "Expected A, D, E"
+hg manifest
+
 exit 0
diff --git a/tests/test-rebase-detach.out b/tests/test-rebase-detach.out
--- a/tests/test-rebase-detach.out
+++ b/tests/test-rebase-detach.out
@@ -100,3 +100,91 @@
 C
 D
 E
+
+@  4: E
+|
+| o  3: D
+| |
+| o  2: C
+| |
+| o  1: B
+|/
+o  0: A
+
+% Cherry picking C onto E
+rebase completed
+@  5: C
+|
+o  4: E
+|
+| o  3: D
+| |
+| o  2: C
+| |
+| o  1: B
+|/
+o  0: A
+
+Expected A, C, E
+A
+C
+E
+
+@  4: E
+|
+| o  3: D
+| |
+| o  2: C
+| |
+| o  1: B
+|/
+o  0: A
+
+% Cherry picking B onto E
+rebase completed
+@  5: B
+|
+o  4: E
+|
+| o  3: D
+| |
+| o  2: C
+| |
+| o  1: B
+|/
+o  0: A
+
+Expected A, B, E
+A
+B
+E
+
+@  4: E
+|
+| o  3: D
+| |
+| o  2: C
+| |
+| o  1: B
+|/
+o  0: A
+
+% Cherry picking D onto E
+not in dirstate: E
+rebase completed
+@  5: D
+|
+o  4: E
+|
+| o  3: D
+| |
+| o  2: C
+| |
+| o  1: B
+|/
+o  0: A
+
+Expected A, D, E
+A
+D
+E
diff --git a/tests/test-rebase-parameters b/tests/test-rebase-parameters
--- a/tests/test-rebase-parameters
+++ b/tests/test-rebase-parameters
@@ -65,6 +65,14 @@
 echo "% Specify detach and collapse"
 hg rebase --detach --source 2 --collapse 2>&1 | sed 's/\(saving bundle to \).*/\1/'
 
+echo
+echo "% Specify cherrypick and not source"
+hg rebase --cherrypick 2>&1 | sed 's/\(saving bundle to \).*/\1/'
+
+echo
+echo "% Specify cherrypick and collapse"
+hg rebase --cherrypick --source 2 --collapse 2>&1 | sed 's/\(saving bundle to \).*/\1/'
+
 echo "% ----------"
 echo "% These work"
 echo
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 | --detach] [--keep] [--keepbranches] | [-c] | [-a]
+hg rebase [-s REV | -b REV] [-d REV] [--collapse | --detach | --cherrypick] [--keep] [--keepbranches] | [-c] | [-a]
 
 move changeset (and descendants) to a different branch
 
@@ -22,6 +22,7 @@
     --keep          keep original changesets
     --keepbranches  keep original branch names
     --detach        force detaching of source from its original branch
+    --cherrypick    cherrypick the revision specified
  -c --continue      continue an interrupted rebase
  -a --abort         abort an interrupted rebase
     --style         display using template map file
@@ -31,7 +32,7 @@
 
 % Use continue and collapse
 hg rebase: cannot use collapse with continue or abort
-hg rebase [-s REV | -b REV] [-d REV] [--collapse | --detach] [--keep] [--keepbranches] | [-c] | [-a]
+hg rebase [-s REV | -b REV] [-d REV] [--collapse | --detach | --cherrypick] [--keep] [--keepbranches] | [-c] | [-a]
 
 move changeset (and descendants) to a different branch
 
@@ -51,6 +52,7 @@
     --keep          keep original changesets
     --keepbranches  keep original branch names
     --detach        force detaching of source from its original branch
+    --cherrypick    cherrypick the revision specified
  -c --continue      continue an interrupted rebase
  -a --abort         abort an interrupted rebase
     --style         display using template map file
@@ -60,7 +62,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 | --detach] [--keep] [--keepbranches] | [-c] | [-a]
+hg rebase [-s REV | -b REV] [-d REV] [--collapse | --detach | --cherrypick] [--keep] [--keepbranches] | [-c] | [-a]
 
 move changeset (and descendants) to a different branch
 
@@ -80,6 +82,7 @@
     --keep          keep original changesets
     --keepbranches  keep original branch names
     --detach        force detaching of source from its original branch
+    --cherrypick    cherrypick the revision specified
  -c --continue      continue an interrupted rebase
  -a --abort         abort an interrupted rebase
     --style         display using template map file
@@ -89,7 +92,7 @@
 
 % Use source and base
 hg rebase: cannot specify both a revision and a base
-hg rebase [-s REV | -b REV] [-d REV] [--collapse | --detach] [--keep] [--keepbranches] | [-c] | [-a]
+hg rebase [-s REV | -b REV] [-d REV] [--collapse | --detach | --cherrypick] [--keep] [--keepbranches] | [-c] | [-a]
 
 move changeset (and descendants) to a different branch
 
@@ -109,6 +112,7 @@
     --keep          keep original changesets
     --keepbranches  keep original branch names
     --detach        force detaching of source from its original branch
+    --cherrypick    cherrypick the revision specified
  -c --continue      continue an interrupted rebase
  -a --abort         abort an interrupted rebase
     --style         display using template map file
@@ -125,7 +129,7 @@
 
 % Specify detach and not source
 hg rebase: detach requires a revision to be specified
-hg rebase [-s REV | -b REV] [-d REV] [--collapse | --detach] [--keep] [--keepbranches] | [-c] | [-a]
+hg rebase [-s REV | -b REV] [-d REV] [--collapse | --detach | --cherrypick] [--keep] [--keepbranches] | [-c] | [-a]
 
 move changeset (and descendants) to a different branch
 
@@ -145,6 +149,7 @@
     --keep          keep original changesets
     --keepbranches  keep original branch names
     --detach        force detaching of source from its original branch
+    --cherrypick    cherrypick the revision specified
  -c --continue      continue an interrupted rebase
  -a --abort         abort an interrupted rebase
     --style         display using template map file
@@ -154,7 +159,7 @@
 
 % Specify detach and collapse
 hg rebase: cannot use collapse with detach
-hg rebase [-s REV | -b REV] [-d REV] [--collapse | --detach] [--keep] [--keepbranches] | [-c] | [-a]
+hg rebase [-s REV | -b REV] [-d REV] [--collapse | --detach | --cherrypick] [--keep] [--keepbranches] | [-c] | [-a]
 
 move changeset (and descendants) to a different branch
 
@@ -174,6 +179,67 @@
     --keep          keep original changesets
     --keepbranches  keep original branch names
     --detach        force detaching of source from its original branch
+    --cherrypick    cherrypick the revision specified
+ -c --continue      continue an interrupted rebase
+ -a --abort         abort an interrupted rebase
+    --style         display using template map file
+    --template      display with template
+
+use "hg -v help rebase" to show global options
+
+% Specify cherrypick and not source
+hg rebase: cherrypick requires a revision to be specified
+hg rebase [-s REV | -b REV] [-d REV] [--collapse | --detach | --cherrypick] [--keep] [--keepbranches] | [-c] | [-a]
+
+move changeset (and descendants) to a different branch
+
+    Rebase uses repeated merging to graft changesets from one part of history
+    onto another. This can be useful for linearizing local changes relative to
+    a master development tree.
+
+    If a rebase is interrupted to manually resolve a merge, it can be
+    continued with --continue/-c or aborted with --abort/-a.
+
+options:
+
+ -s --source        rebase from a given revision
+ -b --base          rebase from the base of a given revision
+ -d --dest          rebase onto a given revision
+    --collapse      collapse the rebased changesets
+    --keep          keep original changesets
+    --keepbranches  keep original branch names
+    --detach        force detaching of source from its original branch
+    --cherrypick    cherrypick the revision specified
+ -c --continue      continue an interrupted rebase
+ -a --abort         abort an interrupted rebase
+    --style         display using template map file
+    --template      display with template
+
+use "hg -v help rebase" to show global options
+
+% Specify cherrypick and collapse
+hg rebase: cannot use collapse with cherrypick
+hg rebase [-s REV | -b REV] [-d REV] [--collapse | --detach | --cherrypick] [--keep] [--keepbranches] | [-c] | [-a]
+
+move changeset (and descendants) to a different branch
+
+    Rebase uses repeated merging to graft changesets from one part of history
+    onto another. This can be useful for linearizing local changes relative to
+    a master development tree.
+
+    If a rebase is interrupted to manually resolve a merge, it can be
+    continued with --continue/-c or aborted with --abort/-a.
+
+options:
+
+ -s --source        rebase from a given revision
+ -b --base          rebase from the base of a given revision
+ -d --dest          rebase onto a given revision
+    --collapse      collapse the rebased changesets
+    --keep          keep original changesets
+    --keepbranches  keep original branch names
+    --detach        force detaching of source from its original branch
+    --cherrypick    cherrypick the revision specified
  -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