Commits

Pierre-Yves David committed 2bd6016 Merge

merge with stable

Comments (0)

Files changed (12)

 Changelog
 ==================
 
+3.1.0 --
+
+- prune: various minor improvements
+- amend: drop deprecated --change option for amend
+- alias: add a grab aliast to be used instead of graft -O
+- touch: add a --duplicate option to *not* obsolete the old version
+- touch: fix touching multiple revision at the same time
+- evolve: add a --all option
+
 3.0.0 -- 2013-02-02
 
 - compatibility with 2.5
 ..                Logilab SA        <contact@logilab.fr>
 
 ========================================
-Safe Mutable History
+Changeset Evolution Experimentation
 ========================================
 
 
+This is the online documentation of the `evolve extension`_. An experimental
+extension that drive the implementation of the `changeset evolution concept`_ for
+Mercurial.
+
+.. _`evolve extension`: http://mercurial.selenic.com/wiki/EvolveExtension
+.. _`changeset evolution concept`: http://mercurial.selenic.com/wiki/ChangesetEvolution
+
 Here are various materials on planned improvement to Mercurial regarding
 rewriting history.
 
 testedwith = '2.5'
 buglink = 'https://bitbucket.org/marmoute/mutable-history/issues'
 
-
+import sys
 import random
 
+import mercurial
 from mercurial import util
 
 try:
 from mercurial import commands
 from mercurial import context
 from mercurial import copies
-from mercurial import discovery
 from mercurial import error
 from mercurial import extensions
 from mercurial import hg
-from mercurial import localrepo
 from mercurial import lock as lockmod
 from mercurial import merge
 from mercurial import node
 from mercurial.commands import walkopts, commitopts, commitopts2
 from mercurial.node import nullid
 
-import mercurial.hgweb.hgweb_mod
-
 
 
 # This extension contains the following code
             def setupcelestine(ui):
                 print 'this is extsetup!'
         """
-        self._uicallables.append(call)
+        self._extcallables.append(call)
         return call
 
     def reposetup(self, call):
 
     Nullid successors was created by older version of evolve.
     """
+    nb = 0
     for marker in orig(data):
         if nullid in marker[1]:
             marker = (marker[0],
                       tuple(s for s in marker[1] if s != nullid),
                       marker[2],
                       marker[3])
+            nb += 1
         yield marker
+    if nb:
+        e = sys.stderr
+        print >> e, 'repo contains %i invalid obsolescence markers' % nb
 
-
-cachefuncs = obsolete.cachefuncs
-cachefor = obsolete.cachefor
 getrevs = obsolete.getrevs
-clearobscaches = obsolete.clearobscaches
 
 #####################################################################
 ### Complete troubles computation logic                           ###
 #####################################################################
 
-# there is one kind of trouble not handled by core right now:
-# - divergent: (two changeset try to succeed to the same precursors)
-#
-# This section add support for those two addition trouble
-#
-# - Cache computation
-# - revset and ctx method
-# - push warning
 
 ### Cache computation
 latediff = 1  # flag to prevent taking late comer fix into account
 
-
-
 ### changectx method
 
 @eh.addattr(context.changectx, 'latecomer')
 ### revset symbol
 
 eh.revset('latecomer')(revset.symbols['bumped'])
-
 eh.revset('conflicting')(revset.symbols['divergent'])
 
 
         ui.setconfig('alias', 'olog', "log -r 'precursors(.)' --hidden")
     if ui.config('alias', 'odiff', None) is None:
         ui.setconfig('alias', 'odiff', "diff --hidden --rev 'limit(precursors(.),1)' --rev .")
+    if ui.config('alias', 'grab', None) is None:
+        ui.setconfig('alias', 'grab', "rebase --dest . --rev $1")
+
 
 ### Troubled revset symbol
 
             extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors)
     except KeyError:
         pass  # rebase not found
+    try:
+        histedit = extensions.find('histedit')
+        if histedit:
+            extensions.wrapcommand(histedit.cmdtable, 'histedit', warnobserrors)
+    except KeyError:
+        pass  # rebase not found
 
 
 #####################################################################
 ### util function
 #############################
 
-def noderange(repo, revsets):
-    """The same as revrange but return node"""
-    return map(repo.changelog.node,
-               scmutil.revrange(repo, revsets))
-
 ### changeset rewriting logic
 #############################
 
         newid = repo.commitctx(new)
         new = repo[newid]
         created = len(repo) != revcount
-        if created:
-            updatebookmarks(newid)
-            # add evolution metadata
-            markers = [(u, (new,)) for u in updates]
-            markers.append((old, (new,)))
-            createmarkers(repo, markers)
-        else:
-            # newid is an existing revision. It could make sense to
-            # replace revisions with existing ones but probably not by
-            # default.
-            pass
+        updatebookmarks(newid)
     finally:
         wlock.release()
 
         nodesrc = orig.node()
         destphase = repo[nodesrc].phase()
         try:
-            if rebase.rebasenode.func_code.co_argcount == 5:
-                # rebasenode collapse argument was introduced by
-                # d1afbf03e69a (2.3)
-                r = rebase.rebasenode(repo, orig.node(), dest.node(),
-                                      {node.nullrev: node.nullrev}, False)
-            else:
-                r = rebase.rebasenode(repo, orig.node(), dest.node(),
-                                     {node.nullrev: node.nullrev})
+            r = rebase.rebasenode(repo, orig.node(), dest.node(),
+                                  {node.nullrev: node.nullrev}, False)
             if r[-1]: #some conflict
                 raise util.Abort(
                         'unresolved merge conflicts (see hg help resolve)')
         return 1
 
 @command('^prune|obsolete|kill',
-    [('n', 'new', [], _("successor changeset")),
-     ('r', 'rev', [], _("revisions to fold"))],
+    [('n', 'new', [], _("successor changeset (DEPRECATED)")),
+     ('s', 'succ', [], _("successor changeset")),
+     ('r', 'rev', [], _("revisions to prune"))],
     _('[OPTION] [-r] REV...'))
-def kill(ui, repo, *revs, **opts):
-    """mark a changeset as obsolete
+    # -d --date
+    # -u --user
+    # -U  --noupdate option to prevent wc update and or bookmarks update ?
+def cmdprune(ui, repo, *revs, **opts):
+    """get rid of changesets by marking them obsolete
 
-    This update the parent directory to a not-killed parent if the current
-    working directory parent are killed.
+    Obsolete changesets becomes invisible to all commands.
 
-    XXX bookmark support
-    XXX handle merge
-    XXX check immutable first
+    Non-pruned descendant of pruned changesets becomes "unstable". Use the
+    :hg:`evolve` to handle such situation.
+
+    When the working directory parent is pruned the repository is updated to a
+    non obsolete parents.
+
+    you can use the ``--succ`` option to informs mercurial that a newer version
+    of the pruned changeset exists.
+
+    XXX this commands needs bookmarks support.
     """
     revs = list(revs)
     revs.extend(opts['rev'])
+    succs = opts['new'] + opts['succ']
+    wlock = lock = None
     wlock = repo.wlock()
+    sortedrevs = lambda specs: sorted(set(scmutil.revrange(repo, specs)))
     try:
         lock = repo.lock()
-        try:
-            new = set(noderange(repo, opts['new']))
-            targetnodes = set(noderange(repo, revs))
-            if not targetnodes:
-                raise util.Abort('nothing to prune')
-            if new:
-                sucs = tuple(repo[n] for n in new)
+        # defines pruned changesets
+        precs = []
+        for p in sortedrevs(revs):
+            cp = repo[p]
+            if not cp.mutable():
+                # note: create marker would had raise something anyway
+                raise util.Abort('cannot prune immutable changeset: %s' % cp,
+                                 hint='see "hg help phases" for details')
+            precs.append(cp)
+        if not precs:
+            raise util.Abort('nothing to prune')
+
+        # defines successors changesets
+        sucs = tuple(repo[n] for n in sortedrevs(succs))
+        if len(sucs) > 1 and len(precs) > 1:
+            msg = "Can't use multiple successors for multiple precursors"
+            raise util.Abort(msg)
+        # create markers
+        createmarkers(repo, [(p, sucs) for p in precs])
+        # informs that changeset have been pruned
+        ui.status(_('%i changesets pruned\n') % len(precs))
+        # update to an unkilled parent
+        wdp = repo['.']
+        newnode = wdp
+        while newnode.obsolete():
+            newnode = newnode.parents()[0]
+        if newnode.node() != wdp.node():
+            commands.update(ui, repo, newnode.rev())
+            ui.status(_('working directory now at %s\n') % newnode)
+        # upVdate bookmarks
+        for ctx in repo.unfiltered().set('bookmark() and %ld', precs):
+            ldest = list(repo.set('max((::%d) - obsolete())', ctx))
+            if ldest:
+                dest = ldest[0]
+                updatebookmarks = _bookmarksupdater(repo, ctx.node())
+                updatebookmarks(dest.node())
             else:
-                sucs = ()
-            markers = []
-            for n in targetnodes:
-                markers.append((repo[n], sucs))
-            createmarkers(repo, markers)
+                # delete bookmarks
+                pass
+    finally:
+        lockmod.release(lock, wlock)
 
-            # update to an unkilled parent
-            wdp = repo['.']
-            newnode = wdp
-            while newnode.obsolete():
-                newnode = newnode.parents()[0]
-            if newnode.node() != wdp.node():
-                commands.update(ui, repo, newnode.rev())
-                ui.status(_('working directory now at %s\n') % newnode)
-        finally:
-            lock.release()
-    finally:
-        wlock.release()
-
-@command('^amend|refresh',
+@command('amend|refresh',
     [('A', 'addremove', None,
      _('mark new/missing files as added/removed before committing')),
     ('n', 'note', '', _('use text as commit message for this update')),
-    ('c', 'change', '', _('specifies the changesets to amend (DEPRECATED)'), _('REV')),
     ('e', 'edit', False, _('invoke editor on commit messages')),
     ] + walkopts + commitopts + commitopts2,
     _('[OPTION]... [FILE]...'))
 
     If you don't specify -m, the parent's message will be reused.
 
-    If you specify --change, amend additionally considers all
-    changesets between the indicated changeset and the working copy
-    parent as updates to be subsumed.
-
     Behind the scenes, Mercurial first commits the update as a regular child
     of the current parent. Then it creates a new commit on the parent's parents
     with the updated contents. Then it changes the working copy parent to this
     """
 
     # determine updates to subsume
-    old = scmutil.revsingle(repo, opts.get('change') or '.')
+    old = scmutil.revsingle(repo, '.')
 
     lock = repo.lock()
     try:
             if old.phase() == phases.public:
                 raise util.Abort(_("can not rewrite immutable changeset %s")
                                  % old)
-            if not repo.revs('%d and (::.)', old):
-                raise error.Abort(_('cannot amend non ancestor changeset'))
             tr = repo.transaction('amend')
             try:
                 oldphase = old.phase()
                     opts['force_editor'] = True
                 newid, created = rewrite(repo, old, updates, head,
                                          [old.p1().node(), old.p2().node()], opts)
+
+                if newid != old.node():
+                    createmarkers(repo, [(old, (repo[newid],))])
+                if tempid is not None:
+                    createmarkers(repo, [(repo[tempid], ())])
                 if created:
                     # reroute the working copy parent to the new changeset
                     phases.retractboundary(repo, oldphase, [newid])
                     # rewrite() recreated an existing revision, discard
                     # the intermediate revision if any. No need to update
                     # phases or parents.
-                    if tempid is not None:
-                        createmarkers(repo, [(repo[tempid], ())])
                     # XXX: need another message in collapse case.
                     tr.close()
                     raise error.Abort(_('no updates found'))
             lock.release()
 
 @command('^touch',
-    [('r', 'rev', [], 'revision to update'),],
+    [('r', 'rev', [], 'revision to update'),
+     ('D', 'duplicate', False,
+      'do not mark the new revision as successor of the old one')],
     # allow to choose the seed ?
     _('[-r] revs'))
 def touch(ui, repo, *revs, **opts):
 
     This is used to "resurrect" changesets
     """
+    duplicate = opts['duplicate']
     revs = list(revs)
     revs.extend(opts['rev'])
     if not revs:
     if not revs:
         ui.write_err('no revision to touch\n')
         return 1
-    if repo.revs('public() and %ld', revs):
+    if not duplicate and repo.revs('public() and %ld', revs):
         raise util.Abort("can't touch public revision")
     wlock = lock = None
     try:
         wlock = repo.wlock()
         lock = repo.lock()
         tr = repo.transaction('touch')
+        revs.sort() # ensure parent are run first
+        newmapping = {}
         try:
             for r in revs:
                 ctx = repo[r]
                 extra = ctx.extra().copy()
                 extra['__touch-noise__'] = random.randint(0, 0xffffffff)
+                # search for touched parent
+                p1 = ctx.p1().node()
+                p2 = ctx.p2().node()
+                p1 = newmapping.get(p1, p1)
+                p2 = newmapping.get(p2, p2)
                 new, _ = rewrite(repo, ctx, [], ctx,
-                                 [ctx.p1().node(), ctx.p2().node()],
+                                 [p1, p2],
                                  commitopts={'extra': extra})
-                createmarkers(repo, [(ctx, (repo[new],))])
+                # store touched version to help potential children
+                newmapping[ctx.node()] = new
+                if not duplicate:
+                    createmarkers(repo, [(ctx, (repo[new],))])
                 phases.retractboundary(repo, ctx.phase(), [new])
                 if ctx in repo[None].parents():
                     repo.dirstate.setparents(new, node.nullid)
         lockmod.release(lock, wlock)
 
 @command('^fold',
-    [('r', 'rev', [], _("revisions to fold")),
+    [('r', 'rev', [], _("explicitly specify the full set of revision to fold")),
     ],
     # allow to choose the seed ?
-    _('[-r] revs'))
+    _('rev'))
 def fold(ui, repo, *revs, **opts):
-    """Fold multiple revisions into a single one"""
+    """Fold multiple revisions into a single one
+
+    Revision from your current working directory to the specified one are fold
+    as a new one replacing the other
+
+    you can alternatively use --rev to explicitly specify revision to be fold
+    ignoring the current working directory parent.
+    """
     revs = list(revs)
-    revs.extend(opts['rev'])
     if revs:
-        revs = scmutil.revrange(repo, revs)
+        if opts.get('rev', ()):
+            raise util.Abort("cannot specify both --rev and a target revision")
+        targets = scmutil.revrange(repo, revs)
+        revs = repo.revs('(%ld::.) or (.::%ld)', targets, targets)
+    elif 'rev' in opts:
+        revs = scmutil.revrange(repo, opts['rev'])
+    else:
+        revs = ()
     if not revs:
         ui.write_err('no revision to fold\n')
         return 1

tests/test-amend.t

   (branches are permanent and global, did you want a bookmark?)
   $ hg amend
   $ hg debugobsolete
-  bd19cbe78fbfbd87eb33420c63986fe5f3154f2c a34b93d251e49c93d5685ebacad785c73a7e8605 0 {'date': '* *', 'user': 'test'} (glob)
   07f4944404050f47db2e5c5071e0e84e7a27bba9 a34b93d251e49c93d5685ebacad785c73a7e8605 0 {'date': '* *', 'user': 'test'} (glob)
+  bd19cbe78fbfbd87eb33420c63986fe5f3154f2c 0 {'date': '* *', 'user': 'test'} (glob)
   $ hg branch
   foo
   $ hg branches
   -a
   $ hg pdiff
   $ hg ci -m reseta
-  $ hg amend --change 2
-  abort: no updates found
-  [255]
   $ hg debugobsolete
-  bd19cbe78fbfbd87eb33420c63986fe5f3154f2c a34b93d251e49c93d5685ebacad785c73a7e8605 0 {'date': '* *', 'user': 'test'} (glob)
   07f4944404050f47db2e5c5071e0e84e7a27bba9 a34b93d251e49c93d5685ebacad785c73a7e8605 0 {'date': '* *', 'user': 'test'} (glob)
+  bd19cbe78fbfbd87eb33420c63986fe5f3154f2c 0 {'date': '* *', 'user': 'test'} (glob)
   $ hg phase 2
   2: draft
   $ glog
   o  2@foo(draft) adda
   
 
-Test collapsing into an existing rev, with an intermediate revision.
-
-  $ hg branch --force default
-  marked working directory as branch default
-  (branches are permanent and global, did you want a bookmark?)
-  $ hg ci -m resetbranch
-  $ hg branch --force foo
-  marked working directory as branch foo
-  (branches are permanent and global, did you want a bookmark?)
-  $ hg amend --change 2
-  abort: no updates found
-  [255]
-  $ hg debugobsolete
-  bd19cbe78fbfbd87eb33420c63986fe5f3154f2c a34b93d251e49c93d5685ebacad785c73a7e8605 0 {'date': '* *', 'user': 'test'} (glob)
-  07f4944404050f47db2e5c5071e0e84e7a27bba9 a34b93d251e49c93d5685ebacad785c73a7e8605 0 {'date': '* *', 'user': 'test'} (glob)
-  7384bbcba36fde1a789cd00f9cd6f9b919ab5910 0 {'date': '* *', 'user': 'test'} (glob)
-  $ glog
-  @  6@foo(draft) amends a34b93d251e49c93d5685ebacad785c73a7e8605
-  |
-  o  5@default(draft) resetbranch
-  |
-  o  4@foo(draft) reseta
-  |
-  o  3@foo(draft) changea
-  |
-  o  2@foo(draft) adda
-  

tests/test-corrupt.t

   
 
   $ hg kill -n -1 -- -2 -3
+  2 changesets pruned
   $ hg push ../other
   pushing to ../other
   searching for changes

tests/test-evolve.t

   $ hg log -r 1 --template '{rev} {phase} {obsolete}\n'
   1 public stable
   $ hg kill 1
-  abort: cannot obsolete immutable changeset: 7c3bad9141dc
+  abort: cannot prune immutable changeset: 7c3bad9141dc
+  (see "hg help phases" for details)
   [255]
   $ hg log -r 1 --template '{rev} {phase} {obsolete}\n'
   1 public stable
   $ hg id -n
   5
   $ hg kill .
+  1 changesets pruned
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   working directory now at fbb94e3a0ecf
   $ hg qlog
 test multiple kill
 
   $ hg kill 4 -r 3
+  2 changesets pruned
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   working directory now at 7c3bad9141dc
   $ hg qlog
   $ echo 4 > g
   $ hg add g
   $ hg kill .
+  1 changesets pruned
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   working directory now at 7c3bad9141dc
   $ hg st
 
   $ hg phase --public 0 -v
   phase changed for 1 changesets
-  $ hg amend -c 4
-  abort: cannot amend non ancestor changeset
-  [255]
 
 
 (amend of on ancestors)
   @  0:e55e0562ee93@default(public) base
   
   $ hg debugobsolete
-  524e478d4811d405c8771e4c441de4483bdf8b33 f8111a076f0975cbecb336e2bd3411be22b673fb 0 {'date': '* *', 'user': 'test'} (glob)
   7b36850622b2fd159fa30a4fb2a1edd2043b4a14 f8111a076f0975cbecb336e2bd3411be22b673fb 0 {'date': '* *', 'user': 'test'} (glob)
-  e416e48b27428695d00c2a2cc4a0b9619482e63f 23409eba69a0986e90cd42252852c1e6da97af5b 0 {'date': '* *', 'user': 'test'} (glob)
+  524e478d4811d405c8771e4c441de4483bdf8b33 0 {'date': '* *', 'user': 'test'} (glob)
   568a468b60fc99a42d5d4ddbe181caff1eef308d 23409eba69a0986e90cd42252852c1e6da97af5b 0 {'date': '* *', 'user': 'test'} (glob)
+  e416e48b27428695d00c2a2cc4a0b9619482e63f 0 {'date': '* *', 'user': 'test'} (glob)
   $ hg evolve
   move:[4] another feature
   atop:[6] a nifty feature
   $ hg fold
   no revision to fold
   [1]
-  $ hg fold 6::10
+  $ hg fold 6 --rev 10
+  abort: cannot specify both --rev and a target revision
+  [255]
+  $ hg fold 6 # want to run hg fold 6
   2 changesets folded
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ glog
 
   $ hg up 4
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ hg fold 4::11
+  $ hg fold --rev 4::11
   3 changesets folded
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ glog

tests/test-obsolete-push.t

   $ hg ci -qAm C c
   $ hg phase --secret --force .
   $ hg kill 0 1
+  2 changesets pruned
   1 new unstable changesets
   $ glog --hidden
   @  2:244232c2222a@default(unstable/secret) C

tests/test-prune.t

+  $ cat >> $HGRCPATH <<EOF
+  > [ui]
+  > logtemplate={rev}:{node|short}[{bookmarks}] ({obsolete}/{phase}) {desc|firstline}\n
+  > [extensions]
+  > hgext.rebase=
+  > EOF
+  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
+
+  $ mkcommit() {
+  >    echo "$1" > "$1"
+  >    hg add "$1"
+  >    hg ci -m "add $1"
+  > }
+
+  $ hg init repo
+  $ cd repo
+  $ mkcommit a
+  $ hg phase --public .
+  $ mkcommit b
+  $ mkcommit c
+  $ mkcommit d
+  $ mkcommit e
+  $ hg bookmarks BABAR
+  $ hg log -G
+  @  4:9d206ffc875e[BABAR] (stable/draft) add e
+  |
+  o  3:47d2a3944de8[] (stable/draft) add d
+  |
+  o  2:4538525df7e2[] (stable/draft) add c
+  |
+  o  1:7c3bad9141dc[] (stable/draft) add b
+  |
+  o  0:1f0dee641bb7[] (stable/public) add a
+  
+
+Check simple case
+----------------------------
+
+prune current and tip changeset
+
+  $ hg prune .
+  1 changesets pruned
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  working directory now at 47d2a3944de8
+  $ hg debugobsolete
+  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '*', 'user': 'test'} (glob)
+
+prune leaving unstability behind
+
+  $ hg prune 1
+  1 changesets pruned
+  2 new unstable changesets
+  $ hg debugobsolete
+  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '*', 'user': 'test'} (glob)
+  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '*', 'user': 'test'} (glob)
+
+pruning multiple changeset at once
+
+  $ hg prune 2:
+  2 changesets pruned
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  working directory now at 1f0dee641bb7
+  $ hg debugobsolete
+  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '*', 'user': 'test'} (glob)
+  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '*', 'user': 'test'} (glob)
+  4538525df7e2b9f09423636c61ef63a4cb872a2d 0 {'date': '*', 'user': 'test'} (glob)
+  47d2a3944de8b013de3be9578e8e344ea2e6c097 0 {'date': '*', 'user': 'test'} (glob)
+
+cannot prune public changesets
+
+  $ hg prune 0
+  abort: cannot prune immutable changeset: 1f0dee641bb7
+  (see "hg help phases" for details)
+  [255]
+  $ hg debugobsolete
+  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '*', 'user': 'test'} (glob)
+  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '*', 'user': 'test'} (glob)
+  4538525df7e2b9f09423636c61ef63a4cb872a2d 0 {'date': '*', 'user': 'test'} (glob)
+  47d2a3944de8b013de3be9578e8e344ea2e6c097 0 {'date': '*', 'user': 'test'} (glob)
+
+Check successors addition
+----------------------------
+
+  $ mkcommit bb
+  $ mkcommit cc
+  $ mkcommit dd
+  $ mkcommit ee
+  $ hg up 0
+  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+  $ mkcommit nB
+  created new head
+  $ mkcommit nC
+  $ mkcommit nD
+  $ mkcommit nE
+
+  $ hg log -G
+  @  12:6e8148413dd5[] (stable/draft) add nE
+  |
+  o  11:8ee176ff1d4b[] (stable/draft) add nD
+  |
+  o  10:aa96dc3f04c2[] (stable/draft) add nC
+  |
+  o  9:6f6f25e4f748[] (stable/draft) add nB
+  |
+  | o  8:bb5e90a7ea1f[] (stable/draft) add ee
+  | |
+  | o  7:00ded550b1e2[] (stable/draft) add dd
+  | |
+  | o  6:354011cd103f[] (stable/draft) add cc
+  | |
+  | o  5:814c38b95e72[] (stable/draft) add bb
+  |/
+  o  0:1f0dee641bb7[BABAR] (stable/public) add a
+  
+
+one old, one new
+
+  $ hg prune 'desc("add ee")' -s 'desc("add nE")'
+  1 changesets pruned
+  $ hg debugobsolete
+  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '*', 'user': 'test'} (glob)
+  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '*', 'user': 'test'} (glob)
+  4538525df7e2b9f09423636c61ef63a4cb872a2d 0 {'date': '*', 'user': 'test'} (glob)
+  47d2a3944de8b013de3be9578e8e344ea2e6c097 0 {'date': '*', 'user': 'test'} (glob)
+  bb5e90a7ea1f3b4b38b23150a4a597b6146d70ef 6e8148413dd541855b72a920a90c06fca127c7e7 0 {'date': '*', 'user': 'test'} (glob)
+  $ hg log -G
+  @  12:6e8148413dd5[] (stable/draft) add nE
+  |
+  o  11:8ee176ff1d4b[] (stable/draft) add nD
+  |
+  o  10:aa96dc3f04c2[] (stable/draft) add nC
+  |
+  o  9:6f6f25e4f748[] (stable/draft) add nB
+  |
+  | o  7:00ded550b1e2[] (stable/draft) add dd
+  | |
+  | o  6:354011cd103f[] (stable/draft) add cc
+  | |
+  | o  5:814c38b95e72[] (stable/draft) add bb
+  |/
+  o  0:1f0dee641bb7[BABAR] (stable/public) add a
+  
+
+one old, two new
+
+  $ hg prune 'desc("add dd")' -s 'desc("add nD")' -s 'desc("add nC")'
+  1 changesets pruned
+  $ hg debugobsolete
+  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '*', 'user': 'test'} (glob)
+  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '*', 'user': 'test'} (glob)
+  4538525df7e2b9f09423636c61ef63a4cb872a2d 0 {'date': '*', 'user': 'test'} (glob)
+  47d2a3944de8b013de3be9578e8e344ea2e6c097 0 {'date': '*', 'user': 'test'} (glob)
+  bb5e90a7ea1f3b4b38b23150a4a597b6146d70ef 6e8148413dd541855b72a920a90c06fca127c7e7 0 {'date': '*', 'user': 'test'} (glob)
+  00ded550b1e28bba454bd34cec1269d22cf3ef25 aa96dc3f04c2c2341fe6880aeb6dc9fbffff9ef9 8ee176ff1d4b2034ce51e3efc579c2de346b631d 0 {'date': '**', 'user': 'test'} (glob)
+  $ hg log -G
+  @  12:6e8148413dd5[] (stable/draft) add nE
+  |
+  o  11:8ee176ff1d4b[] (stable/draft) add nD
+  |
+  o  10:aa96dc3f04c2[] (stable/draft) add nC
+  |
+  o  9:6f6f25e4f748[] (stable/draft) add nB
+  |
+  | o  6:354011cd103f[] (stable/draft) add cc
+  | |
+  | o  5:814c38b95e72[] (stable/draft) add bb
+  |/
+  o  0:1f0dee641bb7[BABAR] (stable/public) add a
+  
+
+two old, two new (should be denied)
+
+  $ hg prune 'desc("add cc")' 'desc("add bb")' -s 'desc("add nD")' -s 'desc("add nC")'
+  abort: Can't use multiple successors for multiple precursors
+  [255]
+  $ hg debugobsolete
+  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '*', 'user': 'test'} (glob)
+  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '*', 'user': 'test'} (glob)
+  4538525df7e2b9f09423636c61ef63a4cb872a2d 0 {'date': '*', 'user': 'test'} (glob)
+  47d2a3944de8b013de3be9578e8e344ea2e6c097 0 {'date': '*', 'user': 'test'} (glob)
+  bb5e90a7ea1f3b4b38b23150a4a597b6146d70ef 6e8148413dd541855b72a920a90c06fca127c7e7 0 {'date': '*', 'user': 'test'} (glob)
+  00ded550b1e28bba454bd34cec1269d22cf3ef25 aa96dc3f04c2c2341fe6880aeb6dc9fbffff9ef9 8ee176ff1d4b2034ce51e3efc579c2de346b631d 0 {'date': '**', 'user': 'test'} (glob)
+
+two old, one new:
+
+  $ hg prune 'desc("add cc")' 'desc("add bb")' -s 'desc("add nB")'
+  2 changesets pruned
+  $ hg debugobsolete
+  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '*', 'user': 'test'} (glob)
+  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '*', 'user': 'test'} (glob)
+  4538525df7e2b9f09423636c61ef63a4cb872a2d 0 {'date': '*', 'user': 'test'} (glob)
+  47d2a3944de8b013de3be9578e8e344ea2e6c097 0 {'date': '*', 'user': 'test'} (glob)
+  bb5e90a7ea1f3b4b38b23150a4a597b6146d70ef 6e8148413dd541855b72a920a90c06fca127c7e7 0 {'date': '*', 'user': 'test'} (glob)
+  00ded550b1e28bba454bd34cec1269d22cf3ef25 aa96dc3f04c2c2341fe6880aeb6dc9fbffff9ef9 8ee176ff1d4b2034ce51e3efc579c2de346b631d 0 {'date': '**', 'user': 'test'} (glob)
+  814c38b95e72dfe2cbf675b1649ea9d780c89a80 6f6f25e4f748d8f7571777e6e168aedf50350ce8 0 {'date': '*', 'user': 'test'} (glob)
+  354011cd103f58bbbd9091a3cee6d6a6bd0dddf7 6f6f25e4f748d8f7571777e6e168aedf50350ce8 0 {'date': '*', 'user': 'test'} (glob)

tests/test-stabilize-order.t

   --- successors.old* (glob)
   +++ successors.new* (glob)
   @@ -3,3 +3,4 @@
-   3a4a591493f80708e46f2bf6d3b4debfad8ff91e f5ff10856e5ab3c8dc420b9c11460e6832a3b78c 0 {'date': '* *', 'user': 'test'} (glob)
    93418d2c0979643ad446f621195e78720edb05b4 f5ff10856e5ab3c8dc420b9c11460e6832a3b78c 0 {'date': '* *', 'user': 'test'} (glob)
+   3a4a591493f80708e46f2bf6d3b4debfad8ff91e 0 {'date': '* *', 'user': 'test'} (glob)
    ab8cbb6d87ff3ab5526735a051cba6b63f3d6775 6bf44048e43f830accbf7d2bd7bc252ad7a3b99c 0 {'date': '* *', 'user': 'test'} (glob)
   +7a7552255fb5f8bd745e46fba6f0ca633a4dd716 5e819fbb0d278117c0a83b7f6f6486689732cfb2 0 {'date': '* *', 'user': 'test'} (glob)
   [1]

tests/test-stabilize-result.t

   o  0:07f494440405@default(draft) bk:[] adda
   
   $ hg debugobsolete
-  41ad4fe8c79565a06c89f032ef0937b3cbd68a04 1447e1c4828d2347df8f858aa041305fa4cf7db1 0 {'date': '* *', 'user': 'test'} (glob)
   102a90ea7b4a3361e4082ed620918c261189a36a 1447e1c4828d2347df8f858aa041305fa4cf7db1 0 {'date': '* *', 'user': 'test'} (glob)
+  41ad4fe8c79565a06c89f032ef0937b3cbd68a04 0 {'date': '* *', 'user': 'test'} (glob)
   cce2c55b896511e0b6e04173c9450ba822ebc740 0 {'date': '* *', 'user': 'test'} (glob)
 
 Test evolve with conflict
 
 Get a successors of 8 on it
 
-  $ hg graft -O 8
-  grafting revision 8
+  $ hg grab 8
 
 Add real change to the successors
 

tests/test-touch.t

+
+  $ cat >> $HGRCPATH <<EOF
+  > [ui]
+  > logtemplate={rev}:{node|short} {desc}\n
+  > [defaults]
+  > amend=-d "0 0"
+  > [extensions]
+  > hgext.rebase=
+  > EOF
+  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
+
+  $ hg init repo
+  $ cd repo
+  $ echo A > a
+  $ hg add a
+  $ hg commit -m a
+
+Basic usage
+
+  $ hg log -G
+  @  0:e93df3427f45 a
+  
+  $ hg touch .
+  $ hg log -G
+  @  1:[0-9a-f]{12} a (re)
+  
+
+
+Revive usage
+
+  $ echo A > b
+  $ hg add b
+  $ hg commit -m ab --amend
+  $ hg up --hidden 1
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  Working directory parent is obsolete
+  $ hg log -G
+  o  3:[0-9a-f]{12} ab (re)
+  
+  @  1:[0-9a-f]{12} a (re)
+  
+  $ hg touch .
+  2 new divergent changesets
+  $ hg log -G
+  @  4:[0-9a-f]{12} a (re)
+  
+  o  3:[0-9a-f]{12} ab (re)
+  
+  $ hg prune 3
+  1 changesets pruned
+
+Duplicate
+
+  $ hg touch --duplicate .
+  $ hg log -G
+  @  5:[0-9a-f]{12} a (re)
+  
+  o  4:[0-9a-f]{12} a (re)
+  
+
+Multiple touch
+
+  $ echo C > c
+  $ hg add c
+  $ hg commit -m c
+  $ echo D > d
+  $ hg add d
+  $ hg commit -m d
+  $ hg log -G
+  @  7:[0-9a-f]{12} d (re)
+  |
+  o  6:[0-9a-f]{12} c (re)
+  |
+  o  5:[0-9a-f]{12} a (re)
+  
+  o  4:[0-9a-f]{12} a (re)
+  
+  $ hg touch 6:7
+  $ hg log -G
+  @  9:[0-9a-f]{12} d (re)
+  |
+  o  8:[0-9a-f]{12} c (re)
+  |
+  o  5:[0-9a-f]{12} a (re)
+  
+  o  4:[0-9a-f]{12} a (re)
+  
+

tests/test-tutorial.t

 not fit well in my standard shopping list)
 
   $ hg prune . # . is for working directory parent
+  1 changesets pruned
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   working directory now at 41aff6a42b75
 
 totally happy with yet. To be able to push "SPAM SPAM" I need a version of "SPAM SPAM" which is not a child of
 "bathroom stuff"
 
-You can use 'rebase -r' or 'graft -O' for that:
+You can use 'rebase -r' or 'grab' for that:
 
   $ hg up 'p1(10b8aeaa8cc8)' # going on "bathroom stuff" parent
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 In the mean time I noticed you can't buy animals in a super market and I prune the animal changeset:
 
   $ hg prune ee942144f952
+  1 changesets pruned
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   working directory now at a44c85f957d3
   1 new unstable changesets