Prepare 0.1.0 by merging default into stable stable
authorPierre-Yves David <pierre-yves.david@logilab.fr>
Fri, 24 Aug 2012 11:53:55 +0200
branchstable
changeset 499 a9c27df23129
parent 476 f17a0f801e0b (current diff)
parent 498 53d7e3413337 (diff)
child 500 4387e62bd4f4
Prepare 0.1.0 by merging default into stable stable is now compatible with 2.3 only.
enable.sh
--- a/.hgignore	Tue Aug 21 12:43:21 2012 +0200
+++ b/.hgignore	Fri Aug 24 11:53:55 2012 +0200
@@ -5,6 +5,9 @@
 ^html/
 \.pyc$
 ~$
+\.swp$
 \.orig$
 \.rej$
 \.err$
+^tests/easy_run.sh$
+^build/
--- a/README	Tue Aug 21 12:43:21 2012 +0200
+++ b/README	Fri Aug 24 11:53:55 2012 +0200
@@ -43,6 +43,26 @@
 Changelog
 ==================
 
+1.0
+
+- Align with Mercurial version 2.3 (drop 2.2 support).
+- stabilize handle killed parent
+- stabilize handle late comer
+- stabilize handle conflicting
+- stabilize get a --continue switch
+- merge and update ignore extinct changeset in most case.
+- new "troubled()" revset
+- summary now reports troubles changesets
+- new touch command
+- new fold command
+- new basic olog alias
+
+- rebase refuse to work on public changeset again
+- rebase explicitly state that there is nothing to rebase because everything is
+  extinct() when that happen.
+- amend now cleanly abort when --change switch is misused
+
+
 0.7 -- 2012-08-06
 
 - hook: work around insanely huge value in obsolete pushkey call
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/nopushpublish.py	Fri Aug 24 11:53:55 2012 +0200
@@ -0,0 +1,36 @@
+# Extension which prevent changeset to be turn public by push operation
+#
+# Copyright 2011 Logilab SA        <contact@logilab.fr>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+
+from mercurial import extensions, util
+from mercurial import discovery
+
+def checkpublish(orig, repo, remote, outgoing, *args):
+
+    # is remote publishing?
+    publish = True
+    if 'phases' in remote.listkeys('namespaces'):
+        remotephases = remote.listkeys('phases')
+        publish = remotephases.get('publishing', False)
+
+    npublish = 0
+    if publish:
+        for rev in outgoing.missing:
+            if repo[rev].phase():
+                npublish += 1
+    if npublish:
+        repo.ui.warn("Push would publish %s changesets" % npublish)
+
+    ret = orig(repo, remote, outgoing, *args)
+    if npublish:
+        raise util.Abort("Publishing push forbiden",
+                         hint="Use `hg phase -p <rev>` to manually publish them")
+
+    return ret
+
+def uisetup(ui):
+    extensions.wrapfunction(discovery, 'checkheads', checkpublish)
--- a/docs/evolve-collaboration.rst	Tue Aug 21 12:43:21 2012 +0200
+++ b/docs/evolve-collaboration.rst	Fri Aug 24 11:53:55 2012 +0200
@@ -111,7 +111,7 @@
 Being now done with grammar and typo fixes, Alice decides it is time to
 stabilize again the tree. She does::
 
-    $ hg stabilize
+    $ hg evolve
 
 two times, one for each unstable descendant. The last time, hgview
 shows her a straight line again. Wow! that feels a bit like a
--- a/docs/evolve-faq.rst	Tue Aug 21 12:43:21 2012 +0200
+++ b/docs/evolve-faq.rst	Fri Aug 24 11:53:55 2012 +0200
@@ -96,9 +96,9 @@
 Getting changes out of a commit
 ------------------------------------------------------------
 
-the ``hg uncommit`` commands allow you to rewrite the current commit to not
-include change for some file. The content of target files are not altered on
-disk and back as "modified"::
+The ``hg uncommit`` command lets you rewrite the parent commit without
+selected changed files. Target files content is not altered and
+appears again as "modified"::
 
   $ hg st
   M babar
@@ -112,8 +112,7 @@
 Split a changeset
 -----------------------
 
-I you just want to split whole file, you can just use the ``uncommit`` command.
-
+To split on file boundaries, just use ``uncommit`` command.
 
 If you need fine-grained split, there is no official command for that yet.
 However, it is easily achieved by manual operation::
@@ -153,7 +152,7 @@
 .. warning:: Beware that rebasing obsolete changesets will result in
              conflicting versions of the changesets.
 
-Stabilize history: ``stabilize``
+Stabilize history: ``evolve``
 ------------------------------------------------------------
 
 When you rewrite (amend) a changeset with children without rewriting
@@ -165,16 +164,6 @@
 newest version. This is not done automatically to avoid the
 proliferation of useless hidden changesets.
 
-.. warning:: ``hg stabilize`` have no --continue to use after conflict
-             resolution
-
-.. warning:: stabilization does not handle deletion yet.
-
-.. warning:: obsolete currently relies on changesets in secret phase
-              to avoid exchanging obsolete and unstable changesets.
-
-             XXX details issue here
-
 
 Fix my history afterward: ``prune -n``
 ------------------------------------------------------------
@@ -209,7 +198,7 @@
 
 You can also use a debug command
 
-    $ hg debugsuccessors
+    $ hg debugobsolete
       5eb72dbe0cb4 e8db4aa611f6
       c4cbebac3751 4f1c269eab68
 
@@ -223,8 +212,8 @@
 
 Extinct changesets are hidden using the *hidden* feature of mercurial.
 
-Only ``hg log`` and ``hgview`` support it. ``hg glog`` Only support that since
-2.2. Other visual viewer don't.
+Only ``hg log``, ``hg glog`` and ``hgview`` support it, other
+graphical viewer do not.
 
 
 
--- a/docs/from-mq.rst	Tue Aug 21 12:43:21 2012 +0200
+++ b/docs/from-mq.rst	Fri Aug 24 11:53:55 2012 +0200
@@ -12,7 +12,7 @@
 qnew                            ``commit``
 qrefresh                        ``amend``
 qpop                            ``update`` or ``qdown``
-qpush                           ``update`` or ``gup`` sometimes ``stabilize``
+qpush                           ``update`` or ``gup`` sometimes ``evolve``
 qrm                             ``prune``
 qfold                           ``amend -c`` (for now, ``collapse`` soon)
 qdiff                           ``odiff``
@@ -84,7 +84,7 @@
 hg qref -X
 ````````````
 
-To remove change from you current commit use::
+To remove changes from you current commit use::
 
   $ hg uncommit not-ready.txt
 
@@ -113,7 +113,7 @@
 The evolution extension adds a command to rewrite the "out of sync"
 changesets:::
 
-  $ hg stabilize
+  $ hg evolve
 
 You can also decide to do it manually using::
 
--- a/docs/index.rst	Tue Aug 21 12:43:21 2012 +0200
+++ b/docs/index.rst	Fri Aug 24 11:53:55 2012 +0200
@@ -16,13 +16,14 @@
 
 The effort is split in two parts:
 
- * The **obsolete marker** concept aims to provide an alternative to ``strip``
-   to get rid of changesets.
+ * The **obsolescence marker** concept aims to provide an alternative to ``strip``
+   to get rid of changesets. This concept have been partially implemented in
+   Mercurial 2.3.
 
  * The **evolve** mercurial extension rewrites history using obsolete
    *marker* under the hood.
 
-The first and most important step is by far the **obsolete marker**. However
+The first and most important step is by far the **obsolescence marker**. However
 most users will never be directly exposed to the concept. For this reason
 this manual starts with changeset evolution.
 
@@ -69,19 +70,20 @@
 
             Production ready version should hide such details to normal user.
 
+The evolve extension require mercurial 2.3
+
 To enable the evolve extension use::
 
     $ hg clone https://bitbucket.org/marmoute/mutable-history -u stable
-    $ mutable-history/enable.sh >> ~/.hgrc
+    $ echo '[extensions]\nevolve=$PWD/mutable-history/hgext/evolve.py' >> ~/.hgrc
 
-You will probably want to use the associated version of hgview (qt viewer
-recommended). ::
+You will probably want to use hgview_ to visualize obsolescence. Version 1.6.2
+or later is required.
 
-    $ hg clone http://hg-lab.logilab.org/wip/hgview/ -u obsolete
-    $ cd hgview
-    $ python setup.py install --user
+.. _hgview: http://www.logilab.org/project/hgview/
 
-Works with mercurial 2.2
+(The old version 0.7 of evolve works with mercurial 2.2 but have far less feature)
+
 
  ---
 
@@ -137,27 +139,13 @@
 Here is a list of know issue that will be fixed later:
 
 
-* ``hg stabilize`` does not handle merge conflict.
-
-    You must fallback to graft or rebase when that happen.
-
-* rewriting conflict are not detected yet``hg stabilize`` does not
-  handle them.
-
-* ``hg update`` can move an obsolete parent
-
 * you need to provide to `graft --continue -O` if you started you
   graft using `-O`.
 
-* ``hg merge`` considers an extinct head to be a valid target, hence requiring
   you to manually specify target all the time.
 
 * trying to exchange obsolete marker with a static http repo will crash.
 
-* trying to exchange a lot of obsolete markers through http crash.
-
-* Extinct changesets are turned secret by various commands.
-
 * Extinct changesets are hidden using the *hidden* feature of mercurial only
   supported by a few commands.
 
--- a/docs/instability.rst	Tue Aug 21 12:43:21 2012 +0200
+++ b/docs/instability.rst	Fri Aug 24 11:53:55 2012 +0200
@@ -183,11 +183,11 @@
     $ hg pull
     added 3 changeset
     +2 unstable changeset
-    (do you want "hg stabilize" ?)
+    (do you want "hg evolve" ?)
     working directory parent is obsolete!
     $ hg push
     outgoing unstable changesets
-    (use "hg stabilize" or force the push)
+    (use "hg evolve" or force the push)
 
 And should not not encourage people to create instability
 
--- a/docs/obs-terms.rst	Tue Aug 21 12:43:21 2012 +0200
+++ b/docs/obs-terms.rst	Fri Aug 24 11:53:55 2012 +0200
@@ -89,16 +89,16 @@
 |                     |                          |                             |
 |                     +--------------------------+-----------------------------+
 |                     |                          |                             |
-|                     | **troublesome**          | **unstable**                |
+|                     | **troubled**             | **unstable**                |
 |                     |                          |                             |
-|                     | *troublesome* has        | *unstable* is a changeset   |
+|                     | *troubled*    has        | *unstable* is a changeset   |
 |                     | unresolved issue caused  | with obsolete ancestors.    |
 |                     | by *obsolete* relations. |                             |
 |                     |                          |                             |
 |                     | Possible issues are      | It must be rebased on a     |
-|                     | listed in the next       | non *troublesome* base to   |
+|                     | listed in the next       | non *troubled*    base to   |
 |                     | column. It is possible   | solve the problem.          |
-|                     | for *troublesome*        |                             |
+|                     | for *troubled*           |                             |
 |                     | changeset to combine     | (possible alternative name: |
 |                     | multiple issue at once.  | precarious)                 |
 |                     | (a.k.a. conflicting and  |                             |
@@ -106,7 +106,7 @@
 |                     |                          |                             |
 |                     | (possible alternative    | **latecomer**               |
 |                     | names: unsettled,        |                             |
-|                     | troubled)                | *latecomer* is a changeset  |
+|                     | troublesome              | *latecomer* is a changeset  |
 |                     |                          | that tries to be successor  |
 |                     |                          | of  public changesets.      |
 |                     |                          |                             |
@@ -141,10 +141,13 @@
 |                     |                          | properly not detected as a  |
 |                     |                          | conflict)                   |
 |                     |                          |                             |
+|                     |                          | (possible alternative names:|
+|                     |                          | clashing, rival)            |
+|                     |                          |                             |
 |                     +--------------------------+-----------------------------+
 |                     |                                                        |
 |                     | Mutable changesets which are neither *obsolete* or     |
-|                     | *troublesome* are *"ok"*.                              |
+|                     | *troubled*    are *"ok"*.                              |
 |                     |                                                        |
 |                     | Do we really need a name for it ? *"ok"* is a pretty   |
 |                     | crappy name :-/ other possibilities are:               |
@@ -176,7 +179,7 @@
 
           - "ok" changeset
           - latecomer
-          - troublesome
+          - conflicting
 
           Any better idea are welcome.
 
@@ -222,7 +225,7 @@
 - kill: shall has funny effects when you forget "hg" in front of ``hg kill``.
 - obsolete: too vague, too long and too generic.
 
-Stabilize
+evolve
 ```````````````
 
 Automatically resolve *troublesome* changesets
@@ -231,9 +234,7 @@
 This is an important name as hg pull/push will suggest it the same way it
 suggest merging when you add heads.
 
-I do not like stabilize much.
-
 alternative names:
 
 - solve (too generic ?)
-- evolve (too vague)
+- stabilize
--- a/docs/tutorials/tutorial.t	Tue Aug 21 12:43:21 2012 +0200
+++ b/docs/tutorials/tutorial.t	Fri Aug 24 11:53:55 2012 +0200
@@ -223,7 +223,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  (run 'hg update' to get a working copy)
+  (run 'hg heads .' to see heads, 'hg merge' to merge)
 
 I now have a new heads. Note that this remote head is immutable
 
@@ -585,7 +585,7 @@
   |
   | @  ffa278c50818 (draft): bathroom stuff
   | |
-  o |  8a79ae8b029e (draft): bathroom stuff
+  x |  8a79ae8b029e (draft): bathroom stuff
   |/
   o  a2fccc2e7b08 (public): SPAM SPAM
   |
@@ -610,7 +610,7 @@
   pushing to $TESTTMP/other
   searching for changes
   abort: push includes an unstable changeset: 9ac5d0e790a2!
-  (use 'hg stabilize' to get a stable history (or --force to proceed))
+  (use 'hg stabilize' to get a stable history or --force to ignore warnings)
   [255]
  
 
@@ -723,7 +723,7 @@
   $ hg log -G
   o  ae45c0c3092a (draft): SPAM SPAM SPAM
   |
-  o  437efbcaf700 (draft): animals
+  x  437efbcaf700 (draft): animals
   |
   @  ffa278c50818 (draft): bathroom stuff
   |
--- a/enable.sh	Tue Aug 21 12:43:21 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-#!/bin/sh
-
-here=`python -c "import os; print os.path.realpath('$0')"`
-repo_root=`dirname "$here"`
-
-if !( hg --version -q | grep -qe 'version 2\.[2-9]' ); then
-    echo 'You need mercurial 2.2 or later' >&2
-    exit 2
-fi
-
-
-
-cat << EOF >&2
-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-XXX Add lines below to the [extensions] section of you hgrc XXX
-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-
-
-EOF
-
-cat << EOF | sed -e "s#XXXREPOPATHXXX#${repo_root}#"
-[extensions]
-### experimental extensions for history rewriting
-
-# obsolete relation support (will move in core)
-obsolete=XXXREPOPATHXXX/hgext/obsolete.py
-
-# history rewriting UI
-# needed by evolve
-hgext.rebase=
-evolve=XXXREPOPATHXXX/hgext/evolve.py
-
-
-[alias]
-### useful alias to check future amend result
-# equivalent to the qdiff command for mq
-
-# diff
-pdiff=diff --rev .^
-
-# status
-pstatus=status --rev .^
-
-# diff with the previous amend
-odiff=diff --rev 'limit(precursors(.),1)' --rev .
-EOF
-
-cat << EOF >&2
-
-
-### check qsync-enable.sh if your need mq export too.
-EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/__init__.py	Fri Aug 24 11:53:55 2012 +0200
@@ -0,0 +1,1 @@
+#f00
--- a/hgext/evolve.py	Tue Aug 21 12:43:21 2012 +0200
+++ b/hgext/evolve.py	Fri Aug 24 11:53:55 2012 +0200
@@ -7,23 +7,1098 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-'''a set of commands to handle changeset mutation'''
+'''Extends Mercurial feature related to Changeset Evolution
+
+This extension Provide several command tommutate history and deal with issue it may raise.
+
+It also:
+
+    - enable the "Changeset Obsolescence" feature of mercurial,
+    - alter core command and extension that rewrite history to use this feature,
+    - improve some aspect of the early implementation in 2.3
+'''
 
+import random
+
+from mercurial import util
+
+try:
+    from mercurial import obsolete
+    if not obsolete._enabled:
+        obsolete._enabled = True
+except ImportError:
+    raise util.Abort('Obsolete extension requires Mercurial 2.3 (or later)')
+
+from mercurial import bookmarks
 from mercurial import cmdutil
-from mercurial import scmutil
-from mercurial import node
-from mercurial import error
-from mercurial import extensions
-from mercurial import commands
-from mercurial import bookmarks
-from mercurial import phases
 from mercurial import commands
 from mercurial import context
 from mercurial import copies
-from mercurial import util
+from mercurial import discovery
+from mercurial import error
+from mercurial import extensions
+from mercurial import hg
+from mercurial import localrepo
+from mercurial import merge
+from mercurial import node
+from mercurial import phases
+from mercurial import revset
+from mercurial import scmutil
+from mercurial import templatekw
 from mercurial.i18n import _
-from mercurial.commands import walkopts, commitopts, commitopts2, logopts
-from mercurial import hg
+from mercurial.commands import walkopts, commitopts, commitopts2
+from mercurial.node import nullid
+
+
+
+# This extension contains the following code
+#
+# - Extension Helper code
+# - Obsolescence cache
+# - ...
+# - Older format compat
+
+
+
+#####################################################################
+### Extension helper                                              ###
+#####################################################################
+
+class exthelper(object):
+    """Helper for modular extension setup
+
+    A single helper should be instanciated for each extension. Helper
+    methods are then used as decorator for various purpose.
+
+    All decorators return the original function and may be chained.
+    """
+
+    def __init__(self):
+        self._uicallables = []
+        self._extcallables = []
+        self._repocallables = []
+        self._revsetsymbols = []
+        self._templatekws = []
+        self._commandwrappers = []
+        self._extcommandwrappers = []
+        self._functionwrappers = []
+        self._duckpunchers = []
+
+    def final_uisetup(self, ui):
+        """Method to be used as the extension uisetup
+
+        The following operations belong here:
+
+        - Changes to ui.__class__ . The ui object that will be used to run the
+          command has not yet been created. Changes made here will affect ui
+          objects created after this, and in particular the ui that will be
+          passed to runcommand
+        - Command wraps (extensions.wrapcommand)
+        - Changes that need to be visible to other extensions: because
+          initialization occurs in phases (all extensions run uisetup, then all
+          run extsetup), a change made here will be visible to other extensions
+          during extsetup
+        - Monkeypatch or wrap function (extensions.wrapfunction) of dispatch
+          module members
+        - Setup of pre-* and post-* hooks
+        - pushkey setup
+        """
+        for cont, funcname, func in self._duckpunchers:
+            setattr(cont, funcname, func)
+        for command, wrapper in self._commandwrappers:
+            extensions.wrapcommand(commands.table, command, wrapper)
+        for cont, funcname, wrapper in self._functionwrappers:
+            extensions.wrapfunction(cont, funcname, wrapper)
+        for c in self._uicallables:
+            c(ui)
+
+    def final_extsetup(self, ui):
+        """Method to be used as a the extension extsetup
+
+        The following operations belong here:
+
+        - Changes depending on the status of other extensions. (if
+          extensions.find('mq'))
+        - Add a global option to all commands
+        - Register revset functions
+        """
+        knownexts = {}
+        for name, symbol in self._revsetsymbols:
+            revset.symbols[name] = symbol
+        for name, kw in self._templatekws:
+            templatekw.keywords[name] = kw
+        for ext, command, wrapper in self._extcommandwrappers:
+            if ext not in knownexts:
+                e = extensions.find(ext)
+                if e is None:
+                    raise util.Abort('extension %s not found' % ext)
+                knownexts[ext] = e.cmdtable
+            extensions.wrapcommand(knownexts[ext], commands, wrapper)
+        for c in self._extcallables:
+            c(ui)
+
+    def final_reposetup(self, ui, repo):
+        """Method to be used as a the extension reposetup
+
+        The following operations belong here:
+
+        - All hooks but pre-* and post-*
+        - Modify configuration variables
+        - Changes to repo.__class__, repo.dirstate.__class__
+        """
+        for c in self._repocallables:
+            c(ui, repo)
+
+    def uisetup(self, call):
+        """Decorated function will be executed during uisetup
+
+        example::
+
+            @eh.uisetup
+            def setupbabar(ui):
+                print 'this is uisetup!'
+        """
+        self._uicallables.append(call)
+        return call
+
+    def extsetup(self, call):
+        """Decorated function will be executed during extsetup
+
+        example::
+
+            @eh.extsetup
+            def setupcelestine(ui):
+                print 'this is extsetup!'
+        """
+        self._uicallables.append(call)
+        return call
+
+    def reposetup(self, call):
+        """Decorated function will be executed during reposetup
+
+        example::
+
+            @eh.reposetup
+            def setupzephir(ui, repo):
+                print 'this is reposetup!'
+        """
+        self._repocallables.append(call)
+        return call
+
+    def revset(self, symbolname):
+        """Decorated function is a revset symbol
+
+        The name of the symbol must be given as the decorator argument.
+        The symbol is added during `extsetup`.
+
+        example::
+
+            @eh.revset('hidden')
+            def revsetbabar(repo, subset, x):
+                args = revset.getargs(x, 0, 0, 'babar accept no argument')
+                return [r for r in subset if 'babar' in repo[r].description()]
+        """
+        def dec(symbol):
+            self._revsetsymbols.append((symbolname, symbol))
+            return symbol
+        return dec
+
+
+    def templatekw(self, keywordname):
+        """Decorated function is a revset keyword
+
+        The name of the keyword must be given as the decorator argument.
+        The symbol is added during `extsetup`.
+
+        example::
+
+            @eh.templatekw('babar')
+            def kwbabar(ctx):
+                return 'babar'
+        """
+        def dec(keyword):
+            self._templatekws.append((keywordname, keyword))
+            return keyword
+        return dec
+
+    def wrapcommand(self, command, extension=None):
+        """Decorated function is a command wrapper
+
+        The name of the command must be given as the decorator argument.
+        The wrapping is installed during `uisetup`.
+
+        If the second option `extension` argument is provided, the wrapping
+        will be applied in the extension commandtable. This argument must be a
+        string that will be searched using `extension.find` if not found and
+        Abort error is raised. If the wrapping applies to an extension, it is
+        installed during `extsetup`
+
+        example::
+
+            @eh.wrapcommand('summary')
+            def wrapsummary(orig, ui, repo, *args, **kwargs):
+                ui.note('Barry!')
+                return orig(ui, repo, *args, **kwargs)
+
+        """
+        def dec(wrapper):
+            if extension is None:
+                self._commandwrappers.append((command, wrapper))
+            else:
+                self._extcommandwrappers.append((extension, command, wrapper))
+            return wrapper
+        return dec
+
+    def wrapfunction(self, container, funcname):
+        """Decorated function is a function wrapper
+
+        This function takes two arguments, the container and the name of the
+        function to wrap. The wrapping is performed during `uisetup`.
+        (there is no extension support)
+
+        example::
+
+            @eh.function(discovery, 'checkheads')
+            def wrapfunction(orig, *args, **kwargs):
+                ui.note('His head smashed in and his heart cut out')
+                return orig(*args, **kwargs)
+        """
+        def dec(wrapper):
+            self._functionwrappers.append((container, funcname, wrapper))
+            return wrapper
+        return dec
+
+    def addattr(self, container, funcname):
+        """Decorated function is to be added to the container
+
+        This function takes two arguments, the container and the name of the
+        function to wrap. The wrapping is performed during `uisetup`.
+
+        example::
+
+            @eh.function(context.changectx, 'babar')
+            def babar(ctx):
+                return 'babar' in ctx.description
+        """
+        def dec(func):
+            self._duckpunchers.append((container, funcname, func))
+            return func
+        return dec
+
+eh = exthelper()
+uisetup = eh.final_uisetup
+extsetup = eh.final_extsetup
+reposetup = eh.final_reposetup
+
+#####################################################################
+### Obsolescence Caching Logic                                    ###
+#####################################################################
+
+# Obsolescence related logic can be very slow if we don't have efficient cache.
+#
+# This section implements a cache mechanism that did not make it into core for
+# time reason. It store meaningful set of revision related to obsolescence
+# (obsolete, unstabletble ...
+#
+# Here is:
+#
+# - Computation of meaningful set,
+# - Cache access logic,
+# - Cache invalidation logic,
+# - revset and ctx using this cache.
+#
+
+
+### Computation of meaningful set
+#
+# Most set can be computed with "simple" revset.
+
+#: { set name -> function to compute this set } mapping
+#:   function take a single "repo" argument.
+#:
+#: Use the `cachefor` decorator to register new cache function
+cachefuncs = {}
+def cachefor(name):
+    """Decorator to register a function as computing the cache for a set"""
+    def decorator(func):
+        assert name not in cachefuncs
+        cachefuncs[name] = func
+        return func
+    return decorator
+
+@cachefor('obsolete')
+def _computeobsoleteset(repo):
+    """the set of obsolete revisions"""
+    obs = set()
+    nm = repo.changelog.nodemap
+    for prec in repo.obsstore.precursors:
+        rev = nm.get(prec)
+        if rev is not None:
+            obs.add(rev)
+    return set(repo.revs('%ld - public()', obs))
+
+@cachefor('unstable')
+def _computeunstableset(repo):
+    """the set of non obsolete revisions with obsolete parents"""
+    return set(repo.revs('(obsolete()::) - obsolete()'))
+
+@cachefor('suspended')
+def _computesuspendedset(repo):
+    """the set of obsolete parents with non obsolete descendants"""
+    return set(repo.revs('obsolete() and obsolete()::unstable()'))
+
+@cachefor('extinct')
+def _computeextinctset(repo):
+    """the set of obsolete parents without non obsolete descendants"""
+    return set(repo.revs('obsolete() - obsolete()::unstable()'))
+
+@eh.wrapfunction(obsolete.obsstore, '__init__')
+def _initobsstorecache(orig, obsstore, *args, **kwargs):
+    """add a cache attribute to obsstore"""
+    obsstore.caches = {}
+    return orig(obsstore, *args, **kwargs)
+
+### Cache access
+
+def getobscache(repo, name):
+    """Return the set of revision that belong to the <name> set
+
+    Such access may compute the set and cache it for future use"""
+    if not repo.obsstore:
+        return ()
+    if name not in repo.obsstore.caches:
+        repo.obsstore.caches[name] = cachefuncs[name](repo)
+    return repo.obsstore.caches[name]
+
+### Cache clean up
+#
+# To be simple we need to invalidate obsolescence cache when:
+#
+# - new changeset is added:
+# - public phase is changed
+# - obsolescence marker are added
+# - strip is used a repo
+
+
+def clearobscaches(repo):
+    """Remove all obsolescence related cache from a repo
+
+    This remove all cache in obsstore is the obsstore already exist on the
+    repo.
+
+    (We could be smarter here)"""
+    if 'obsstore' in repo._filecache:
+        repo.obsstore.caches.clear()
+
+@eh.wrapfunction(localrepo.localrepository, 'addchangegroup')  # new changeset
+@eh.wrapfunction(phases, 'retractboundary')  # phase movement
+@eh.wrapfunction(phases, 'advanceboundary')  # phase movement
+@eh.wrapfunction(localrepo.localrepository, 'destroyed')  # strip
+def wrapclearcache(orig, repo, *args, **kwargs):
+    try:
+        return orig(repo, *args, **kwargs)
+    finally:
+        # we are a bit wide here
+        # we could restrict to:
+        # advanceboundary + phase==public
+        # retractboundary + phase==draft
+        clearobscaches(repo)
+
+@eh.wrapfunction(obsolete.obsstore, 'add')  # new marker
+def clearonadd(orig, obsstore, *args, **kwargs):
+    try:
+        return orig(obsstore, *args, **kwargs)
+    finally:
+        obsstore.caches.clear()
+
+### Use the case
+# Function in core that could benefic from the cache are overwritten by cache using version
+
+# changectx method
+
+@eh.addattr(context.changectx, 'unstable')
+def unstable(ctx):
+    """is the changeset unstable (have obsolete ancestor)"""
+    if ctx.node() is None:
+        return False
+    return ctx.rev() in getobscache(ctx._repo, 'unstable')
+
+
+@eh.addattr(context.changectx, 'extinct')
+def extinct(ctx):
+    """is the changeset extinct by other"""
+    if ctx.node() is None:
+        return False
+    return ctx.rev() in getobscache(ctx._repo, 'extinct')
+
+# revset
+
+@eh.revset('obsolete')
+def revsetobsolete(repo, subset, x):
+    """``obsolete()``
+    Changeset is obsolete.
+    """
+    args = revset.getargs(x, 0, 0, 'obsolete takes no argument')
+    obsoletes = getobscache(repo, 'obsolete')
+    return [r for r in subset if r in obsoletes]
+
+@eh.revset('unstable')
+def revsetunstable(repo, subset, x):
+    """``unstable()``
+    Unstable changesets are non-obsolete with obsolete ancestors.
+    """
+    args = revset.getargs(x, 0, 0, 'unstable takes no arguments')
+    unstables = getobscache(repo, 'unstable')
+    return [r for r in subset if r in unstables]
+
+@eh.revset('extinct')
+def revsetextinct(repo, subset, x):
+    """``extinct()``
+    Obsolete changesets with obsolete descendants only.
+    """
+    args = revset.getargs(x, 0, 0, 'extinct takes no arguments')
+    extincts = getobscache(repo, 'extinct')
+    return [r for r in subset if r in extincts]
+
+#####################################################################
+### Complete troubles computation logic                           ###
+#####################################################################
+
+# there is two kind of trouble not handled by core right now:
+# - latecomer: (successors for public changeset)
+# - conflicting: (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
+
+@cachefor('latecomer')
+def _computelatecomerset(repo):
+    """the set of rev trying to obsolete public revision"""
+    candidates = _allsuccessors(repo, repo.revs('public()'),
+                                                haltonflags=latediff)
+    query = '%ld - obsolete() - public()'
+    return set(repo.revs(query, candidates))
+
+@cachefor('conflicting')
+def _computeconflictingset(repo):
+    """the set of rev trying to obsolete public revision"""
+    conflicting = set()
+    obsstore = repo.obsstore
+    newermap = {}
+    for ctx in repo.set('(not public()) - obsolete()'):
+        prec = obsstore.successors.get(ctx.node(), ())
+        toprocess = set(prec)
+        while toprocess:
+            prec = toprocess.pop()[0]
+            if prec not in newermap:
+                newermap[prec] = newerversion(repo, prec)
+            newer = [n for n in newermap[prec] if n] # filter kill
+            if len(newer) > 1:
+                conflicting.add(ctx.rev())
+                break
+        toprocess.update(obsstore.successors.get(prec, ()))
+    return conflicting
+
+### changectx method
+
+@eh.addattr(context.changectx, 'latecomer')
+def latecomer(ctx):
+    """is the changeset latecomer (Try to succeed to public change)"""
+    if ctx.node() is None:
+        return False
+    return ctx.rev() in getobscache(ctx._repo, 'latecomer')
+
+@eh.addattr(context.changectx, 'conflicting')
+def conflicting(ctx):
+    """is the changeset conflicting (Try to succeed to public change)"""
+    if ctx.node() is None:
+        return False
+    return ctx.rev() in getobscache(ctx._repo, 'conflicting')
+
+### revset symbol
+
+@eh.revset('latecomer')
+def revsetlatecomer(repo, subset, x):
+    """``latecomer()``
+    Changesets marked as successors of public changesets.
+    """
+    args = revset.getargs(x, 0, 0, 'latecomer takes no arguments')
+    lates = getobscache(repo, 'latecomer')
+    return [r for r in subset if r in lates]
+
+@eh.revset('conflicting')
+def revsetconflicting(repo, subset, x):
+    """``conflicting()``
+    Changesets marked as successors of a same changeset.
+    """
+    args = revset.getargs(x, 0, 0, 'conflicting takes no arguments')
+    conf = getobscache(repo, 'conflicting')
+    return [r for r in subset if r in conf]
+
+
+### Discovery wrapping
+
+@eh.wrapfunction(discovery, 'checkheads')
+def wrapcheckheads(orig, repo, remote, outgoing, *args, **kwargs):
+    """wrap mercurial.discovery.checkheads
+
+    * prevent latecomer and unstable to be pushed
+    """
+    # do not push instability
+    for h in outgoing.missingheads:
+        # Checking heads is enough, obsolete descendants are either
+        # obsolete or unstable.
+        ctx = repo[h]
+        if ctx.latecomer():
+            raise util.Abort(_("push includes a latecomer changeset: %s!")
+                             % ctx)
+        if ctx.conflicting():
+            raise util.Abort(_("push includes a conflicting changeset: %s!")
+                             % ctx)
+    return orig(repo, remote, outgoing, *args, **kwargs)
+
+#####################################################################
+### Filter extinct changeset from common operation                ###
+#####################################################################
+
+@eh.wrapfunction(merge, 'update')
+def wrapmergeupdate(orig, repo, node, *args, **kwargs):
+    """ensure we don't automatically update on hidden changeset"""
+    if node is None:
+        # tip of current branch
+        branch = repo[None].branch()
+        node = repo.revs('last((.:: and branch(%s)) - hidden())', branch)[0]
+    return orig(repo, node, *args, **kwargs)
+
+@eh.wrapfunction(localrepo.localrepository, 'branchtip')
+def obsbranchtip(orig, repo, branch):
+    """ensure "stable" reference does not end on a hidden changeset"""
+    result = ()
+    heads = repo.branchmap().get(branch, ())
+    if heads:
+        result = list(repo.set('last(heads(branch(%n) - hidden()))', heads[0]))
+    if not result:
+        raise error.RepoLookupError(_("unknown branch '%s'") % branch)
+    return result[0].node()
+
+
+#####################################################################
+### Additional Utilities                                          ###
+#####################################################################
+
+# This section contains a lot of small utility function and method
+
+# - Function to create markers
+# - useful alias pstatus and pdiff (should probably go in evolve)
+# - "troubles" method on changectx
+# - function to travel throught the obsolescence graph
+# - function to find useful changeset to stabilize
+
+### Marker Create
+
+def createmarkers(repo, relations, metadata=None, flag=0):
+    """Add obsolete markers between changeset in a repo
+
+    <relations> must be an iterable of (<old>, (<new>, ...)) tuple.
+    `old` and `news` are changectx.
+
+    Current user and date are used except if specified otherwise in the
+    metadata attribute.
+
+    /!\ assume the repo have been locked by the user /!\
+    """
+    # prepare metadata
+    if metadata is None:
+        metadata = {}
+    if 'date' not in metadata:
+        metadata['date'] = '%i %i' % util.makedate()
+    if 'user' not in metadata:
+        metadata['user'] = repo.ui.username()
+    # check future marker
+    tr = repo.transaction('add-obsolescence-marker')
+    try:
+        for prec, sucs in relations:
+            if not prec.mutable():
+                raise util.Abort("Cannot obsolete immutable changeset: %s" % prec)
+            nprec = prec.node()
+            nsucs = tuple(s.node() for s in sucs)
+            if nprec in nsucs:
+                raise util.Abort("Changeset %s cannot obsolete himself" % prec)
+            repo.obsstore.create(tr, nprec, nsucs, flag, metadata)
+            clearobscaches(repo)
+        tr.close()
+    finally:
+        tr.release()
+
+
+### Useful alias
+
+@eh.uisetup
+def _installalias(ui):
+    if ui.config('alias', 'pstatus', None) is None:
+        ui.setconfig('alias', 'pstatus', 'status --rev .^')
+    if ui.config('alias', 'pdiff', None) is None:
+        ui.setconfig('alias', 'pdiff', 'diff --rev .^')
+    if ui.config('alias', 'olog', None) is None:
+        ui.setconfig('alias', 'olog', "log -r 'precursors(.)' --hidden")
+    if ui.config('alias', 'odiff', None) is None:
+        ui.setconfig('alias', 'odiff', "diff --rev 'limit(precursors(.),1)' --rev .")
+
+# - "troubles" method on changectx
+
+@eh.addattr(context.changectx, 'troubles')
+def troubles(ctx):
+    """Return a tuple listing all the troubles that affect a changeset
+
+    Troubles may be "unstable", "latecomer" or "conflicting".
+    """
+    troubles = []
+    if ctx.unstable():
+        troubles.append('unstable')
+    if ctx.latecomer():
+        troubles.append('latecomer')
+    if ctx.conflicting():
+        troubles.append('conflicting')
+    return tuple(troubles)
+
+### Troubled revset symbol
+
+@eh.revset('troubled')
+def revsetlatecomer(repo, subset, x):
+    """``troubled()``
+    Changesets with troubles.
+    """
+    _ = revset.getargs(x, 0, 0, 'troubled takes no arguments')
+    return list(repo.revs('%ld and (unstable() + latecomer() + conflicting())',
+                          subset))
+
+
+### Obsolescence graph
+
+# XXX SOME MAJOR CLEAN UP TO DO HERE XXX
+
+def _precursors(repo, s):
+    """Precursor of a changeset"""
+    cs = set()
+    nm = repo.changelog.nodemap
+    markerbysubj = repo.obsstore.successors
+    for r in s:
+        for p in markerbysubj.get(repo[r].node(), ()):
+            pr = nm.get(p[0])
+            if pr is not None:
+                cs.add(pr)
+    return cs
+
+def _allprecursors(repo, s):  # XXX we need a better naming
+    """transitive precursors of a subset"""
+    toproceed = [repo[r].node() for r in s]
+    seen = set()
+    allsubjects = repo.obsstore.successors
+    while toproceed:
+        nc = toproceed.pop()
+        for mark in allsubjects.get(nc, ()):
+            np = mark[0]
+            if np not in seen:
+                seen.add(np)
+                toproceed.append(np)
+    nm = repo.changelog.nodemap
+    cs = set()
+    for p in seen:
+        pr = nm.get(p)
+        if pr is not None:
+            cs.add(pr)
+    return cs
+
+def _successors(repo, s):
+    """Successors of a changeset"""
+    cs = set()
+    nm = repo.changelog.nodemap
+    markerbyobj = repo.obsstore.precursors
+    for r in s:
+        for p in markerbyobj.get(repo[r].node(), ()):
+            for sub in p[1]:
+                sr = nm.get(sub)
+                if sr is not None:
+                    cs.add(sr)
+    return cs
+
+def _allsuccessors(repo, s, haltonflags=0):  # XXX we need a better naming
+    """transitive successors of a subset
+
+    haltonflags allows to provide flags which prevent the evaluation of a
+    marker.  """
+    toproceed = [repo[r].node() for r in s]
+    seen = set()
+    allobjects = repo.obsstore.precursors
+    while toproceed:
+        nc = toproceed.pop()
+        for mark in allobjects.get(nc, ()):
+            if mark[2] & haltonflags:
+                continue
+            for sub in mark[1]:
+                if sub == nullid:
+                    continue # should not be here!
+                if sub not in seen:
+                    seen.add(sub)
+                    toproceed.append(sub)
+    nm = repo.changelog.nodemap
+    cs = set()
+    for s in seen:
+        sr = nm.get(s)
+        if sr is not None:
+            cs.add(sr)
+    return cs
+
+
+
+def newerversion(repo, obs):
+    """Return the newer version of an obsolete changeset"""
+    toproceed = set([(obs,)])
+    # XXX known optimization available
+    newer = set()
+    objectrels = repo.obsstore.precursors
+    while toproceed:
+        current = toproceed.pop()
+        assert len(current) <= 1, 'splitting not handled yet. %r' % current
+        current = [n for n in current if n != nullid]
+        if current:
+            n, = current
+            if n in objectrels:
+                markers = objectrels[n]
+                for mark in markers:
+                    toproceed.add(tuple(mark[1]))
+            else:
+                newer.add(tuple(current))
+        else:
+            newer.add(())
+    return sorted(newer)
+
+
+#####################################################################
+### Extending revset and template                                 ###
+#####################################################################
+
+# this section add several useful revset symbol not yet in core.
+# they are subject to changes
+
+### hidden revset is not in core yet
+
+@eh.revset('hidden')
+def revsethidden(repo, subset, x):
+    """``hidden()``
+    Changeset is hidden.
+    """
+    args = revset.getargs(x, 0, 0, 'hidden takes no argument')
+    return [r for r in subset if r in repo.hiddenrevs]
+
+### XXX I'm not sure this revset is useful
+@eh.revset('suspended')
+def revsetsuspended(repo, subset, x):
+    """``suspended()``
+    Obsolete changesets with non-obsolete descendants.
+    """
+    args = revset.getargs(x, 0, 0, 'suspended takes no arguments')
+    suspended = getobscache(repo, 'suspended')
+    return [r for r in subset if r in suspended]
+
+
+@eh.revset('precursors')
+def revsetprecursors(repo, subset, x):
+    """``precursors(set)``
+    Immediate precursors of changesets in set.
+    """
+    s = revset.getset(repo, range(len(repo)), x)
+    cs = _precursors(repo, s)
+    return [r for r in subset if r in cs]
+
+
+@eh.revset('allprecursors')
+def revsetallprecursors(repo, subset, x):
+    """``allprecursors(set)``
+    Transitive precursors of changesets in set.
+    """
+    s = revset.getset(repo, range(len(repo)), x)
+    cs = _allprecursors(repo, s)
+    return [r for r in subset if r in cs]
+
+
+@eh.revset('successors')
+def revsetsuccessors(repo, subset, x):
+    """``successors(set)``
+    Immediate successors of changesets in set.
+    """
+    s = revset.getset(repo, range(len(repo)), x)
+    cs = _successors(repo, s)
+    return [r for r in subset if r in cs]
+
+@eh.revset('allsuccessors')
+def revsetallsuccessors(repo, subset, x):
+    """``allsuccessors(set)``
+    Transitive successors of changesets in set.
+    """
+    s = revset.getset(repo, range(len(repo)), x)
+    cs = _allsuccessors(repo, s)
+    return [r for r in subset if r in cs]
+
+### template keywords
+# XXX it does not handle troubles well :-/
+
+@eh.templatekw('obsolete')
+def obsoletekw(repo, ctx, templ, **args):
+    """:obsolete: String. The obsolescence level of the node, could be
+    ``stable``, ``unstable``, ``suspended`` or ``extinct``.
+    """
+    rev = ctx.rev()
+    if ctx.obsolete():
+        if ctx.extinct():
+            return 'extinct'
+        else:
+            return 'suspended'
+    elif ctx.unstable():
+        return 'unstable'
+    return 'stable'
+
+#####################################################################
+### Various trouble warning                                       ###
+#####################################################################
+
+# This section take care of issue warning to the user when troubles appear
+
+@eh.wrapcommand("update")
+@eh.wrapcommand("pull")
+def wrapmayobsoletewc(origfn, ui, repo, *args, **opts):
+    """Warn that the working directory parent is an obsolete changeset"""
+    res = origfn(ui, repo, *args, **opts)
+    if repo['.'].obsolete():
+        ui.warn(_('Working directory parent is obsolete\n'))
+    return res
+
+# XXX this could wrap transaction code
+# XXX (but this is a bit a layer violation)
+@eh.wrapcommand("commit")
+@eh.wrapcommand("push")
+@eh.wrapcommand("pull")
+@eh.wrapcommand("graft")
+@eh.wrapcommand("phase")
+@eh.wrapcommand("unbundle")
+def warnobserrors(orig, ui, repo, *args, **kwargs):
+    """display warning is the command resulted in more instable changeset"""
+    priorunstables = len(repo.revs('unstable()'))
+    priorlatecomers = len(repo.revs('latecomer()'))
+    priorconflictings = len(repo.revs('conflicting()'))
+    ret = orig(ui, repo, *args, **kwargs)
+    newunstables = len(repo.revs('unstable()')) - priorunstables
+    newlatecomers = len(repo.revs('latecomer()')) - priorlatecomers
+    newconflictings = len(repo.revs('conflicting()')) - priorconflictings
+    if newunstables > 0:
+        ui.warn(_('%i new unstable changesets\n') % newunstables)
+    if newlatecomers > 0:
+        ui.warn(_('%i new latecomer changesets\n') % newlatecomers)
+    if newconflictings > 0:
+        ui.warn(_('%i new conflicting changesets\n') % newconflictings)
+    return ret
+
+@eh.reposetup
+def _repostabilizesetup(ui, repo):
+    """Add a hint for "hg evolve" when troubles make push fails
+    """
+    if not repo.local():
+        return
+
+    opush = repo.push
+
+    class evolvingrepo(repo.__class__):
+        def push(self, remote, *args, **opts):
+            """wrapper around pull that pull obsolete relation"""
+            try:
+                result = opush(remote, *args, **opts)
+            except util.Abort, ex:
+                hint = _("use 'hg evolve' to get a stable history "
+                         "or --force to ignore warnings")
+                if (len(ex.args) >= 1
+                    and ex.args[0].startswith('push includes ')
+                    and ex.hint is None):
+                    ex.hint = hint
+                raise
+            return result
+    repo.__class__ = evolvingrepo
+
+@eh.wrapcommand("summary")
+def obssummary(orig, ui, repo, *args, **kwargs):
+    ret = orig(ui, repo, *args, **kwargs)
+    nbunstable = len(getobscache(repo, 'unstable'))
+    nblatecomer = len(getobscache(repo, 'latecomer'))
+    nbconflicting = len(getobscache(repo, 'unstable'))
+    if nbunstable:
+        ui.write('unstable: %i changesets\n' % nbunstable)
+    else:
+        ui.note('unstable: 0 changesets\n')
+    if nblatecomer:
+        ui.write('latecomer: %i changesets\n' % nblatecomer)
+    else:
+        ui.note('latecomer: 0 changesets\n')
+    if nbconflicting:
+        ui.write('conflicting: %i changesets\n' % nbconflicting)
+    else:
+        ui.note('conflicting: 0 changesets\n')
+    return ret
+
+
+#####################################################################
+### Core Other extension compat                                   ###
+#####################################################################
+
+# This section make official history rewritter create obsolete marker
+
+
+### commit --amend
+# make commit --amend create obsolete marker
+#
+# The precursor is still strip from the repository.
+
+@eh.wrapfunction(cmdutil, 'amend')
+def wrapcmdutilamend(orig, ui, repo, commitfunc, old, *args, **kwargs):
+    oldnode = old.node()
+    new = orig(ui, repo, commitfunc, old, *args, **kwargs)
+    if new != oldnode:
+        lock = repo.lock()
+        try:
+            tr = repo.transaction('post-amend-obst')
+            try:
+                meta = {
+                    'date':  '%i %i' % util.makedate(),
+                    'user': ui.username(),
+                    }
+                repo.obsstore.create(tr, oldnode, [new], 0, meta)
+                tr.close()
+                clearobscaches(repo)
+            finally:
+                tr.release()
+        finally:
+            lock.release()
+    return new
+
+### rebase
+#
+# - ignore obsolete changeset
+# - create obsolete marker *instead of* striping
+
+def buildstate(orig, repo, dest, rebaseset, *ags, **kws):
+    """wrapper for rebase 's buildstate that exclude obsolete changeset"""
+
+    rebaseset = repo.revs('%ld - extinct()', rebaseset)
+    if not rebaseset:
+        repo.ui.warn(_('whole rebase set is extinct and ignored.\n'))
+        return {}
+    root = min(rebaseset)
+    if not repo._rebasekeep and not repo[root].mutable():
+        raise util.Abort(_("can't rebase immutable changeset %s") % repo[root],
+                         hint=_('see hg help phases for details'))
+    return orig(repo, dest, rebaseset, *ags, **kws)
+
+def defineparents(orig, repo, rev, target, state, *args, **kwargs):
+    rebasestate = getattr(repo, '_rebasestate', None)
+    if rebasestate is not None:
+        repo._rebasestate = dict(state)
+        repo._rebasetarget = target
+    return orig(repo, rev, target, state, *args, **kwargs)
+
+def concludenode(orig, repo, rev, p1, *args, **kwargs):
+    """wrapper for rebase 's concludenode that set obsolete relation"""
+    newrev = orig(repo, rev, p1, *args, **kwargs)
+    rebasestate = getattr(repo, '_rebasestate', None)
+    if rebasestate is not None:
+        if newrev is not None:
+            nrev = repo[newrev].rev()
+        else:
+            nrev = p1
+        repo._rebasestate[rev] = nrev
+    return newrev
+
+def cmdrebase(orig, ui, repo, *args, **kwargs):
+
+    reallykeep = kwargs.get('keep', False)
+    kwargs = dict(kwargs)
+    kwargs['keep'] = True
+    repo._rebasekeep = reallykeep
+
+    # We want to mark rebased revision as obsolete and set their
+    # replacements if any. Doing it in concludenode() prevents
+    # aborting the rebase, and is not called with all relevant
+    # revisions in --collapse case. Instead, we try to track the
+    # rebase state structure by sampling/updating it in
+    # defineparents() and concludenode(). The obsolete markers are
+    # added from this state after a successful call.
+    repo._rebasestate = {}
+    repo._rebasetarget = None
+    try:
+        l = repo.lock()
+        try:
+            res = orig(ui, repo, *args, **kwargs)
+            if not reallykeep:
+                # Filter nullmerge or unrebased entries
+                repo._rebasestate = dict(p for p in repo._rebasestate.iteritems()
+                                         if p[1] >= 0)
+                if not res and not kwargs.get('abort') and repo._rebasestate:
+                    # Rebased revisions are assumed to be descendants of
+                    # targetrev. If a source revision is mapped to targetrev
+                    # or to another rebased revision, it must have been
+                    # removed.
+                    markers = []
+                    if kwargs.get('collapse'):
+                        # collapse assume revision disapear because they are all
+                        # in the created revision
+                        newrevs = set(repo._rebasestate.values())
+                        newrevs.remove(repo._rebasetarget)
+                        if newrevs:
+                            # we create new revision.
+                            # A single one by --collapse design
+                            assert len(newrevs) == 1
+                            new = tuple(repo[n] for n in newrevs)
+                        else:
+                            # every body died. no new changeset created
+                            new = (repo[repo._rebasetarget],)
+                        for rev, newrev in sorted(repo._rebasestate.items()):
+                            markers.append((repo[rev], new))
+                    else:
+                        # no collapse assume revision disapear because they are
+                        # contained in parent
+                        for rev, newrev in sorted(repo._rebasestate.items()):
+                            markers.append((repo[rev], (repo[newrev],)))
+                    createmarkers(repo, markers)
+            return res
+        finally:
+            l.release()
+    finally:
+        delattr(repo, '_rebasestate')
+        delattr(repo, '_rebasetarget')
+
+@eh.extsetup
+def _rebasewrapping(ui):
+    # warning about more obsolete
+    try:
+        rebase = extensions.find('rebase')
+        if rebase:
+            entry = extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors)
+            extensions.wrapfunction(rebase, 'buildstate', buildstate)
+            extensions.wrapfunction(rebase, 'defineparents', defineparents)
+            extensions.wrapfunction(rebase, 'concludenode', concludenode)
+            extensions.wrapcommand(rebase.cmdtable, "rebase", cmdrebase)
+    except KeyError:
+        pass  # rebase not found
+
+
+#####################################################################
+### Old Evolve extension content                                  ###
+#####################################################################
+
+# XXX need clean up and proper sorting in other section
 
 ### util function
 #############################
@@ -118,8 +1193,9 @@
         if created:
             updatebookmarks(newid)
             # add evolution metadata
-            collapsed = set([u.node() for u in updates] + [old.node()])
-            repo.addcollapsedobsolete(collapsed, new.node())
+            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
@@ -130,6 +1206,9 @@
 
     return newid, created
 
+class MergeFailure(util.Abort):
+    pass
+
 def relocate(repo, orig, dest):
     """rewrite <rev> on dest"""
     try:
@@ -151,19 +1230,19 @@
         try:
             nodenew = rebase.concludenode(repo, orig.node(), dest.node(),
                                           node.nullid)
-        except util.Abort:
-            repo.ui.write_err(_('/!\\ stabilize failed                          /!\\\n'))
-            repo.ui.write_err(_('/!\\ Their is no "hg stabilize --continue"     /!\\\n'))
-            repo.ui.write_err(_('/!\\ use "hg up -C . ; hg stabilize --dry-run" /!\\\n'))
+        except util.Abort, exc:
+            class LocalMergeFailure(MergeFailure, exc.__class__):
+                pass
+            exc.__class__ = LocalMergeFailure
             raise
         oldbookmarks = repo.nodebookmarks(nodesrc)
         if nodenew is not None:
             phases.retractboundary(repo, destphase, [nodenew])
-            repo.addobsolete(nodenew, nodesrc)
+            createmarkers(repo, [(repo[nodesrc], (repo[nodenew],))])
             for book in oldbookmarks:
                 repo._bookmarks[book] = nodenew
         else:
-            repo.addobsolete(node.nullid, nodesrc)
+            createmarkers(repo, [(repo[nodesrc], ())])
             # Behave like rebase, move bookmarks to dest
             for book in oldbookmarks:
                 repo._bookmarks[book] = dest.node()
@@ -171,12 +1250,13 @@
             repo._bookmarks[book] = dest.node()
         if oldbookmarks or destbookmarks:
             bookmarks.write(repo)
+        return nodenew
     except util.Abort:
         # Invalidate the previous setparents
         repo.dirstate.invalidate()
         raise
 
-def stabilizableunstable(repo, pctx):
+def _stabilizableunstable(repo, pctx):
     """Return a changectx for an unstable changeset which can be
     stabilized on top of pctx or one of its descendants. None if none
     can be found.
@@ -189,7 +1269,7 @@
     # Look for an unstable which can be stabilized as a child of
     # node. The unstable must be a child of one of node predecessors.
     for ctx in selfanddescendants(repo, pctx):
-        unstables = list(repo.set('unstable() and children(obsancestors(%d))',
+        unstables = list(repo.set('unstable() and children(allprecursors(%d))',
                                   ctx.rev()))
         if unstables:
             return unstables[0]
@@ -219,51 +1299,101 @@
 cmdtable = {}
 command = cmdutil.command(cmdtable)
 
-@command('^stabilize|evolve|solve',
+@command('^evolve|stabilize|evolve|solve',
     [('n', 'dry-run', False, 'do not perform actions, print what to be done'),
-    ('A', 'any', False, 'stabilize any unstable changeset'),],
+    ('A', 'any', False, 'evolve any troubled changeset'),
+    ('c', 'continue', False, 'continue an interrupted evolution'), ],
     _('[OPTIONS]...'))
-def stabilize(ui, repo, **opts):
-    """rebase an unstable changeset to make it stable again
+def evolve(ui, repo, **opts):
+    """Solve trouble in your repository
+
+    - rebase unstable changeset to make it stable again,
+    - create proper diff from latecomer changeset,
+    - merge conflicting changeset.
 
-    By default, take the first unstable changeset which could be
-    rebased as child of the working directory parent revision or one
-    of its descendants and rebase it.
+    By default, take the first troubles changeset that looks relevant.
+
+    (The logic is still a bit fuzzy)
 
-    With --any, stabilize any unstable changeset.
+    - For unstable, that mean the first which could be rebased as child of the
+      working directory parent revision or one of its descendants and rebase
+      it.
 
-    The working directory is updated to the rebased revision.
+    - For conflicting this mean "." if applicable.
+
+    With --any, evolve pick any troubled changeset to solve
+
+    The working directory is updated to the newly created revision.
     """
 
-    obsolete = extensions.find('obsolete')
+    contopt = opts['continue']
+    anyopt = opts['any']
+
+    if contopt:
+        if anyopt:
+            raise util.Abort('can not specify both "--any" and "--continue"')
+        graftcmd = commands.table['graft'][0]
+        return graftcmd(ui, repo, old_obsolete=True, **{'continue': True})
+
+    troubled = list(repo.revs('troubled()'))
+    tr = _picknexttroubled(ui, repo, anyopt)
+    if tr is None:
+        if troubled:
+            ui.write_err(_('nothing to evolve here\n'))
+            ui.status(_('(%i troubled changesets, do you want --any ?)\n')
+                      % len(troubled))
+            return 2
+        else:
+            ui.write_err(_('no troubled changeset\n'))
+            return 1
+    cmdutil.bailifchanged(repo)
+    troubles = tr.troubles()
+    if 'unstable' in troubles:
+        return _solveunstable(ui, repo, tr, opts['dry_run'])
+    elif 'latecomer' in troubles:
+        return _solvelatecomer(ui, repo, tr, opts['dry_run'])
+    elif 'conflicting' in troubles:
+        return _solveconflicting(ui, repo, tr, opts['dry_run'])
+    else:
+        assert False  # WHAT? unknown troubles
 
-    node = None
-    if not opts['any']:
-        node = stabilizableunstable(repo, repo['.'])
-    if node is None:
-        unstables = list(repo.set('unstable()'))
-        if unstables and not opts['any']:
-            ui.write_err(_('nothing to stabilize here\n'))
-            ui.status(_('(%i unstable changesets, do you want --any ?)\n')
-                      % len(unstables))
-            return 2
-        elif not unstables:
-            ui.write_err(_('no unstable changeset\n'))
-            return 1
-        node = unstables[0]
+def _picknexttroubled(ui, repo, any=False):
+    """Pick a the next trouble changeset to solve"""
+    tr = _stabilizableunstable(repo, repo['.'])
+    if tr is None:
+        wdp = repo['.']
+        if 'conflicting' in wdp.troubles():
+            tr = wdp
+    if tr is None and any:
+        troubled = list(repo.set('unstable()'))
+        if not troubled:
+            troubled = list(repo.set('latecomer()'))
+        if not troubled:
+            troubled = list(repo.set('conflicting()'))
+        if troubled:
+            tr = troubled[0]
 
-    obs = node.parents()[0]
+    return tr
+
+
+def _solveunstable(ui, repo, orig, dryrun=False):
+    """Stabilize a unstable changeset"""
+    obs = orig.parents()[0]
     if not obs.obsolete():
-        obs = node.parents()[1]
+        obs = orig.parents()[1]
     assert obs.obsolete()
-    newer = obsolete.newerversion(repo, obs.node())
+    newer = newerversion(repo, obs.node())
+    # search of a parent which is not killed
+    while newer == [()]:
+        ui.debug("stabilize target %s is plain dead,"
+                 " trying to stabilize on it's parent")
+        obs = obs.parents()[0]
+        newer = newerversion(repo, obs.node())
     if len(newer) > 1:
         ui.write_err(_("conflict rewriting. can't choose destination\n"))
         return 2
     targets = newer[0]
-    if not targets:
-        ui.write_err(_("does not handle kill parent yet\n"))
-        return 2
+    assert targets
     if len(targets) > 1:
         ui.write_err(_("does not handle splitted parent yet\n"))
         return 2
@@ -272,21 +1402,221 @@
     target = repo[target]
     repo.ui.status(_('move:'))
     if not ui.quiet:
-        displayer.show(node)
+        displayer.show(orig)
     repo.ui.status(_('atop:'))
     if not ui.quiet:
         displayer.show(target)
-    todo= 'hg rebase -Dr %s -d %s\n' % (node, target)
-    if opts['dry_run']:
+    todo = 'hg rebase -Dr %s -d %s\n' % (orig, target)
+    if dryrun:
         repo.ui.write(todo)
     else:
         repo.ui.note(todo)
         lock = repo.lock()
         try:
-            relocate(repo, node, target)
+            relocate(repo, orig, target)
+        except MergeFailure:
+            repo.opener.write('graftstate', orig.hex() + '\n')
+            repo.ui.write_err(_('evolve failed!\n'))
+            repo.ui.write_err(_('fix conflict and run "hg evolve --continue"\n'))
+            raise
         finally:
             lock.release()
 
+def _solvelatecomer(ui, repo, latecomer, dryrun=False):
+    """Stabilize a latecomer changeset"""
+    # For now we deny latecomer merge
+    if len(latecomer.parents()) > 1:
+        raise util.Abort('late comer stabilization is confused by latecomer'
+                         ' %s being a merge' % latecomer)
+    prec = repo.set('last(allprecursors(%d) and public())', latecomer).next()
+    # For now we deny target merge
+    if len(prec.parents()) > 1:
+        raise util.Abort('late comer evolution is confused by precursors'
+                         ' %s being a merge' % prec)
+
+    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
+    repo.ui.status(_('recreate:'))
+    if not ui.quiet:
+        displayer.show(latecomer)
+    repo.ui.status(_('atop:'))
+    if not ui.quiet:
+        displayer.show(prec)
+    if dryrun:
+        todo = 'hg rebase --rev %s --detach %s;\n' % (latecomer, prec.p1())
+        repo.ui.write(todo)
+        repo.ui.write('hg update %s;\n' % prec)
+        repo.ui.write('hg revert --all --rev %s;\n' % latecomer)
+        repo.ui.write('hg commit --msg "latecomer update to %s"')
+        return 0
+    wlock = repo.wlock()
+    try:
+        newid = tmpctx = None
+        tmpctx = latecomer
+        lock = repo.lock()
+        try:
+            bmupdate = _bookmarksupdater(repo, latecomer.node())
+            # Basic check for common parent. Far too complicated and fragile
+            tr = repo.transaction('latecomer-stabilize')
+            try:
+                if not list(repo.set('parents(%d) and parents(%d)', latecomer, prec)):
+                    # Need to rebase the changeset at the right place
+                    repo.ui.status(_('rebasing to destination parent: %s\n') % prec.p1())
+                    try:
+                        tmpid = relocate(repo, latecomer, prec.p1())
+                        if tmpid is not None:
+                            tmpctx = repo[tmpid]
+                            createmarkers(repo, [(latecomer, (tmpctx,))])
+                    except MergeFailure:
+                        repo.opener.write('graftstate', latecomer.hex() + '\n')
+                        repo.ui.write_err(_('evolution failed!\n'))
+                        repo.ui.write_err(_('fix conflict and run "hg evolve --continue"\n'))
+                        raise
+                # Create the new commit context
+                repo.ui.status(_('computing new diff\n'))
+                files = set()
+                copied = copies.pathcopies(prec, latecomer)
+                precmanifest = prec.manifest()
+                for key, val in latecomer.manifest().iteritems():
+                    if precmanifest.pop(key, None) != val:
+                        files.add(key)
+                files.update(precmanifest)  # add missing files
+                # commit it
+                if files: # something to commit!
+                    def filectxfn(repo, ctx, path):
+                        if path in latecomer:
+                            fctx = latecomer[path]
+                            flags = fctx.flags()
+                            mctx = context.memfilectx(fctx.path(), fctx.data(),
+                                                      islink='l' in flags,
+                                                      isexec='x' in flags,
+                                                      copied=copied.get(path))
+                            return mctx
+                        raise IOError()
+                    text = 'latecomer update to %s:\n\n' % prec
+                    text += latecomer.description()
+
+                    new = context.memctx(repo,
+                                         parents=[prec.node(), node.nullid],
+                                         text=text,
+                                         files=files,
+                                         filectxfn=filectxfn,
+                                         user=latecomer.user(),
+                                         date=latecomer.date(),
+                                         extra=latecomer.extra())
+
+                    newid = repo.commitctx(new)
+                if newid is None:
+                    createmarkers(repo, [(tmpctx, ())])
+                    newid = prec.node()
+                else:
+                    phases.retractboundary(repo, latecomer.phase(), [newid])
+                    createmarkers(repo, [(tmpctx, (repo[newid],))],
+                                           flag=latediff)
+                bmupdate(newid)
+                tr.close()
+                repo.ui.status(_('commited as %s\n') % node.short(newid))
+            finally:
+                tr.release()
+        finally:
+            lock.release()
+        # reroute the working copy parent to the new changeset
+        repo.dirstate.setparents(newid, node.nullid)
+    finally:
+        wlock.release()
+
+def _solveconflicting(ui, repo, conflicting, dryrun=False):
+    base, others = conflictingdata(conflicting)
+    if len(others) > 1:
+        raise util.Abort("We do not handle split yet")
+    other = others[0]
+    if conflicting.phase() <= phases.public:
+        raise util.Abort("We can't resolve this conflict from the public side")
+    if len(other.parents()) > 1:
+        raise util.Abort("conflicting changeset can't be a merge (yet)")
+    if other.p1() not in conflicting.parents():
+        raise util.Abort("parent are not common (not handled yet)")
+
+    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
+    ui.status(_('merge:'))
+    if not ui.quiet:
+        displayer.show(conflicting)
+    ui.status(_('with: '))
+    if not ui.quiet:
+        displayer.show(other)
+    ui.status(_('base: '))
+    if not ui.quiet:
+        displayer.show(base)
+    if dryrun:
+        ui.write('hg update -c %s &&\n' % conflicting)
+        ui.write('hg merge %s && \n' % other)
+        ui.write('hg commit -m "auto merge resolving conflict between %s and %s"&&\n'
+                  % (conflicting, other))
+        ui.write('hg up -C %s &&\n' % base)
+        ui.write('hg revert --all --rev tip &&\n')
+        ui.write('hg commit -m "`hg log -r %s --template={desc}`";\n' % conflicting)
+        return
+    #oldphase = max(conflicting.phase(), other.phase())
+    wlock = repo.wlock()
+    try:
+        lock = repo.lock()
+        try:
+            if conflicting not in repo[None].parents():
+                repo.ui.status(_('updating to "local" conflict\n'))
+                hg.update(repo, conflicting.rev())
+            repo.ui.note(_('merging conflicting changeset\n'))
+            stats = merge.update(repo,
+                                 other.node(),
+                                 branchmerge=True,
+                                 force=False,
+                                 partial=None,
+                                 ancestor=base.node(),
+                                 mergeancestor=True)
+            hg._showstats(repo, stats)
+            if stats[3]:
+                repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
+                                 "or 'hg update -C .' to abandon\n"))
+            #repo.dirstate.write()
+            if stats[3] > 0:
+                raise util.Abort('GASP! Merge Conflict! You are on you own chap!',
+                                 hint='/!\\ hg evolve --continue will NOT work /!\\')
+            tr = repo.transaction('stabilize-conflicting')
+            try:
+                repo.dirstate.setparents(conflicting.node(), node.nullid)
+                oldlen = len(repo)
+                amend(ui, repo)
+                if oldlen == len(repo):
+                    new = conflicting
+                    # no changes
+                else:
+                    new = repo['.']
+                createmarkers(repo, [(other, (new,))])
+                phases.retractboundary(repo, other.phase(), [new.node()])
+                tr.close()
+            finally:
+                tr.release()
+        finally:
+            lock.release()
+    finally:
+        wlock.release()
+
+
+def conflictingdata(ctx):
+    """return base, other part of a conflict
+
+    This only return the first one.
+
+    XXX this woobly function won't survive XXX
+    """
+    for base in ctx._repo.set('reverse(precursors(%d))', ctx):
+        newer = newerversion(ctx._repo, base.node())
+        # drop filter and solution including the original ctx
+        newer = [n for n in newer if n and ctx.node() not in n]
+        if newer:
+            return base, tuple(ctx._repo[o] for o in newer[0])
+    raise KeyError('Base seem unknown. This case is not handled yet.')
+
+
+
 shorttemplate = '[{rev}] {desc|firstline}\n'
 
 @command('^gdown',
@@ -353,25 +1683,29 @@
     """
     wlock = repo.wlock()
     try:
-        new = set(noderange(repo, opts['new']))
-        targetnodes = set(noderange(repo, revs))
-        if not new:
-            new = [node.nullid]
-        for n in targetnodes:
-            if not repo[n].mutable():
-                ui.warn(_("cannot kill immutable changeset %s\n") % repo[n])
+        lock = repo.lock()
+        try:
+            new = set(noderange(repo, opts['new']))
+            targetnodes = set(noderange(repo, revs))
+            if new:
+                sucs = tuple(repo[n] for n in new)
             else:
-                for ne in new:
-                    repo.addobsolete(ne, n)
-        # 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)
+                sucs = ()
+            markers = []
+            for n in targetnodes:
+                markers.append((repo[n], sucs))
+            createmarkers(repo, markers)
 
+            # 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()
 
@@ -465,7 +1799,7 @@
                     # the intermediate revision if any. No need to update
                     # phases or parents.
                     if tempid is not None:
-                        repo.addobsolete(node.nullid, tempid)
+                        createmarkers(repo, [(repo[tempid], ())])
                     # XXX: need another message in collapse case.
                     tr.close()
                     raise error.Abort(_('no updates found'))
@@ -601,7 +1935,7 @@
             if newid is None:
                 raise util.Abort(_('nothing to uncommit'))
             # Move local changes on filtered changeset
-            repo.addobsolete(newid, old.node())
+            createmarkers(repo, [(old, (repo[newid],))])
             phases.retractboundary(repo, oldphase, [newid])
             repo.dirstate.setparents(newid, node.nullid)
             _uncommitdirstate(repo, old, match)
@@ -614,8 +1948,12 @@
     finally:
         lock.release()
 
+@eh.wrapcommand('commit')
 def commitwrapper(orig, ui, repo, *arg, **kwargs):
-    lock = repo.lock()
+    if kwargs.get('amend', False):
+        lock = None
+    else:
+        lock = repo.lock()
     try:
         obsoleted = kwargs.get('obsolete', [])
         if obsoleted:
@@ -624,19 +1962,122 @@
         if not result: # commit successed
             new = repo['-1']
             oldbookmarks = []
+            markers = []
             for old in obsoleted:
                 oldbookmarks.extend(repo.nodebookmarks(old.node()))
-                repo.addobsolete(new.node(), old.node())
+                markers.append((old, (new,)))
+            if markers:
+                createmarkers(repo, markers)
             for book in oldbookmarks:
                 repo._bookmarks[book] = new.node()
             if oldbookmarks:
                 bookmarks.write(repo)
         return result
     finally:
-        lock.release()
+        if lock is not None:
+            lock.release()
+
+@command('^touch',
+    [('r', 'rev', [], 'revision to update'),],
+    # allow to choose the seed ?
+    _('[-r] revs'))
+def touch(ui, repo, *revs, **opts):
+    """Create successors with exact same property but hash
+    
+    This is used to "resurect" changeset"""
+    revs = list(revs)
+    revs.extend(opts['rev'])
+    if not revs:
+        revs = ['.']
+    revs = scmutil.revrange(repo, revs)
+    if not revs:
+        ui.write_err('no revision to touch\n')
+        return 1
+    if repo.revs('public() and %ld', revs):
+        raise util.Abort("can't touch public revision")
+    wlock = repo.wlock()
+    try:
+        lock = repo.lock()
+        try:
+            tr = repo.transaction('touch')
+            try:
+                for r in revs:
+                    ctx = repo[r]
+                    extra = ctx.extra().copy()
+                    extra['__touch-noise__'] = random.randint(0, 0xffffffff)
+                    new, _ = rewrite(repo, ctx, [], ctx,
+                                     [ctx.p1().node(), ctx.p2().node()],
+                                     commitopts={'extra': extra})
+                    createmarkers(repo, [(ctx, (repo[new],))])
+                    phases.retractboundary(repo, ctx.phase(), [new])
+                    if ctx in repo[None].parents():
+                        repo.dirstate.setparents(new, node.nullid)
+                tr.close()
+            finally:
+                tr.release()
+        finally:
+            lock.release()
+    finally:
+        wlock.release()
 
+@command('^fold',
+    [('r', 'rev', [], 'revision to fold'),],
+    # allow to choose the seed ?
+    _('[-r] revs'))
+def fold(ui, repo, *revs, **opts):
+    """Fold multiple revision into a single one"""
+    revs = list(revs)
+    revs.extend(opts['rev'])
+    if not revs:
+        revs = ['.']
+    revs = scmutil.revrange(repo, revs)
+    if not revs:
+        ui.write_err('no revision to fold\n')
+        return 1
+    roots = repo.revs('roots(%ld)', revs)
+    if len(roots) > 1:
+        raise util.Abort("set have multiple roots")
+    root = repo[roots[0]]
+    if root.phase() <= phases.public:
+        raise util.Abort("can't touch public revision")
+    heads = repo.revs('heads(%ld)', revs)
+    if len(heads) > 1:
+        raise util.Abort("set have multiple heads")
+    head = repo[heads[0]]
+    wlock = repo.wlock()
+    try:
+        lock = repo.lock()
+        try:
+            tr = repo.transaction('touch')
+            try:
+                allctx = [repo[r] for r in revs]
+                targetphase = max(c.phase() for c in allctx)
+                msg = '\n\n***\n\n'.join(c.description() for c in allctx)
+                newid, _ = rewrite(repo, root, allctx, head,
+                                 [root.p1().node(), root.p2().node()],
+                                 commitopts={'message': msg})
+                phases.retractboundary(repo, targetphase, [newid])
+                createmarkers(repo, [(ctx, (repo[newid],))
+                                               for ctx in allctx])
+                tr.close()
+            finally:
+                tr.release()
+        finally:
+            lock.release()
+        ui.status('%i changesets folded\n' % len(revs))
+        if repo.revs('. and %ld', revs):
+            repo.dirstate.setparents(newid, node.nullid)
+    finally:
+        wlock.release()
+
+
+@eh.wrapcommand('graft')
 def graftwrapper(orig, ui, repo, *revs, **kwargs):
+    kwargs = dict(kwargs)
+    revs = list(revs) + kwargs.get('rev', [])
+    kwargs['rev'] = []
     obsoleted = kwargs.setdefault('obsolete', [])
+
     lock = repo.lock()
     try:
         if kwargs.get('old_obsolete'):
@@ -655,25 +2096,21 @@
     finally:
         lock.release()
 
-def extsetup(ui):
-    try:
-        obsolete = extensions.find('obsolete')
-    except KeyError:
-        raise error.Abort(_('evolution extension requires obsolete extension.'))
+@eh.extsetup
+def oldevolveextsetup(ui):
     try:
         rebase = extensions.find('rebase')
     except KeyError:
-        rebase = None
         raise error.Abort(_('evolution extension requires rebase extension.'))
 
     for cmd in ['amend', 'kill', 'uncommit']:
         entry = extensions.wrapcommand(cmdtable, cmd,
-                                       obsolete.warnobserrors)
+                                       warnobserrors)
 
-    entry = extensions.wrapcommand(commands.table, 'commit', commitwrapper)
+    entry = cmdutil.findcmd('commit', commands.table)[1]
     entry[1].append(('o', 'obsolete', [],
                      _("make commit obsolete this revision")))
-    entry = extensions.wrapcommand(commands.table, 'graft', graftwrapper)
+    entry = cmdutil.findcmd('graft', commands.table)[1]
     entry[1].append(('o', 'obsolete', [],
                      _("make graft obsoletes this revision")))
     entry[1].append(('O', 'old-obsolete', False,
--- a/hgext/obsolete.py	Tue Aug 21 12:43:21 2012 +0200
+++ b/hgext/obsolete.py	Fri Aug 24 11:53:55 2012 +0200
@@ -5,777 +5,60 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-"""Introduce the Obsolete concept to mercurial
-
-General concept
-===============
-
-This extension introduces the *obsolete* concept. It adds a new *obsolete*
-relation between two changesets. A relation ``<changeset B> obsolete <changeset
-A>`` is set to denote that ``<changeset B>`` is new version of ``<changeset
-A>``.
-
-The *obsolete* relation act as a **perpendicular history** to the standard
-changeset history. Standard changeset history versions files. The *obsolete*
-relation versions changesets.
-
-:obsolete:     a changeset that has been replaced by another one.
-:unstable:     a changeset that is not obsolete but has an obsolete ancestor.
-:suspended:    an obsolete changeset with unstable descendant.
-:extinct:      an obsolete changeset without unstable descendant.
-               (subject to garbage collection)
-
-Another name for unstable could be out of sync.
-
-
-Usage and Feature
-=================
-
-Display and Exchange
---------------------
-
-obsolete changesets are hidden. (except if they have non obsolete changeset)
+"""Deprecated extension that formely introduces "Changeset Obsolescence".
 
-obsolete changesets are not exchanged. This will probably change later but it
-was the simpler solution for now.
-
-New commands
-------------
-
-Note that rebased changesets are now marked obsolete instead of being stripped.
-
-Context object
---------------
-
-Context gains a ``obsolete`` method that will return True if a changeset is
-obsolete False otherwise.
-
-revset
-------
+This concept is now partially in Mercurial core (starting with mercurial 2.3). The remaining logic have been grouped with the evolve extension.
 
-Add an ``obsolete()`` entry.
-
-repo extension
---------------
-
-To Do
-~~~~~
-
-- refuse to obsolete published changesets
-
-- handle split
-
-- handle conflict
-
-- handle unstable // out of sync
-
+Some code cemains in this extensions to detect and convert prehistoric format of obsolete marker than early user may have create. Keep it enabled if you were such user.
 """
 
-import os
-try:
-    from cStringIO import StringIO
-except ImportError:
-    from StringIO import StringIO
-
-from mercurial.i18n import _
-
-import base64
-import json
-
-import struct
-from mercurial import util, base85
-
-_pack = struct.pack
-_unpack = struct.unpack
-
 from mercurial import util
-from mercurial import context
-from mercurial import revset
-from mercurial import scmutil
-from mercurial import extensions
-from mercurial import pushkey
-from mercurial import discovery
-from mercurial import error
-from mercurial import commands
-from mercurial import changelog
-from mercurial import phases
-from mercurial.node import hex, bin, short, nullid
-from mercurial.lock import release
-from mercurial import localrepo
-from mercurial import cmdutil
-from mercurial import templatekw
 
 try:
-    from mercurial.localrepo import storecache
-    storecache('babar') # to trigger import
-except (TypeError, ImportError):
-    def storecache(*args):
-        return scmutil.filecache(*args, instore=True)
-
-
-### Patch changectx
-#############################
-
-def obsolete(ctx):
-    """is the changeset obsolete by other"""
-    if ctx.node()is None:
-        return False
-    return bool(ctx._repo.obsoletedby(ctx.node())) and ctx.phase()
-
-context.changectx.obsolete = obsolete
-
-def unstable(ctx):
-    """is the changeset unstable (have obsolete ancestor)"""
-    if ctx.node() is None:
-        return False
-    return ctx.rev() in ctx._repo._unstableset
-
-context.changectx.unstable = unstable
-
-def extinct(ctx):
-    """is the changeset extinct by other"""
-    if ctx.node() is None:
-        return False
-    return ctx.rev() in ctx._repo._extinctset
-
-context.changectx.extinct = extinct
-
-def latecomer(ctx):
-    """is the changeset latecomer (Try to succeed to public change)"""
-    if ctx.node() is None:
-        return False
-    return ctx.rev() in ctx._repo._latecomerset
-
-context.changectx.latecomer = latecomer
-
-def conflicting(ctx):
-    """is the changeset conflicting (Try to succeed to public change)"""
-    if ctx.node() is None:
-        return False
-    return ctx.rev() in ctx._repo._conflictingset
-
-context.changectx.conflicting = conflicting
-
-
-### revset
-#############################
-
-def revsethidden(repo, subset, x):
-    """hidden changesets"""
-    args = revset.getargs(x, 0, 0, 'hidden takes no argument')
-    return [r for r in subset if r in repo.changelog.hiddenrevs]
-
-def revsetobsolete(repo, subset, x):
-    """obsolete changesets"""
-    args = revset.getargs(x, 0, 0, 'obsolete takes no argument')
-    return [r for r in subset if r in repo._obsoleteset and repo._phasecache.phase(repo, r) > 0]
-
-# XXX Backward compatibility, to be removed once stabilized
-if '_phasecache' not in vars(localrepo.localrepository): # new api
-    def revsetobsolete(repo, subset, x):
-        """obsolete changesets"""
-        args = revset.getargs(x, 0, 0, 'obsolete takes no argument')
-        return [r for r in subset if r in repo._obsoleteset and repo._phaserev[r] > 0]
-
-def revsetunstable(repo, subset, x):
-    """non obsolete changesets descendant of obsolete one"""
-    args = revset.getargs(x, 0, 0, 'unstable takes no arguments')
-    return [r for r in subset if r in repo._unstableset]
-
-def revsetsuspended(repo, subset, x):
-    """obsolete changesets with non obsolete descendants"""
-    args = revset.getargs(x, 0, 0, 'suspended takes no arguments')
-    return [r for r in subset if r in repo._suspendedset]
-
-def revsetextinct(repo, subset, x):
-    """obsolete changesets without obsolete descendants"""
-    args = revset.getargs(x, 0, 0, 'extinct takes no arguments')
-    return [r for r in subset if r in repo._extinctset]
-
-def revsetlatecomer(repo, subset, x):
-    """latecomer, Try to succeed to public change"""
-    args = revset.getargs(x, 0, 0, 'latecomer takes no arguments')
-    return [r for r in subset if r in repo._latecomerset]
-
-def revsetconflicting(repo, subset, x):
-    """conflicting, Try to succeed to public change"""
-    args = revset.getargs(x, 0, 0, 'conflicting takes no arguments')
-    return [r for r in subset if r in repo._conflictingset]
+    from mercurial import obsolete
+    if not obsolete._enabled:
+        obsolete._enabled = True
+except ImportError:
+    raise util.Abort('Obsolete extension requires Mercurial 2.3 (or later)')
 
-def _precursors(repo, s):
-    """Precursor of a changeset"""
-    cs = set()
-    nm = repo.changelog.nodemap
-    markerbysubj = repo.obsstore.successors
-    for r in s:
-        for p in markerbysubj.get(repo[r].node(), ()):
-            pr = nm.get(p[0])
-            if pr is not None:
-                cs.add(pr)
-    return cs
-
-def revsetprecursors(repo, subset, x):
-    """precursors of a subset"""
-    s = revset.getset(repo, range(len(repo)), x)
-    cs = _precursors(repo, s)
-    return [r for r in subset if r in cs]
-
-def _allprecursors(repo, s):  # XXX we need a better naming
-    """transitive precursors of a subset"""
-    toproceed = [repo[r].node() for r in s]
-    seen = set()
-    allsubjects = repo.obsstore.successors
-    while toproceed:
-        nc = toproceed.pop()
-        for mark in allsubjects.get(nc, ()):
-            np = mark[0]
-            if np not in seen:
-                seen.add(np)
-                toproceed.append(np)
-    nm = repo.changelog.nodemap
-    cs = set()
-    for p in seen:
-        pr = nm.get(p)
-        if pr is not None:
-            cs.add(pr)
-    return cs
-
-def revsetallprecursors(repo, subset, x):
-    """obsolete parents"""
-    s = revset.getset(repo, range(len(repo)), x)
-    cs = _allprecursors(repo, s)
-    return [r for r in subset if r in cs]
+import sys
+import json
 
-def _successors(repo, s):
-    """Successors of a changeset"""
-    cs = set()
-    nm = repo.changelog.nodemap
-    markerbyobj = repo.obsstore.precursors
-    for r in s:
-        for p in markerbyobj.get(repo[r].node(), ()):
-            for sub in p[1]:
-                sr = nm.get(sub)
-                if sr is not None:
-                    cs.add(sr)
-    return cs
-
-def revsetsuccessors(repo, subset, x):
-    """successors of a subset"""
-    s = revset.getset(repo, range(len(repo)), x)
-    cs = _successors(repo, s)
-    return [r for r in subset if r in cs]
-
-def _allsuccessors(repo, s):  # XXX we need a better naming
-    """transitive successors of a subset"""
-    toproceed = [repo[r].node() for r in s]
-    seen = set()
-    allobjects = repo.obsstore.precursors
-    while toproceed:
-        nc = toproceed.pop()
-        for mark in allobjects.get(nc, ()):
-            for sub in mark[1]:
-                if sub == nullid:
-                    continue # should not be here!
-                if sub not in seen:
-                    seen.add(sub)
-                    toproceed.append(sub)
-    nm = repo.changelog.nodemap
-    cs = set()
-    for s in seen:
-        sr = nm.get(s)
-        if sr is not None:
-            cs.add(sr)
-    return cs
-
-def revsetallsuccessors(repo, subset, x):
-    """obsolete parents"""
-    s = revset.getset(repo, range(len(repo)), x)
-    cs = _allsuccessors(repo, s)
-    return [r for r in subset if r in cs]
-
-
-### template keywords
-#####################
-
-def obsoletekw(repo, ctx, templ, **args):
-    """:obsolete: String. The obsolescence level of the node, could be
-    ``stable``, ``unstable``, ``suspended`` or ``extinct``.
-    """
-    rev = ctx.rev()
-    if rev in repo._extinctset:
-        return 'extinct'
-    if rev in repo._suspendedset:
-        return 'suspended'
-    if rev in repo._unstableset:
-        return 'unstable'
-    return 'stable'
-
-### Other Extension compat
-############################
+from mercurial import cmdutil
+from mercurial import error
+from mercurial.node import bin, nullid
 
 
-def buildstate(orig, repo, dest, rebaseset, *ags, **kws):
-    """wrapper for rebase 's buildstate that exclude obsolete changeset"""
-    rebaseset = repo.revs('%ld - extinct()', rebaseset)
-    return orig(repo, dest, rebaseset, *ags, **kws)
-
-def defineparents(orig, repo, rev, target, state, *args, **kwargs):
-    rebasestate = getattr(repo, '_rebasestate', None)
-    if rebasestate is not None:
-        repo._rebasestate = dict(state)
-        repo._rebasetarget = target
-    return orig(repo, rev, target, state, *args, **kwargs)
-
-def concludenode(orig, repo, rev, p1, *args, **kwargs):
-    """wrapper for rebase 's concludenode that set obsolete relation"""
-    newrev = orig(repo, rev, p1, *args, **kwargs)
-    rebasestate = getattr(repo, '_rebasestate', None)
-    if rebasestate is not None:
-        if newrev is not None:
-            nrev = repo[newrev].rev()
-        else:
-            nrev = p1
-        repo._rebasestate[rev] = nrev
-    return newrev
-
-def cmdrebase(orig, ui, repo, *args, **kwargs):
-
-    reallykeep = kwargs.get('keep', False)
-    kwargs = dict(kwargs)
-    kwargs['keep'] = True
+#####################################################################
+### Older format management                                       ###
+#####################################################################
 
-    # We want to mark rebased revision as obsolete and set their
-    # replacements if any. Doing it in concludenode() prevents
-    # aborting the rebase, and is not called with all relevant
-    # revisions in --collapse case. Instead, we try to track the
-    # rebase state structure by sampling/updating it in
-    # defineparents() and concludenode(). The obsolete markers are
-    # added from this state after a successful call.
-    repo._rebasestate = {}
-    repo._rebasetarget = None
-    try:
-        res = orig(ui, repo, *args, **kwargs)
-        if not reallykeep:
-            # Filter nullmerge or unrebased entries
-            repo._rebasestate = dict(p for p in repo._rebasestate.iteritems()
-                                     if p[1] >= 0)
-            if not res and not kwargs.get('abort') and repo._rebasestate:
-                # Rebased revisions are assumed to be descendants of
-                # targetrev. If a source revision is mapped to targetrev
-                # or to another rebased revision, it must have been
-                # removed.
-                targetrev = repo[repo._rebasetarget].rev()
-                newrevs = set([targetrev])
-                replacements = {}
-                for rev, newrev in sorted(repo._rebasestate.items()):
-                    oldnode = repo[rev].node()
-                    if newrev not in newrevs:
-                        newnode = repo[newrev].node()
-                        newrevs.add(newrev)
-                    else:
-                        newnode = nullid
-                    replacements[oldnode] = newnode
-
-                if kwargs.get('collapse'):
-                    newnodes = set(n for n in replacements.values() if n != nullid)
-                    if newnodes:
-                        # Collapsing into more than one revision?
-                        assert len(newnodes) == 1, newnodes
-                        newnode = newnodes.pop()
-                    else:
-                        newnode = nullid
-                    repo.addcollapsedobsolete(replacements, newnode)
-                else:
-                    for oldnode, newnode in replacements.iteritems():
-                        repo.addobsolete(newnode, oldnode)
-        return res
-    finally:
-        delattr(repo, '_rebasestate')
-        delattr(repo, '_rebasetarget')
+# Code related to detection and management of older legacy format never
+# handled by core
 
 
-def extsetup(ui):
-
-    revset.symbols["hidden"] = revsethidden
-    revset.symbols["obsolete"] = revsetobsolete
-    revset.symbols["unstable"] = revsetunstable
-    revset.symbols["suspended"] = revsetsuspended
-    revset.symbols["extinct"] = revsetextinct
-    revset.symbols["latecomer"] = revsetlatecomer
-    revset.symbols["conflicting"] = revsetconflicting
-    revset.symbols["obsparents"] = revsetprecursors  # DEPR
-    revset.symbols["precursors"] = revsetprecursors
-    revset.symbols["obsancestors"] = revsetallprecursors  # DEPR
-    revset.symbols["allprecursors"] = revsetallprecursors  # bad name
-    revset.symbols["successors"] = revsetsuccessors
-    revset.symbols["allsuccessors"] = revsetallsuccessors  # bad name
-
-    templatekw.keywords['obsolete'] = obsoletekw
-
-    # warning about more obsolete
-    for cmd in ['commit', 'push', 'pull', 'graft', 'phase', 'unbundle']:
-        entry = extensions.wrapcommand(commands.table, cmd, warnobserrors)
-    try:
-        rebase = extensions.find('rebase')
-        if rebase:
-            entry = extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors)
-            extensions.wrapfunction(rebase, 'buildstate', buildstate)
-            extensions.wrapfunction(rebase, 'defineparents', defineparents)
-            extensions.wrapfunction(rebase, 'concludenode', concludenode)
-            extensions.wrapcommand(rebase.cmdtable, "rebase", cmdrebase)
-    except KeyError:
-        pass  # rebase not found
-
-# Pushkey mechanism for mutable
-#########################################
-
-def listmarkers(repo):
-    """List markers over pushkey"""
-    if not repo.obsstore:
-        return {}
-    data = repo.obsstore._writemarkers()
-    encdata = base85.b85encode(data)
-    return {'dump0': encdata,
-            'dump': encdata} # legacy compat
-
-def pushmarker(repo, key, old, new):
-    """Push markers over pushkey"""
-    if not key.startswith('dump'):
-        repo.ui.warn(_('unknown key: %r') % key)
-        return 0
-    if old:
-        repo.ui.warn(_('unexpected old value') % key)
-        return 0
-    data = base85.b85decode(new)
-    lock = repo.lock()
-    try:
-        try:
-            repo.obsstore.mergemarkers(data)
-            return 1
-        except util.Abort:
-            return 0
-    finally:
-        lock.release()
-
-pushkey.register('obsolete', pushmarker, listmarkers)
-
-### Discovery wrapping
-#############################
-
-class blist(list, object):
-    """silly class to have non False but empty list"""
-
-    def __nonzero__(self):
-        return bool(len(self.orig))
-
-def wrapfindcommonoutgoing(orig, repo, *args, **kwargs):
-    """wrap mercurial.discovery.findcommonoutgoing to remove extinct changeset
-
-    Such excluded changeset are removed from excluded  and will *not* appear
-    are excluded secret changeset.
-    """
-    outgoing = orig(repo, *args, **kwargs)
-    orig = outgoing.excluded
-    outgoing.excluded = blist(n for n in orig if not repo[n].extinct())
-    # when no revision is specified (push everything) a shortcut is taken when
-    # nothign was exclude. taking this code path when extinct changeset have
-    # been excluded leads to repository corruption.
-    outgoing.excluded.orig = orig
-    return outgoing
-
-def wrapcheckheads(orig, repo, remote, outgoing, *args, **kwargs):
-    """wrap mercurial.discovery.checkheads
-
-    * prevent unstability to be pushed
-    * patch remote to ignore obsolete heads on remote
-    """
-    # do not push instability
-    for h in outgoing.missingheads:
-        # checking heads only is enought because any thing base on obsolete
-        # changeset is either obsolete or unstable.
-        ctx =  repo[h]
-        if ctx.unstable():
-            raise util.Abort(_("push includes an unstable changeset: %s!")
-                             % ctx)
-        if ctx.obsolete():
-            raise util.Abort(_("push includes an  obsolete changeset: %s!")
-                             % ctx)
-        if ctx.latecomer():
-            raise util.Abort(_("push includes an latecomer changeset: %s!")
-                             % ctx)
-        if ctx.conflicting():
-            raise util.Abort(_("push includes conflicting changeset: %s!")
-                             % ctx)
-    ### patch remote branch map
-    # do not read it this burn eyes
-    try:
-        if 'oldbranchmap' not in vars(remote):
-            remote.oldbranchmap = remote.branchmap
-            def branchmap():
-                newbm = {}
-                oldbm = None
-                if (util.safehasattr(phases, 'visiblebranchmap')
-                    and not util.safehasattr(remote, 'ignorevisiblebranchmap')
-                   ):
-                    remote.ignorevisiblebranchmap = False
-                    remote.branchmap = remote.oldbranchmap
-                    oldbm = phases.visiblebranchmap(remote)
-                    remote.branchmap = remote.newbranchmap
-                    remote.ignorevisiblebranchmap = True
-                if oldbm is None:
-                    oldbm = remote.oldbranchmap()
-                for branch, nodes in oldbm.iteritems():
-                    nodes = list(nodes)
-                    new = set()
-                    while nodes:
-                        n = nodes.pop()
-                        if n in repo.obsstore.precursors:
-                            markers = repo.obsstore.precursors[n]
-                            for mark in markers:
-                                for newernode in mark[1]:
-                                    if newernode is not None:
-                                        nodes.append(newernode)
-                        else:
-                            new.add(n)
-                    if new:
-                        newbm[branch] = list(new)
-                return newbm
-            remote.ignorevisiblebranchmap = True
-            remote.branchmap = branchmap
-            remote.newbranchmap = branchmap
-        return orig(repo, remote, outgoing, *args, **kwargs)
-    finally:
-        remote.__dict__.pop('branchmap', None) # restore class one
-        remote.__dict__.pop('oldbranchmap', None)
-        remote.__dict__.pop('newbranchmap', None)
-        remote.__dict__.pop('ignorevisiblebranchmap', None)
-
-# eye are still burning
-def wrapvisiblebranchmap(orig, repo):
-    ignore = getattr(repo, 'ignorevisiblebranchmap', None)
-    if ignore is None:
-        return orig(repo)
-    elif ignore:
-        return repo.branchmap()
-    else:
-        return None # break recursion
-
-def wrapclearcache(orig, repo, *args, **kwargs):
-    try:
-        return orig(repo, *args, **kwargs)
-    finally:
-        repo._clearobsoletecache()
-
-
-### New commands
-#############################
-
-cmdtable = {}
-command = cmdutil.command(cmdtable)
-
-@command('debugobsolete', [], _('SUBJECT OBJECT'))
-def cmddebugobsolete(ui, repo, subject, object):
-    """add an obsolete relation between two nodes
-
-    The subject is expected to be a newer version of the object.
+def reposetup(ui, repo):
+    """Detect that a repo still contains some old obsolete format
     """
-    lock = repo.lock()
-    try:
-        sub = repo[subject]
-        obj = repo[object]
-        repo.addobsolete(sub.node(), obj.node())
-    finally:
-        lock.release()
-    return 0
-
-@command('debugconvertobsolete', [], '')
-def cmddebugconvertobsolete(ui, repo):
-    """import markers from an .hg/obsolete-relations file"""
-    cnt = 0
-    err = 0
-    l = repo.lock()
-    some = False
-    try:
-        repo._importoldobsolete = True
-        store = repo.obsstore
-        ### very first format
-        try:
-            f = repo.opener('obsolete-relations')
-            try:
-                some = True
-                for line in f:
-                    subhex, objhex = line.split()
-                    suc = bin(subhex)
-                    prec = bin(objhex)
-                    sucs = (suc==nullid) and [] or [suc]
-                    meta = {
-                        'date':  '%i %i' % util.makedate(),
-                        'user': ui.username(),
-                        }
-                    try:
-                        store.create(prec, sucs, 0, meta)
-                        cnt += 1
-                    except ValueError:
-                        repo.ui.write_err("invalid old marker line: %s"
-                                          % (line))
-                        err += 1
-            finally:
-                f.close()
-            util.unlink(repo.join('obsolete-relations'))
-        except IOError:
-            pass
-        ### second (json) format
-        data = repo.sopener.tryread('obsoletemarkers')
+    if not repo.local():
+        return
+    for arg in sys.argv:
+        if 'debugc' in arg:
+            break
+    else:
+        data = repo.opener.tryread('obsolete-relations')
+        if not data:
+            data = repo.sopener.tryread('obsoletemarkers')
         if data:
-            some = True
-            for oldmark in json.loads(data):
-                del oldmark['id']  # dropped for now
-                del oldmark['reason']  # unused until then
-                oldobject = str(oldmark.pop('object'))
-                oldsubjects = [str(s) for s in oldmark.pop('subjects', [])]
-                LOOKUP_ERRORS = (error.RepoLookupError, error.LookupError)
-                if len(oldobject) != 40:
-                    try:
-                        oldobject = repo[oldobject].node()
-                    except LOOKUP_ERRORS:
-                        pass
-                if any(len(s) != 40 for s in oldsubjects):
-                    try:
-                        oldsubjects = [repo[s].node() for s in oldsubjects]
-                    except LOOKUP_ERRORS:
-                        pass
-
-                oldmark['date'] = '%i %i' % tuple(oldmark['date'])
-                meta = dict((k.encode('utf-8'), v.encode('utf-8'))
-                             for k, v in oldmark.iteritems())
-                try:
-                    succs = [bin(n) for n in oldsubjects]
-                    succs = [n for n in succs if n != nullid]
-                    store.create(bin(oldobject), succs,
-                                 0, meta)
-                    cnt += 1
-                except ValueError:
-                    repo.ui.write_err("invalid marker %s -> %s\n"
-                                 % (oldobject, oldsubjects))
-                    err += 1
-            util.unlink(repo.sjoin('obsoletemarkers'))
-    finally:
-        del repo._importoldobsolete
-        l.release()
-    if not some:
-            ui.warn('nothing to do\n')
-    ui.status('%i obsolete marker converted\n' % cnt)
-    if err:
-        ui.write_err('%i conversion failed. check you graph!\n' % err)
-
-@command('debugsuccessors', [], '')
-def cmddebugsuccessors(ui, repo):
-    """dump obsolete changesets and their successors
-
-    Each line matches an existing marker, the first identifier is the
-    obsolete changeset identifier, followed by it successors.
-    """
-    lock = repo.lock()
-    try:
-        allsuccessors = repo.obsstore.precursors
-        for old in sorted(allsuccessors):
-            successors = [sorted(m[1]) for m in allsuccessors[old]]
-            for i, group in enumerate(sorted(successors)):
-                ui.write('%s' % short(old))
-                for new in group:
-                    ui.write(' %s' % short(new))
-                ui.write('\n')
-    finally:
-        lock.release()
-
-### Altering existing command
-#############################
-
-def wrapmayobsoletewc(origfn, ui, repo, *args, **opts):
-    res = origfn(ui, repo, *args, **opts)
-    if repo['.'].obsolete():
-        ui.warn(_('Working directory parent is obsolete\n'))
-    return res
-
-def warnobserrors(orig, ui, repo, *args, **kwargs):
-    """display warning is the command resulted in more instable changeset"""
-    priorunstables = len(repo.revs('unstable()'))
-    priorlatecomers = len(repo.revs('latecomer()'))
-    priorconflictings = len(repo.revs('conflicting()'))
-    #print orig, priorunstables
-    #print len(repo.revs('secret() - obsolete()'))
-    try:
-        return orig(ui, repo, *args, **kwargs)
-    finally:
-        newunstables = len(repo.revs('unstable()')) - priorunstables
-        newlatecomers = len(repo.revs('latecomer()')) - priorlatecomers
-        newconflictings = len(repo.revs('conflicting()')) - priorconflictings
-        #print orig, newunstables
-        #print len(repo.revs('secret() - obsolete()'))
-        if newunstables > 0:
-            ui.warn(_('%i new unstables changesets\n') % newunstables)
-        if newlatecomers > 0:
-            ui.warn(_('%i new latecomers changesets\n') % newlatecomers)
-        if newconflictings > 0:
-            ui.warn(_('%i new conflictings changesets\n') % newconflictings)
-
-def noextinctsvisibleheads(orig, repo):
-    repo._turn_extinct_secret()
-    return orig(repo)
-
-def wrapcmdutilamend(orig, ui, repo, commitfunc, old, *args, **kwargs):
-    oldnode = old.node()
-    new = orig(ui, repo, commitfunc, old, *args, **kwargs)
-    if new != oldnode:
-        lock = repo.lock()
-        try:
-            meta = {
-                'subjects':  [new],
-                'object': oldnode,
-                'date':  util.makedate(),
-                'user': ui.username(),
-                'reason': 'commit --amend',
-                }
-            repo.obsstore.create(oldnode, [new], 0, meta)
-            repo._clearobsoletecache()
-            repo._turn_extinct_secret()
-        finally:
-            lock.release()
-    return new
-
-def uisetup(ui):
-    extensions.wrapcommand(commands.table, "update", wrapmayobsoletewc)
-    extensions.wrapcommand(commands.table, "pull", wrapmayobsoletewc)
-    if util.safehasattr(cmdutil, 'amend'):
-        extensions.wrapfunction(cmdutil, 'amend', wrapcmdutilamend)
-    extensions.wrapfunction(discovery, 'findcommonoutgoing', wrapfindcommonoutgoing)
-    extensions.wrapfunction(discovery, 'checkheads', wrapcheckheads)
-    extensions.wrapfunction(phases, 'visibleheads', noextinctsvisibleheads)
-    extensions.wrapfunction(phases, 'advanceboundary', wrapclearcache)
-    if util.safehasattr(phases, 'visiblebranchmap'):
-        extensions.wrapfunction(phases, 'visiblebranchmap', wrapvisiblebranchmap)
-
-### serialisation
-#############################
-
-def _obsserialise(obssubrels, flike):
-    """serialise an obsolete relation mapping in a plain text one
-
-    this is for subject -> [objects] mapping
-
-    format is::
-
-        <subject-full-hex> <object-full-hex>\n"""
-    for sub, objs in obssubrels.iteritems():
-        for obj in objs:
-            if sub is None:
-                sub = nullid
-            flike.write('%s %s\n' % (hex(sub), hex(obj)))
+            raise util.Abort('old format of obsolete marker detected!\n'
+                             'run `hg debugconvertobsolete` once.')
 
 def _obsdeserialise(flike):
     """read a file like object serialised with _obsserialise
 
-    this desierialize into a {subject -> objects} mapping"""
+    this desierialize into a {subject -> objects} mapping
+
+    this was the very first format ever."""
     rels = {}
     for line in flike:
         subhex, objhex = line.split()
@@ -785,527 +68,92 @@
         rels.setdefault( subnode, set()).add(bin(objhex))
     return rels
 
-### diagnostique tools
-#############################
-
-def unstables(repo):
-    """Return all unstable changeset"""
-    return scmutil.revrange(repo, ['obsolete():: and (not obsolete())'])
-
-def newerversion(repo, obs):
-    """Return the newer version of an obsolete changeset"""
-    toproceed = set([(obs,)])
-    # XXX known optimization available
-    newer = set()
-    objectrels = repo.obsstore.precursors
-    while toproceed:
-        current = toproceed.pop()
-        assert len(current) <= 1, 'splitting not handled yet. %r' % current
-        current = [n for n in current if n != nullid]
-        if current:
-            n, = current
-            if n in objectrels:
-                markers = objectrels[n]
-                for mark in markers:
-                    toproceed.add(tuple(mark[1]))
-            else:
-                newer.add(tuple(current))
-        else:
-            newer.add(())
-    return sorted(newer)
-
-### obsolete relation storage
-#############################
-def add2set(d, key, mark):
-    """add <mark> to a `set` in <d>[<key>]"""
-    d.setdefault(key, []).append(mark)
-
-def markerid(marker):
-    KEYS = ['subjects', "object", "date", "user", "reason"]
-    for key in KEYS:
-        assert key in marker
-    keys = sorted(marker.keys())
-    a = util.sha1()
-    for key in keys:
-        if key == 'subjects':
-            for sub in sorted(marker[key]):
-                a.update(sub)
-        elif key == 'id':
-            pass
-        else:
-            a.update(str(marker[key]))
-    a.update('\0')
-    return a.digest()
-
-# mercurial backport
-
-def encodemeta(meta):
-    """Return encoded metadata string to string mapping.
-
-    Assume no ':' in key and no '\0' in both key and value."""
-    for key, value in meta.iteritems():
-        if ':' in key or '\0' in key:
-            raise ValueError("':' and '\0' are forbidden in metadata key'")
-        if '\0' in value:
-            raise ValueError("':' are forbidden in metadata value'")
-    return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
-
-def decodemeta(data):
-    """Return string to string dictionary from encoded version."""
-    d = {}
-    for l in data.split('\0'):
-        if l:
-            key, value = l.split(':')
-            d[key] = value
-    return d
-
-# data used for parsing and writing
-_fmversion = 0
-_fmfixed   = '>BIB20s'
-_fmnode = '20s'
-_fmfsize = struct.calcsize(_fmfixed)
-_fnodesize = struct.calcsize(_fmnode)
-
-def _readmarkers(data):
-    """Read and enumerate markers from raw data"""
-    off = 0
-    diskversion = _unpack('>B', data[off:off + 1])[0]
-    off += 1
-    if diskversion != _fmversion:
-        raise util.Abort(_('parsing obsolete marker: unknown version %r')
-                         % diskversion)
-
-    # Loop on markers
-    l = len(data)
-    while off + _fmfsize <= l:
-        # read fixed part
-        cur = data[off:off + _fmfsize]
-        off += _fmfsize
-        nbsuc, mdsize, flags, pre = _unpack(_fmfixed, cur)
-        # read replacement
-        sucs = ()
-        if nbsuc:
-            s = (_fnodesize * nbsuc)
-            cur = data[off:off + s]
-            sucs = _unpack(_fmnode * nbsuc, cur)
-            off += s
-        # read metadata
-        # (metadata will be decoded on demand)
-        metadata = data[off:off + mdsize]
-        if len(metadata) != mdsize:
-            raise util.Abort(_('parsing obsolete marker: metadata is too '
-                               'short, %d bytes expected, got %d')
-                             % (len(metadata), mdsize))
-        off += mdsize
-        yield (pre, sucs, flags, metadata)
-
-class obsstore(object):
-    """Store obsolete markers
-
-    Markers can be accessed with two mappings:
-    - precursors: old -> set(new)
-    - successors: new -> set(old)
-    """
-
-    def __init__(self):
-        self._all = []
-        # new markers to serialize
-        self._new = []
-        self.precursors = {}
-        self.successors = {}
-
-    def __iter__(self):
-        return iter(self._all)
-
-    def __nonzero__(self):
-        return bool(self._all)
-
-    def create(self, prec, succs=(), flag=0, metadata=None):
-        """obsolete: add a new obsolete marker
-
-        * ensuring it is hashable
-        * check mandatory metadata
-        * encode metadata
-        """
-        if metadata is None:
-            metadata = {}
-        if len(prec) != 20:
-            raise ValueError(repr(prec))
-        for succ in succs:
-            if len(succ) != 20:
-                raise ValueError((succs))
-        marker = (str(prec), tuple(succs), int(flag), encodemeta(metadata))
-        self.add(marker)
-
-    def add(self, marker):
-        """Add a new marker to the store
-
-        This marker still needs to be written to disk"""
-        self._new.append(marker)
-        self._load(marker)
-
-    def loadmarkers(self, data):
-        """Load all markers in data, mark them as known."""
-        for marker in _readmarkers(data):
-            self._load(marker)
-
-    def mergemarkers(self, data):
-        other = set(_readmarkers(data))
-        local = set(self._all)
-        new = other - local
-        for marker in new:
-            self.add(marker)
-
-    def flushmarkers(self, stream):
-        """Write all markers to a stream
-
-        After this operation, "new" markers are considered "known"."""
-        self._writemarkers(stream)
-        self._new[:] = []
-
-    def _load(self, marker):
-        self._all.append(marker)
-        pre, sucs = marker[:2]
-        self.precursors.setdefault(pre, set()).add(marker)
-        for suc in sucs:
-            self.successors.setdefault(suc, set()).add(marker)
-
-    def _writemarkers(self, stream=None):
-        # Kept separate from flushmarkers(), it will be reused for
-        # markers exchange.
-        if stream is None:
-            final = []
-            w = final.append
-        else:
-            w = stream.write
-        w(_pack('>B', _fmversion))
-        for marker in self._all:
-            pre, sucs, flags, metadata = marker
-            nbsuc = len(sucs)
-            format = _fmfixed + (_fmnode * nbsuc)
-            data = [nbsuc, len(metadata), flags, pre]
-            data.extend(sucs)
-            w(_pack(format, *data))
-            w(metadata)
-        if stream is None:
-            return ''.join(final)
-
-
-### repo subclassing
-#############################
-
-def reposetup(ui, repo):
-    if not repo.local():
-        return
-
-    if not util.safehasattr(repo.opener, 'tryread'):
-        raise util.Abort('Obsolete extension requires Mercurial 2.2 (or later)')
-    opull = repo.pull
-    opush = repo.push
-    olock = repo.lock
-    o_rollback = repo._rollback
-    o_updatebranchcache = repo.updatebranchcache
-
-    # /!\ api change in  Hg 2.2 (97efd26eb9576f39590812ea9) /!\
-    if util.safehasattr(repo, '_journalfiles'): # Hg 2.2
-        o_journalfiles = repo._journalfiles
-    o_writejournal = repo._writejournal
-    o_hook = repo.hook
-
-
-    class obsoletingrepo(repo.__class__):
-
-        # workaround
-        def hook(self, name, throw=False, **args):
-            if 'pushkey' in name:
-                args.pop('new')
-                args.pop('old')
-            return o_hook(name, throw=False, **args)
-
-        ### Public method
-        def obsoletedby(self, node):
-            """return the set of node that make <node> obsolete (obj)"""
-            others = set()
-            for marker in self.obsstore.precursors.get(node, []):
-                others.update(marker[1])
-            return others
-
-        def obsolete(self, node):
-            """return the set of node that <node> make obsolete (sub)"""
-            return set(marker[0] for marker in self.obsstore.successors.get(node, []))
-
-        @storecache('obsstore')
-        def obsstore(self):
-            if not getattr(self, '_importoldobsolete', False):
-                data = repo.opener.tryread('obsolete-relations')
-                if not data:
-                    data = repo.sopener.tryread('obsoletemarkers')
-                if data:
-                    raise util.Abort('old format of obsolete marker detected!\n'
-                                     'run `hg debugconvertobsolete` once.')
-            store = obsstore()
-            data = self.sopener.tryread('obsstore')
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+@command('debugconvertobsolete', [], '')
+def cmddebugconvertobsolete(ui, repo):
+    """import markers from an .hg/obsolete-relations file"""
+    cnt = 0
+    err = 0
+    l = repo.lock()
+    some = False
+    try:
+        unlink = []
+        tr = repo.transaction('convert-obsolete')
+        try:
+            repo._importoldobsolete = True
+            store = repo.obsstore
+            ### very first format
+            try:
+                f = repo.opener('obsolete-relations')
+                try:
+                    some = True
+                    for line in f:
+                        subhex, objhex = line.split()
+                        suc = bin(subhex)
+                        prec = bin(objhex)
+                        sucs = (suc==nullid) and [] or [suc]
+                        meta = {
+                            'date':  '%i %i' % util.makedate(),
+                            'user': ui.username(),
+                            }
+                        try:
+                            store.create(tr, prec, sucs, 0, meta)
+                            cnt += 1
+                        except ValueError:
+                            repo.ui.write_err("invalid old marker line: %s"
+                                              % (line))
+                            err += 1
+                finally:
+                    f.close()
+                unlink.append(repo.join('obsolete-relations'))
+            except IOError:
+                pass
+            ### second (json) format
+            data = repo.sopener.tryread('obsoletemarkers')
             if data:
-                store.loadmarkers(data)
-            return store
-
-        @util.propertycache
-        def _obsoleteset(self):
-            """the set of obsolete revision"""
-            obs = set()
-            nm = self.changelog.nodemap
-            for obj in self.obsstore.precursors:
-                try: # /!\api change in Hg 2.2 (e8d37b78acfb22ae2c1fb126c2)/!\
-                    rev = nm.get(obj)
-                except TypeError:  #XXX to remove while breaking Hg 2.1 support
-                    rev = nm.get(obj, None)
-                if rev is not None:
-                    obs.add(rev)
-            return obs
-
-        @util.propertycache
-        def _unstableset(self):
-            """the set of non obsolete revision with obsolete parent"""
-            return set(self.revs('(obsolete()::) - obsolete()'))
-
-        @util.propertycache
-        def _suspendedset(self):
-            """the set of obsolete parent with non obsolete descendant"""
-            return set(self.revs('obsolete() and obsolete()::unstable()'))
-
-        @util.propertycache
-        def _extinctset(self):
-            """the set of obsolete parent without non obsolete descendant"""
-            return set(self.revs('obsolete() - obsolete()::unstable()'))
-
-        @util.propertycache
-        def _latecomerset(self):
-            """the set of rev trying to obsolete public revision"""
-            query = 'allsuccessors(public()) - obsolete() - public()'
-            return set(self.revs(query))
-
-        @util.propertycache
-        def _conflictingset(self):
-            """the set of rev trying to obsolete public revision"""
-            conflicting = set()
-            obsstore = self.obsstore
-            newermap = {}
-            for ctx in self.set('(not public()) - obsolete()'):
-                prec = obsstore.successors.get(ctx.node(), ())
-                toprocess = set(prec)
-                while toprocess:
-                    prec = toprocess.pop()[0]
-                    if prec not in newermap:
-                        newermap[prec] = newerversion(self, prec)
-                    newer = [n for n in newermap[prec] if n] # filter kill
-                    if len(newer) > 1:
-                        conflicting.add(ctx.rev())
-                        break
-                toprocess.update(obsstore.successors.get(prec, ()))
-            return conflicting
-
-        def _clearobsoletecache(self):
-            if '_obsoleteset' in vars(self):
-                del self._obsoleteset
-            self._clearunstablecache()
-
-        def updatebranchcache(self):
-            o_updatebranchcache()
-            self._clearunstablecache()
-
-        def _clearunstablecache(self):
-            if '_unstableset' in vars(self):
-                del self._unstableset
-            if '_suspendedset' in vars(self):
-                del self._suspendedset
-            if '_extinctset' in vars(self):
-                del self._extinctset
-            if '_latecomerset' in vars(self):
-                del self._latecomerset
-            if '_conflictingset' in vars(self):
-                del self._conflictingset
-
-        def addobsolete(self, sub, obj):
-            """Add a relation marking that node <sub> is a new version of <obj>"""
-            assert sub != obj
-            if not repo[obj].phase():
-                if sub is None:
-                    self.ui.warn(
-                        _("trying to kill immutable changeset %(obj)s\n")
-                        % {'obj': short(obj)})
-                if sub is not None:
-                    self.ui.warn(
-                        _("%(sub)s try to obsolete immutable changeset %(obj)s\n")
-                        % {'sub': short(sub), 'obj': short(obj)})
-            lock = self.lock()
-            try:
-                meta = {
-                    'date':  util.makedate(),
-                    'user': ui.username(),
-                    'reason': 'unknown',
-                    }
-                subs = (sub == nullid) and [] or [sub]
-                mid = self.obsstore.create(obj, subs, 0, meta)
-                self._clearobsoletecache()
-                self._turn_extinct_secret()
-                return mid
-            finally:
-                lock.release()
-
-        def addcollapsedobsolete(self, oldnodes, newnode):
-            """Mark oldnodes as collapsed into newnode."""
-            # Assume oldnodes are all descendants of a single rev
-            rootrevs = self.revs('roots(%ln)', oldnodes)
-            assert len(rootrevs) == 1, rootrevs
-            rootnode = self[rootrevs[0]].node()
-            for n in oldnodes:
-                self.addobsolete(newnode, n)
-
-        def _turn_extinct_secret(self):
-            """ensure all extinct changeset are secret"""
-            self._clearobsoletecache()
-            # this is mainly for safety purpose
-            # both pull and push
-            query = '(obsolete() - obsolete()::(unstable() - secret())) - secret()'
-            expobs = [c.node() for c in repo.set(query)]
-            phases.retractboundary(repo, 2, expobs)
-
-        ### Disk IO
+                some = True
+                for oldmark in json.loads(data):
+                    del oldmark['id']  # dropped for now
+                    del oldmark['reason']  # unused until then
+                    oldobject = str(oldmark.pop('object'))
+                    oldsubjects = [str(s) for s in oldmark.pop('subjects', [])]
+                    LOOKUP_ERRORS = (error.RepoLookupError, error.LookupError)
+                    if len(oldobject) != 40:
+                        try:
+                            oldobject = repo[oldobject].node()
+                        except LOOKUP_ERRORS:
+                            pass
+                    if any(len(s) != 40 for s in oldsubjects):
+                        try:
+                            oldsubjects = [repo[s].node() for s in oldsubjects]
+                        except LOOKUP_ERRORS:
+                            pass
 
-        def lock(self, *args, **kwargs):
-            l = olock(*args, **kwargs)
-            if not getattr(l.releasefn, 'obspatched', False):
-                oreleasefn = l.releasefn
-                def releasefn(*args, **kwargs):
-                    if 'obsstore' in vars(self) and self.obsstore._new:
-                        f = self.sopener('obsstore', 'wb', atomictemp=True)
-                        try:
-                            self.obsstore.flushmarkers(f)
-                            f.close()
-                        except: # re-raises
-                            f.discard()
-                            raise
-                    oreleasefn(*args, **kwargs)
-                releasefn.obspatched = True
-                l.releasefn = releasefn
-            return l
-
-
-        ### pull // push support
-
-        def pull(self, remote, *args, **kwargs):
-            """wrapper around push that push obsolete relation"""
-            l = repo.lock()
-            try:
-                result = opull(remote, *args, **kwargs)
-                remoteobs = remote.listkeys('obsolete')
-                if 'dump' in remoteobs:
-                    remoteobs['dump0'] = remoteobs.pop('dump')
-                if 'dump0' in remoteobs:
-                    for key, values in remoteobs.iteritems():
-                        if key.startswith('dump'):
-                            data = base85.b85decode(remoteobs['dump0'])
-                            self.obsstore.mergemarkers(data)
-                    self._clearobsoletecache()
-                    self._turn_extinct_secret()
-                    return result
-            finally:
-                l.release()
-
-        def push(self, remote, *args, **opts):
-            """wrapper around pull that pull obsolete relation"""
-            self._turn_extinct_secret()
-            try:
-                result = opush(remote, *args, **opts)
-            except util.Abort, ex:
-                hint = _("use 'hg stabilize' to get a stable history (or --force to proceed)")
-                if (len(ex.args) >= 1
-                    and ex.args[0].startswith('push includes ')
-                    and ex.hint is None):
-                    ex.hint = hint
-                raise
-            if 'obsolete' in remote.listkeys('namespaces') and self.obsstore:
-                data = self.obsstore._writemarkers()
-                r = remote.pushkey('obsolete', 'dump0', '',
-                                   base85.b85encode(data))
-                if not r:
-                    self.ui.warn(_('failed to push obsolete markers!\n'))
-            self._turn_extinct_secret()
-
-            return result
-
-
-        ### rollback support
-
-        # /!\ api change in  Hg 2.2 (97efd26eb9576f39590812ea9) /!\
-        if util.safehasattr(repo, '_journalfiles'): # Hg 2.2
-            def _journalfiles(self):
-                return o_journalfiles() + (self.sjoin('journal.obsstore'),) 
-
-            def _writejournal(self, desc):
-                """wrapped version of _writejournal that save obsolete data"""
-                o_writejournal(desc)
-                filename = 'obsstore'
-                filepath = self.sjoin(filename)
-                if os.path.exists(filepath):
-                    journalname = 'journal.' + filename
-                    journalpath = self.sjoin(journalname)
-                    util.copyfile(filepath, journalpath)
-
-        else: # XXX removing this bloc will break Hg 2.1 support
-            def _writejournal(self, desc):
-                """wrapped version of _writejournal that save obsolete data"""
-                entries = list(o_writejournal(desc))
-                filename = 'obsstore'
-                filepath = self.sjoin(filename)
-                if  os.path.exists(filepath):
-                    journalname = 'journal.' + filename
-                    journalpath = self.sjoin(journalname)
-                    util.copyfile(filepath, journalpath)
-                    entries.append(journalpath)
-                return tuple(entries)
-
-        def _rollback(self, dryrun, force):
-            """wrapped version of _rollback that restore obsolete data"""
-            ret = o_rollback(dryrun, force)
-            if not (ret or dryrun): #rollback did not failed
-                src = self.sjoin('undo.obsstore')
-                dst = self.sjoin('obsstore')
-                if os.path.exists(src):
-                    util.rename(src, dst)
-                elif os.path.exists(dst):
-                    # If no state was saved because the file did not existed before.
-                    os.unlink(dst)
-                # invalidate cache
-                self.__dict__.pop('obsstore', None)
-            return ret
-
-        @storecache('00changelog.i')
-        def changelog(self):
-            # << copy pasted from mercurial source
-            c = changelog.changelog(self.sopener)
-            if 'HG_PENDING' in os.environ:
-                p = os.environ['HG_PENDING']
-                if p.startswith(self.root):
-                    c.readpending('00changelog.i.a')
-            # >> end of the copy paste
-            old = c.__dict__.pop('hiddenrevs', ())
-            if old:
-                ui.warn("old wasn't empty ? %r" % old)
-            def _sethidden(c, value):
-                assert not value
-
-
-            class hchangelog(c.__class__):
-                @util.propertycache
-                def hiddenrevs(c):
-                    shown = ['not obsolete()', '.', 'bookmark()', 'tagged()',
-                             'public()']
-                    basicquery = 'obsolete() - (::(%s))' % (' or '.join(shown))
-                    # !!! self is repo not changelog
-                    result = set(scmutil.revrange(self, [basicquery]))
-                    return result
-            c.__class__ = hchangelog
-            return c
-
-    repo.__class__ = obsoletingrepo
+                    oldmark['date'] = '%i %i' % tuple(oldmark['date'])
+                    meta = dict((k.encode('utf-8'), v.encode('utf-8'))
+                                 for k, v in oldmark.iteritems())
+                    try:
+                        succs = [bin(n) for n in oldsubjects]
+                        succs = [n for n in succs if n != nullid]
+                        store.create(tr, bin(oldobject), succs,
+                                     0, meta)
+                        cnt += 1
+                    except ValueError:
+                        repo.ui.write_err("invalid marker %s -> %s\n"
+                                     % (oldobject, oldsubjects))
+                        err += 1
+                unlink.append(repo.sjoin('obsoletemarkers'))
+            tr.close()
+            for path in unlink:
+                util.unlink(path)
+        finally:
+            tr.release()
+    finally:
+        del repo._importoldobsolete
+        l.release()
+    if not some:
+            ui.warn('nothing to do\n')
+    ui.status('%i obsolete marker converted\n' % cnt)
+    if err:
+        ui.write_err('%i conversion failed. check you graph!\n' % err)
--- a/hgext/qsync.py	Tue Aug 21 12:43:21 2012 +0200
+++ b/hgext/qsync.py	Fri Aug 24 11:53:55 2012 +0200
@@ -83,8 +83,8 @@
                 review_list.append(patch_name)
             except IOError:
                 oldnode = oldfiles[patch_name]
-                obsolete = extensions.find('obsolete')
-                newnodes = obsolete.newerversion(repo, oldnode)
+                evolve = extensions.find('evolve')
+                newnodes = evolve.newerversion(repo, oldnode)
                 if newnodes:
                     newnodes = [n for n in newnodes if n and n[0] in repo] # remove killing
                 if not newnodes:
@@ -166,14 +166,14 @@
     currentdrafts = set(d[0] for d in newdata)
     usednew = set()
     usedold = set()
-    obsolete = extensions.find('obsolete')
+    evolve = extensions.find('evolve')
     for oldhex, oldname in olddata:
         if oldhex in usedold:
             continue # no duplicate
         usedold.add(oldhex)
         oldname = str(oldname)
         oldnode = bin(oldhex)
-        newnodes = obsolete.newerversion(repo, oldnode)
+        newnodes = evolve.newerversion(repo, oldnode)
         if newnodes:
             newnodes = [n for n in newnodes if n and n[0] in repo] # remove killing
             if len(newnodes) > 1:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup.py	Fri Aug 24 11:53:55 2012 +0200
@@ -0,0 +1,25 @@
+# Copied from histedit setup.py
+# Credit to Augie Fackler <durin42@gmail.com>
+
+from distutils.core import setup
+
+requires = []
+try:
+    import mercurial
+except ImportError:
+    requires.append('mercurial')
+
+setup(
+    name='hg-evolve',
+    version='0.1.0',
+    author='Pierre-Yves David',
+    maintainer='Pierre-Yves David',
+    maintainer_email='pierre-yves.david@logilab.fr',
+    url='https://bitbucket.org/marmoute/mutable-history',
+    description='Flexible evolution of Mercurial history.',
+    long_description=open('README').read(),
+    keywords='hg mercurial',
+    license='GPLv2+',
+    py_modules=['hgext.evolve'],
+    install_requires=requires,
+)
--- a/tests/test-amend.t	Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-amend.t	Fri Aug 24 11:53:55 2012 +0200
@@ -5,7 +5,6 @@
   > hgext.rebase=
   > hgext.graphlog=
   > EOF
-  $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
 
   $ glog() {
@@ -24,9 +23,9 @@
   marked working directory as branch foo
   (branches are permanent and global, did you want a bookmark?)
   $ hg amend
-  $ hg debugsuccessors
-  07f494440405 a34b93d251e4
-  bd19cbe78fbf a34b93d251e4
+  $ hg debugobsolete
+  bd19cbe78fbfbd87eb33420c63986fe5f3154f2c a34b93d251e49c93d5685ebacad785c73a7e8605 0 {'date': '* *', 'user': 'test'} (glob)
+  07f4944404050f47db2e5c5071e0e84e7a27bba9 a34b93d251e49c93d5685ebacad785c73a7e8605 0 {'date': '* *', 'user': 'test'} (glob)
   $ hg branch
   foo
   $ hg branches
@@ -58,13 +57,24 @@
   $ echo a >> a
   $ hg ci -m changea
   $ echo a > a
+  $ hg status
+  M a
+  $ hg pstatus
+  $ hg diff
+  diff -r 2f97fe38810f a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	* +0000 (glob)
+  @@ -1,2 +1,1 @@
+   a
+  -a
+  $ hg pdiff
   $ hg ci -m reseta
   $ hg amend --change 2
   abort: no updates found
   [255]
-  $ hg debugsuccessors
-  07f494440405 a34b93d251e4
-  bd19cbe78fbf a34b93d251e4
+  $ hg debugobsolete
+  bd19cbe78fbfbd87eb33420c63986fe5f3154f2c a34b93d251e49c93d5685ebacad785c73a7e8605 0 {'date': '* *', 'user': 'test'} (glob)
+  07f4944404050f47db2e5c5071e0e84e7a27bba9 a34b93d251e49c93d5685ebacad785c73a7e8605 0 {'date': '* *', 'user': 'test'} (glob)
   $ hg phase 2
   2: draft
   $ glog
@@ -88,12 +98,12 @@
   $ hg amend --change 2
   abort: no updates found
   [255]
-  $ hg debugsuccessors
-  07f494440405 a34b93d251e4
-  7384bbcba36f 000000000000
-  bd19cbe78fbf a34b93d251e4
+  $ 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(secret) amends a34b93d251e49c93d5685ebacad785c73a7e8605
+  @  6@foo(draft) amends a34b93d251e49c93d5685ebacad785c73a7e8605
   |
   o  5@default(draft) resetbranch
   |
--- a/tests/test-corrupt.t	Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-corrupt.t	Fri Aug 24 11:53:55 2012 +0200
@@ -16,7 +16,6 @@
   > hgext.rebase=
   > hgext.graphlog=
   > EOF
-  $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
   $ mkcommit() {
   >    echo "$1" >> "$1"
--- a/tests/test-evolve.t	Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-evolve.t	Fri Aug 24 11:53:55 2012 +0200
@@ -15,7 +15,6 @@
   > hgext.rebase=
   > hgext.graphlog=
   > EOF
-  $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
   $ mkcommit() {
   >    echo "$1" > "$1"
@@ -59,7 +58,8 @@
   $ hg log -r 1 --template '{rev} {phase} {obsolete}\n'
   1 public stable
   $ hg kill 1
-  cannot kill immutable changeset 7c3bad9141dc
+  abort: Cannot obsolete immutable changeset: 7c3bad9141dc
+  [255]
   $ hg log -r 1 --template '{rev} {phase} {obsolete}\n'
   1 public stable
 
@@ -245,7 +245,7 @@
   [255]
 
   $ hg amend --note 'french looks better'
-  1 new unstables changesets
+  1 new unstable changesets
   $ hg log
   6	feature-A: a nifty feature - test
   4	feature-B: another feature - test
@@ -255,24 +255,24 @@
   $ glog --hidden
   o  6:23409eba69a0@default(draft) a nifty feature
   |
-  | o  5:e416e48b2742@default(secret) french looks better
+  | x  5:e416e48b2742@default(draft) french looks better
   | |
   | | o  4:f8111a076f09@default(draft) another feature
   | |/
-  | | o  3:524e478d4811@default(secret) fix spelling of Zwei
+  | | x  3:524e478d4811@default(draft) fix spelling of Zwei
   | | |
-  | | o  2:7b36850622b2@default(secret) another feature
+  | | x  2:7b36850622b2@default(draft) another feature
   | |/
-  | o  1:568a468b60fc@default(draft) a nifty feature
+  | x  1:568a468b60fc@default(draft) a nifty feature
   |/
   @  0:e55e0562ee93@default(public) base
   
-  $ hg debugsuccessors
-  524e478d4811 f8111a076f09
-  568a468b60fc 23409eba69a0
-  7b36850622b2 f8111a076f09
-  e416e48b2742 23409eba69a0
-  $ hg stabilize
+  $ hg debugobsolete
+  524e478d4811d405c8771e4c441de4483bdf8b33 f8111a076f0975cbecb336e2bd3411be22b673fb 0 {'date': '* *', 'user': 'test'} (glob)
+  7b36850622b2fd159fa30a4fb2a1edd2043b4a14 f8111a076f0975cbecb336e2bd3411be22b673fb 0 {'date': '* *', 'user': 'test'} (glob)
+  e416e48b27428695d00c2a2cc4a0b9619482e63f 23409eba69a0986e90cd42252852c1e6da97af5b 0 {'date': '* *', 'user': 'test'} (glob)
+  568a468b60fc99a42d5d4ddbe181caff1eef308d 23409eba69a0986e90cd42252852c1e6da97af5b 0 {'date': '* *', 'user': 'test'} (glob)
+  $ hg evolve
   move:[4] another feature
   atop:[6] a nifty feature
   merging main-file-1
@@ -299,7 +299,42 @@
 phase change turning obsolete changeset public issue a latecomer warning
 
   $ hg phase --public 7
-  1 new latecomers changesets
+  1 new latecomer changesets
+
+all solving latecomer troubled
+
+  $ hg glog
+  @  8	feature-B: another feature that rox - test
+  |
+  | o  7	: another feature - test
+  |/
+  o  6	feature-A: a nifty feature - test
+  |
+  o  0	: base - test
+  
+  $ hg evolve --any --traceback
+  recreate:[8] another feature that rox
+  atop:[7] another feature
+  computing new diff
+  commited as 8d77fa12ab0c
+  $ hg glog
+  @  9	feature-B: latecomer update to 5f4744038ed5: - test
+  |
+  o  7	: another feature - test
+  |
+  o  6	feature-A: a nifty feature - test
+  |
+  o  0	: base - test
+  
+  $ hg diff -r 9 -r 8
+  $ hg diff -r 9^ -r 9
+  diff --git a/main-file-1 b/main-file-1
+  --- a/main-file-1
+  +++ b/main-file-1
+  @@ -3,1 +3,1 @@
+  -Zwei
+  +deux
+  $ hg log -r 'latecomer()' # no more latecomer
 
   $ cd ..
 
@@ -405,9 +440,9 @@
   |/
   o  0:8685c6d34325@default(draft) add 0
   
-  $ hg graft 3 -O
+  $ hg graft -r3 -O
   grafting revision 3
-  $ hg graft 1 -o 2
+  $ hg graft -r1 -o 2
   grafting revision 1
   $ glog --hidden
   @  6:acb28cd497b7@default(draft) add 1
@@ -416,17 +451,17 @@
   |
   o  4:ce341209337f@default(draft) add 4
   |
-  | o  3:0e84df4912da@default(secret) add 3
+  | x  3:0e84df4912da@default(draft) add 3
   | |
-  | o  2:db038628b9e5@default(secret) add 2
+  | x  2:db038628b9e5@default(draft) add 2
   | |
   | o  1:73d38bb17fd7@default(draft) add 1
   |/
   o  0:8685c6d34325@default(draft) add 0
   
-  $ hg debugsuccessors
-  0e84df4912da 0b9e50c35132
-  db038628b9e5 acb28cd497b7
+  $ hg debugobsolete
+  0e84df4912da4c7cad22a3b4fcfd58ddfb7c8ae9 0b9e50c35132ff548ec0065caea6a87e1ebcef32 0 {'date': '* *', 'user': 'test'} (glob)
+  db038628b9e56f51a454c0da0c508df247b41748 acb28cd497b7f8767e01ef70f68697a959573c2d 0 {'date': '* *', 'user': 'test'} (glob)
 
 Test graft --continue
 
@@ -452,7 +487,7 @@
   $ glog --hidden
   @  8:920e58bb443b@default(draft) conflict
   |
-  | o  7:a5bfd90a2f29@default(secret) conflict
+  | x  7:a5bfd90a2f29@default(draft) conflict
   | |
   o |  6:acb28cd497b7@default(draft) add 1
   | |
@@ -460,18 +495,87 @@
   | |
   o |  4:ce341209337f@default(draft) add 4
   |/
-  | o  3:0e84df4912da@default(secret) add 3
+  | x  3:0e84df4912da@default(draft) add 3
   | |
-  | o  2:db038628b9e5@default(secret) add 2
+  | x  2:db038628b9e5@default(draft) add 2
   | |
   | o  1:73d38bb17fd7@default(draft) add 1
   |/
   o  0:8685c6d34325@default(draft) add 0
   
-  $ hg debugsuccessors
-  0e84df4912da 0b9e50c35132
-  a5bfd90a2f29 920e58bb443b
-  db038628b9e5 acb28cd497b7
+  $ hg debugobsolete
+  0e84df4912da4c7cad22a3b4fcfd58ddfb7c8ae9 0b9e50c35132ff548ec0065caea6a87e1ebcef32 0 {'date': '* *', 'user': 'test'} (glob)
+  db038628b9e56f51a454c0da0c508df247b41748 acb28cd497b7f8767e01ef70f68697a959573c2d 0 {'date': '* *', 'user': 'test'} (glob)
+  a5bfd90a2f29c7ccb8f917ff4e5013a9053d0a04 920e58bb443b73eea9d6d65570b4241051ea3229 0 {'date': '* *', 'user': 'test'} (glob)
+
+Test touch
 
-  $ cd ..
+  $ glog
+  @  8:920e58bb443b@default(draft) conflict
+  |
+  o  6:acb28cd497b7@default(draft) add 1
+  |
+  o  5:0b9e50c35132@default(draft) add 3
+  |
+  o  4:ce341209337f@default(draft) add 4
+  |
+  | o  1:73d38bb17fd7@default(draft) add 1
+  |/
+  o  0:8685c6d34325@default(draft) add 0
+  
+  $ hg touch
+  $ glog
+  @  9:*@default(draft) conflict (glob)
+  |
+  o  6:acb28cd497b7@default(draft) add 1
+  |
+  o  5:0b9e50c35132@default(draft) add 3
+  |
+  o  4:ce341209337f@default(draft) add 4
+  |
+  | o  1:73d38bb17fd7@default(draft) add 1
+  |/
+  o  0:8685c6d34325@default(draft) add 0
+  
+  $ hg touch .
+  $ glog
+  @  10:*@default(draft) conflict (glob)
+  |
+  o  6:acb28cd497b7@default(draft) add 1
+  |
+  o  5:0b9e50c35132@default(draft) add 3
+  |
+  o  4:ce341209337f@default(draft) add 4
+  |
+  | o  1:73d38bb17fd7@default(draft) add 1
+  |/
+  o  0:8685c6d34325@default(draft) add 0
+  
 
+Test fold
+
+  $ hg fold 6::10
+  2 changesets folded
+  $ glog
+  @  11:*@default(draft) add 1 (glob)
+  |
+  o  5:0b9e50c35132@default(draft) add 3
+  |
+  o  4:ce341209337f@default(draft) add 4
+  |
+  | o  1:73d38bb17fd7@default(draft) add 1
+  |/
+  o  0:8685c6d34325@default(draft) add 0
+  
+  $ hg log -r 11 --template '{desc}\n'
+  add 1
+  
+  ***
+  
+  conflict
+
+Test olog
+
+  $ hg olog
+  6	: add 1 - test
+  10	: conflict - test
--- a/tests/test-obsolete-push.t	Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-obsolete-push.t	Fri Aug 24 11:53:55 2012 +0200
@@ -5,7 +5,6 @@
   > hgext.rebase=
   > hgext.graphlog=
   > EOF
-  $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
 
   $ template='{rev}:{node|short}@{branch}({obsolete}/{phase}) {desc|firstline}\n'
@@ -28,13 +27,13 @@
   $ hg ci -qAm C c
   $ hg phase --secret --force .
   $ hg kill 0 1
-  1 new unstables changesets
+  1 new unstable changesets
   $ glog --hidden
   @  2:244232c2222a@default(unstable/secret) C
   |
-  | o  1:6c81ed0049f8@default(extinct/secret) B
+  | x  1:6c81ed0049f8@default(extinct/draft) B
   |/
-  o  0:1994f17a630e@default(suspended/secret) A
+  x  0:1994f17a630e@default(suspended/draft) A
   
   $ hg init ../clone
   $ cat >  ../clone/.hg/hgrc <<EOF
@@ -44,5 +43,4 @@
   $ hg outgoing ../clone --template "$template"
   comparing with ../clone
   searching for changes
-  no changes found (ignored 2 secret changesets)
-  [1]
+  0:1994f17a630e@default(suspended/draft) A
--- a/tests/test-obsolete-rebase.t	Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-obsolete-rebase.t	Fri Aug 24 11:53:55 2012 +0200
@@ -5,7 +5,7 @@
   > hgext.rebase=
   > hgext.graphlog=
   > EOF
-  $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
+  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
 
   $ glog() {
   >   hg glog --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n'\
@@ -30,6 +30,15 @@
   created new head
   $ echo e > e
   $ hg ci -Am adde e
+
+(phase compliance)
+
+  $ hg phase --public 3
+  $ hg rebase -d 1 -r 3
+  abort: can't rebase immutable changeset 98e4a024635e
+  (see hg help phases for details)
+  [255]
+  $ hg phase --draft --force 0
   $ hg rebase -d 1 -r 3 --detach --keep  
   $ glog
   @  4:9c5494949763@default(draft) adde
@@ -53,7 +62,7 @@
   |/
   o  0:07f494440405@default(draft) adda
   
-  $ hg debugsuccessors
+  $ hg debugobsolete
   $ hg --config extensions.hgext.mq= strip tip
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9c5494949763-backup.hg
@@ -70,7 +79,7 @@
   $ glog --hidden
   @  4:9c5494949763@default(draft) adde
   |
-  | o  3:98e4a024635e@default(secret) adde
+  | x  3:98e4a024635e@default(draft) adde
   | |
   | o  2:102a90ea7b4a@default(draft) addb
   | |
@@ -78,8 +87,8 @@
   |/
   o  0:07f494440405@default(draft) adda
   
-  $ hg debugsuccessors
-  98e4a024635e 9c5494949763
+  $ hg debugobsolete
+  98e4a024635e8c50928144c9277a4388d26bd786 9c54949497631abfb5a255d96746bbd3a42ed2ba 0 {'date': '* *', 'user': 'test'} (glob)
 
 Test rebase with deleted empty revision
 
@@ -92,11 +101,11 @@
   $ hg ci -m changea
   $ hg rebase -d 1
   $ glog --hidden
-  o  5:4e322f7ce8e3@foo(secret) changea
+  x  5:4e322f7ce8e3@foo(draft) changea
   |
   | o  4:9c5494949763@default(draft) adde
   | |
-  | | o  3:98e4a024635e@default(secret) adde
+  | | x  3:98e4a024635e@default(draft) adde
   | | |
   +---o  2:102a90ea7b4a@default(draft) addb
   | |
@@ -104,9 +113,9 @@
   |/
   o  0:07f494440405@default(draft) adda
   
-  $ hg debugsuccessors
-  4e322f7ce8e3 000000000000
-  98e4a024635e 9c5494949763
+  $ hg debugobsolete
+  98e4a024635e8c50928144c9277a4388d26bd786 9c54949497631abfb5a255d96746bbd3a42ed2ba 0 {'date': '* *', 'user': 'test'} (glob)
+  4e322f7ce8e3e4203950eac9ece27bf7e45ffa6c 540395c442253af3b991be882b539e7e198b5808 0 {'date': '* *', 'user': 'test'} (glob)
 
 Test rebase --collapse
 
@@ -123,15 +132,15 @@
   $ glog --hidden
   @  8:a7773ffa7edc@default(draft) Collapsed revision
   |
-  | o  7:03f31481307a@default(secret) changec
+  | x  7:03f31481307a@default(draft) changec
   | |
-  | o  6:076e9b2ffbe1@default(secret) addc
+  | x  6:076e9b2ffbe1@default(draft) addc
   | |
-  | | o  5:4e322f7ce8e3@foo(secret) changea
+  | | x  5:4e322f7ce8e3@foo(draft) changea
   | |/
   +---o  4:9c5494949763@default(draft) adde
   | |
-  | | o  3:98e4a024635e@default(secret) adde
+  | | x  3:98e4a024635e@default(draft) adde
   | | |
   | | o  2:102a90ea7b4a@default(draft) addb
   | |/
@@ -139,15 +148,15 @@
   |/
   o  0:07f494440405@default(draft) adda
   
-  $ hg debugsuccessors
-  03f31481307a a7773ffa7edc
-  076e9b2ffbe1 a7773ffa7edc
-  4e322f7ce8e3 000000000000
-  98e4a024635e 9c5494949763
+  $ hg debugobsolete
+  98e4a024635e8c50928144c9277a4388d26bd786 9c54949497631abfb5a255d96746bbd3a42ed2ba 0 {'date': '* *', 'user': 'test'} (glob)
+  4e322f7ce8e3e4203950eac9ece27bf7e45ffa6c 540395c442253af3b991be882b539e7e198b5808 0 {'date': '* *', 'user': 'test'} (glob)
+  076e9b2ffbe11c7bcb9ee97f5c0c8b88a1a10b93 a7773ffa7edcfac27b5dcdb2d5c1036e15a49861 0 {'date': '* *', 'user': 'test'} (glob)
+  03f31481307aaf5275d07ec28c1c59931759ccd2 a7773ffa7edcfac27b5dcdb2d5c1036e15a49861 0 {'date': '* *', 'user': 'test'} (glob)
 
 Test rebase --abort
 
-  $ hg debugsuccessors > ../successors.old
+  $ hg debugobsolete > ../successors.old
   $ hg up 0
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ echo d > d
@@ -164,7 +173,7 @@
   $ hg rebase --abort
   saved backup bundle to $TESTTMP/repo/.hg/strip-backup/03f165c84ea8-backup.hg
   rebase aborted
-  $ hg debugsuccessors > ../successors.new
+  $ hg debugobsolete > ../successors.new
   $ diff -u ../successors.old ../successors.new
 
 Test rebase --continue
@@ -182,21 +191,21 @@
   |
   o  11:03f165c84ea8@default(draft) addd
   |
-  | o  10:4b9d80f48523@default(secret) appendab
+  | x  10:4b9d80f48523@default(draft) appendab
   | |
-  | o  9:a31943eabc43@default(secret) addd
+  | x  9:a31943eabc43@default(draft) addd
   | |
   +---o  8:a7773ffa7edc@default(draft) Collapsed revision
   | |
-  | | o  7:03f31481307a@default(secret) changec
+  | | x  7:03f31481307a@default(draft) changec
   | | |
-  | | o  6:076e9b2ffbe1@default(secret) addc
+  | | x  6:076e9b2ffbe1@default(draft) addc
   | |/
-  | | o  5:4e322f7ce8e3@foo(secret) changea
+  | | x  5:4e322f7ce8e3@foo(draft) changea
   | |/
   +---o  4:9c5494949763@default(draft) adde
   | |
-  | | o  3:98e4a024635e@default(secret) adde
+  | | x  3:98e4a024635e@default(draft) adde
   | | |
   | | o  2:102a90ea7b4a@default(draft) addb
   | |/
@@ -204,15 +213,14 @@
   |/
   o  0:07f494440405@default(draft) adda
   
-  $ hg debugsuccessors > ../successors.new
+  $ hg debugobsolete > ../successors.new
   $ diff -u ../successors.old ../successors.new
   --- ../successors.old* (glob)
   +++ ../successors.new* (glob)
-  @@ -1,4 +1,6 @@
-   03f31481307a a7773ffa7edc
-   076e9b2ffbe1 a7773ffa7edc
-  +4b9d80f48523 1951ead97108
-   4e322f7ce8e3 000000000000
-   98e4a024635e 9c5494949763
-  +a31943eabc43 03f165c84ea8
+  @@ -2,3 +2,5 @@
+   4e322f7ce8e3e4203950eac9ece27bf7e45ffa6c 540395c442253af3b991be882b539e7e198b5808 0 {'date': '* *', 'user': 'test'} (glob)
+   076e9b2ffbe11c7bcb9ee97f5c0c8b88a1a10b93 a7773ffa7edcfac27b5dcdb2d5c1036e15a49861 0 {'date': '* *', 'user': 'test'} (glob)
+   03f31481307aaf5275d07ec28c1c59931759ccd2 a7773ffa7edcfac27b5dcdb2d5c1036e15a49861 0 {'date': '* *', 'user': 'test'} (glob)
+  +a31943eabc4327df16f9eca71bf7779c32f815f7 03f165c84ea8889fc35a64a392caa7a0084dd212 0 {'date': '* *', 'user': 'test'} (glob)
+  +4b9d80f48523e296f4402cc8e37236b768dfb981 1951ead9710803dbf117e95901954d5ed717f80b 0 {'date': '* *', 'user': 'test'} (glob)
   [1]
--- a/tests/test-obsolete.t	Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-obsolete.t	Fri Aug 24 11:53:55 2012 +0200
@@ -8,13 +8,17 @@
   > odiff=diff --rev 'limit(precursors(.),1)' --rev .
   > [extensions]
   > hgext.graphlog=
+  > hgext.rebase=
   > EOF
-  $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
+  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
   $ mkcommit() {
   >    echo "$1" > "$1"
   >    hg add "$1"
   >    hg ci -m "add $1"
   > }
+  $ getid() {
+  >    hg id --debug -ir "$1"
+  > }
 
   $ alias qlog="hg log --template='{rev}\n- {node|short}\n'"
   $ hg init local
@@ -27,9 +31,21 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ mkcommit obsol_c # 3
   created new head
-  $ hg debugobsolete 3 2
+  $ getid 2
+  4538525df7e2b9f09423636c61ef63a4cb872a2d
+  $ getid 3
+  0d3f46688ccc6e756c7e96cf64c391c411309597
+  $ hg debugobsolete 4538525df7e2b9f09423636c61ef63a4cb872a2d 0d3f46688ccc6e756c7e96cf64c391c411309597
+  $ hg debugobsolete
+  4538525df7e2b9f09423636c61ef63a4cb872a2d 0d3f46688ccc6e756c7e96cf64c391c411309597 0 {'date': '', 'user': 'test'} (glob)
 
 
+Test hidden() revset
+
+  $ qlog -r 'hidden()' --hidden
+  2
+  - 4538525df7e2
+
 Test that obsolete changeset are hidden
 
   $ qlog
@@ -82,7 +98,7 @@
   $ hg up 1 -q
   $ mkcommit "obsol_c'" # 4 (on 1)
   created new head
-  $ hg debugobsolete 4 3
+  $ hg debugobsolete `getid 3` `getid 4`
   $ qlog
   4
   - 725c380fe99b
@@ -108,7 +124,7 @@
   $ hg up 3 -q
   Working directory parent is obsolete
   $ mkcommit d # 5 (on 3)
-  1 new unstables changesets
+  1 new unstable changesets
   $ qlog -r 'obsolete()'
   3
   - 0d3f46688ccc
@@ -127,13 +143,13 @@
 
   $ hg glog --template '{rev}:{node|short}@{branch}({obsolete}/{phase}) {desc|firstline}\n' \
   >   --hidden
-  @  5:a7a6f2b5d8a5@default(unstable/secret) add d
+  @  5:a7a6f2b5d8a5@default(unstable/draft) add d
   |
   | o  4:725c380fe99b@default(stable/draft) add obsol_c'
   | |
-  o |  3:0d3f46688ccc@default(suspended/secret) add obsol_c
+  x |  3:0d3f46688ccc@default(suspended/draft) add obsol_c
   |/
-  | o  2:4538525df7e2@default(extinct/secret) add c
+  | x  2:4538525df7e2@default(extinct/draft) add c
   |/
   o  1:7c3bad9141dc@default(stable/draft) add b
   |
@@ -144,11 +160,13 @@
 
   $ hg init ../other-new
   $ hg phase --draft 'secret() - extinct()' # until we fix exclusion
+  abort: empty revision set
+  [255]
   $ hg push ../other-new
   pushing to ../other-new
   searching for changes
   abort: push includes an unstable changeset: a7a6f2b5d8a5!
-  (use 'hg stabilize' to get a stable history (or --force to proceed))
+  (use 'hg evolve' to get a stable history or --force to ignore warnings)
   [255]
   $ hg push -f ../other-new
   pushing to ../other-new
@@ -181,8 +199,8 @@
   Working directory parent is obsolete
   $ mkcommit obsol_d # 6
   created new head
-  1 new unstables changesets
-  $ hg debugobsolete 6 5
+  1 new unstable changesets
+  $ hg debugobsolete `getid 5` `getid 6`
   $ qlog
   6
   - 95de7fc6918d
@@ -201,7 +219,7 @@
   pushing to ../other-new
   searching for changes
   abort: push includes an unstable changeset: 95de7fc6918d!
-  (use 'hg stabilize' to get a stable history (or --force to proceed))
+  (use 'hg evolve' to get a stable history or --force to ignore warnings)
   [255]
   $ hg push ../other-new -f # use f because there is unstability
   pushing to ../other-new
@@ -230,15 +248,15 @@
   $ hg push ../other-new
   pushing to ../other-new
   searching for changes
-  no changes found (ignored 0 secret changesets)
+  no changes found
   [1]
 
   $ hg up -q .^ # 3
   Working directory parent is obsolete
   $ mkcommit "obsol_d'" # 7
   created new head
-  1 new unstables changesets
-  $ hg debugobsolete 7 6
+  1 new unstable changesets
+  $ hg debugobsolete `getid 6` `getid 7`
   $ hg pull -R ../other-new .
   pulling from .
   searching for changes
@@ -261,55 +279,58 @@
 
 pushing to stuff that doesn't support obsolete
 
-  $ hg init ../other-old
-  > # XXX I don't like this but changeset get published otherwise
-  > # remove it when we will get a --keep-state flag for push
-  $ echo '[extensions]'  > ../other-old/.hg/hgrc
-  $ echo "obsolete=!$(echo $(dirname $TESTDIR))/obsolete.py" >> ../other-old/.hg/hgrc
-  $ hg push ../other-old
-  pushing to ../other-old
-  searching for changes
-  abort: push includes an unstable changeset: 909a0fb57e5d!
-  (use 'hg stabilize' to get a stable history (or --force to proceed))
-  [255]
-  $ hg push -f ../other-old
-  pushing to ../other-old
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 5 changesets with 5 changes to 5 files (+1 heads)
-  $ qlog -R ../other-old
-  4
-  - 909a0fb57e5d
-  3
-  - 725c380fe99b
-  2
-  - 0d3f46688ccc
-  1
-  - 7c3bad9141dc
-  0
-  - 1f0dee641bb7
+DISABLED. the _enable switch it global :-/
+
+..  $ hg init ../other-old
+..  > # XXX I don't like this but changeset get published otherwise
+..  > # remove it when we will get a --keep-state flag for push
+..  $ echo '[extensions]'  > ../other-old/.hg/hgrc
+..  $ echo "obsolete=!$(echo $(dirname $TESTDIR))/obsolete.py" >> ../other-old/.hg/hgrc
+..  $ hg push ../other-old
+..  pushing to ../other-old
+..  searching for changes
+..  abort: push includes an unstable changeset: 909a0fb57e5d!
+..  (use 'hg evolve' to get a stable history or --force to ignore warnings)
+..  [255]
+..  $ hg push -f ../other-old
+..  pushing to ../other-old
+..  searching for changes
+..  adding changesets
+..  adding manifests
+..  adding file changes
+..  added 5 changesets with 5 changes to 5 files (+1 heads)
+..  $ qlog -R ../other-ol
+..  4
+..  - 909a0fb57e5d
+..  3
+..  - 725c380fe99b
+..  2
+..  - 0d3f46688ccc
+..  1
+..  - 7c3bad9141dc
+..  0
+..  - 1f0dee641bb7
 
 clone support
 
   $ hg clone . ../cloned
   > # The warning should go away once we have default value to set ready before we pull
-  requesting all changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 5 changesets with 5 changes to 5 files (+1 heads)
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
-  $ qlog -R ../cloned
+  $ qlog -R ../cloned --hidden
+  7
+  - 909a0fb57e5d
+  6
+  - 95de7fc6918d
+  5
+  - a7a6f2b5d8a5
   4
-  - 909a0fb57e5d
+  - 725c380fe99b
   3
-  - 725c380fe99b
+  - 0d3f46688ccc
   2
-  - 0d3f46688ccc
+  - 4538525df7e2
   1
   - 7c3bad9141dc
   0
@@ -321,8 +342,8 @@
   Working directory parent is obsolete
   $ mkcommit "obsol_d''"
   created new head
-  1 new unstables changesets
-  $ hg debugobsolete 8 7
+  1 new unstable changesets
+  $ hg debugobsolete `getid 7` `getid 8`
   $ cd ../other-new
   $ hg up -q 3
   $ hg pull ../local/
@@ -355,6 +376,8 @@
   branch: default
   commit: 1 deleted, 2 unknown (clean)
   update: 4 new changesets, 4 branch heads (merge)
+  unstable: 1 changesets
+  conflicting: 1 changesets
   $ qlog
   6
   - 909a0fb57e5d
@@ -379,8 +402,10 @@
   created new head
   $ hg id -n
   9
-  $ hg debugobsolete 9 0
-  83b5778897ad try to obsolete immutable changeset 1f0dee641bb7
+  $ hg debugobsolete `getid 0` `getid 9`
+83b5778897ad try to obsolete immutable changeset 1f0dee641bb7
+# at core level the warning is not issued
+# this is now a big issue now that we have latecomer warning
   $ qlog -r 'obsolete()'
   3
   - 0d3f46688ccc
@@ -400,7 +425,7 @@
   0
   - 1f0dee641bb7
 
-  $ hg debugobsolete null 9 #kill
+  $ hg debugobsolete `getid 9` #kill
   $ hg up null -q # to be not based on 9 anymore
   $ qlog
   8
@@ -414,6 +439,23 @@
   0
   - 1f0dee641bb7
 
+Check that auto update ignore hidden changeset
+  $ hg up 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg up 
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg id -n
+  8
+
+Check that named update do too
+
+  $ hg update default
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg id -n
+  8
+
+  $ hg up null -q # to be not based on 9 anymore
+
 check rebase compat
 
   $ hg glog -r 'not extinct()'  --template='{rev} - {node|short}\n'
@@ -421,29 +463,29 @@
   |
   | o  4 - 725c380fe99b
   | |
-  o |  3 - 0d3f46688ccc
+  x |  3 - 0d3f46688ccc
   |/
   o  1 - 7c3bad9141dc
   |
   o  0 - 1f0dee641bb7
   
 
-  $ hg glog  --template='{rev} - {node|short}\n' `(hg --version | grep -q 'version 2.1') ||  echo '--hidden'`
-  o  9 - 83b5778897ad
+  $ hg glog  --template='{rev} - {node|short}\n' --hidden
+  x  9 - 83b5778897ad
   
   o  8 - 159dfc9fa5d3
   |
-  | o  7 - 909a0fb57e5d
+  | x  7 - 909a0fb57e5d
   |/
-  | o  6 - 95de7fc6918d
+  | x  6 - 95de7fc6918d
   |/
-  | o  5 - a7a6f2b5d8a5
+  | x  5 - a7a6f2b5d8a5
   |/
   | o  4 - 725c380fe99b
   | |
-  o |  3 - 0d3f46688ccc
+  x |  3 - 0d3f46688ccc
   |/
-  | o  2 - 4538525df7e2
+  | x  2 - 4538525df7e2
   |/
   o  1 - 7c3bad9141dc
   |
@@ -452,6 +494,10 @@
 
 should not rebase extinct changeset
 
+  $ hg --config extensions.hgext.rebase= rebase -s 7 -d 4
+  whole rebase set is extinct and ignored.
+  nothing to rebase
+  [1]
   $ hg --config extensions.hgext.rebase= rebase -b 3 -d 4 --traceback
   $ hg --config extensions.graphlog= glog -r 'not extinct()'  --template='{rev} - {node|short}\n'
   @  11 - 9468a5f5d8b2
@@ -477,7 +523,7 @@
   $ hg up -q 10
   $ mkcommit "obsol_d'''"
   created new head
-  $ hg debugobsolete 12 11
+  $ hg debugobsolete `getid 11` `getid 12`
   $ hg push ../other-new --traceback
   pushing to ../other-new
   searching for changes
@@ -492,7 +538,7 @@
 
   $ cd local
   $ hg phase --public 11
-  1 new latecomers changesets
+  1 new latecomer changesets
   $ hg --config extensions.graphlog=glog glog --template='{rev} - ({phase}) {node|short} {desc}\n'
   @  12 - (draft) 6db5e282cb91 add obsol_d'''
   |
@@ -517,8 +563,8 @@
   $ hg push ../other-new/
   pushing to ../other-new/
   searching for changes
-  abort: push includes an latecomer changeset: 6db5e282cb91!
-  (use 'hg stabilize' to get a stable history (or --force to proceed))
+  abort: push includes a latecomer changeset: 6db5e282cb91!
+  (use 'hg evolve' to get a stable history or --force to ignore warnings)
   [255]
 
 Check hg commit --amend compat
@@ -571,18 +617,18 @@
      date:        Thu Jan 01 00:00:00 1970 +0000
      summary:     add a
   
-  $ hg debugsuccessors
-  0b1b6dd009c0 3734a65252e6
-  0d3f46688ccc 2033b4e49474
-  0d3f46688ccc 725c380fe99b
-  159dfc9fa5d3 9468a5f5d8b2
-  1f0dee641bb7 83b5778897ad
-  4538525df7e2 0d3f46688ccc
-  83b5778897ad 000000000000
-  909a0fb57e5d 159dfc9fa5d3
-  9468a5f5d8b2 6db5e282cb91
-  95de7fc6918d 909a0fb57e5d
-  a7a6f2b5d8a5 95de7fc6918d
+  $ hg debugobsolete
+  4538525df7e2b9f09423636c61ef63a4cb872a2d 0d3f46688ccc6e756c7e96cf64c391c411309597 0 {'date': '', 'user': 'test'} (glob)
+  0d3f46688ccc6e756c7e96cf64c391c411309597 725c380fe99b5e76613493f0903e8d11ddc70d54 0 {'date': '', 'user': 'test'} (glob)
+  a7a6f2b5d8a54b81bc7aa2fba2934ad6d700a79e 95de7fc6918dea4c9c8d5382f50649794b474c4a 0 {'date': '', 'user': 'test'} (glob)
+  95de7fc6918dea4c9c8d5382f50649794b474c4a 909a0fb57e5d909f353d89e394ffd7e0890fec88 0 {'date': '', 'user': 'test'} (glob)
+  909a0fb57e5d909f353d89e394ffd7e0890fec88 159dfc9fa5d334d7e03a0aecfc7f7ab4c3431fea 0 {'date': '', 'user': 'test'} (glob)
+  1f0dee641bb7258c56bd60e93edfa2405381c41e 83b5778897adafb967ef2f75be3aaa4fce49a4cc 0 {'date': '', 'user': 'test'} (glob)
+  83b5778897adafb967ef2f75be3aaa4fce49a4cc 0 {'date': '', 'user': 'test'} (glob)
+  0d3f46688ccc6e756c7e96cf64c391c411309597 2033b4e494742365851fac84d276640cbf52833e 0 {'date': '* *', 'user': 'test'} (glob)
+  159dfc9fa5d334d7e03a0aecfc7f7ab4c3431fea 9468a5f5d8b2c5d91e17474e95ae4791e9718fdf 0 {'date': '* *', 'user': 'test'} (glob)
+  9468a5f5d8b2c5d91e17474e95ae4791e9718fdf 6db5e282cb91df5c43ff1f1287c119ff83230d42 0 {'date': '', 'user': 'test'} (glob)
+  0b1b6dd009c037985363e2290a0b579819f659db 3734a65252e69ddcced85901647a4f335d40de1e 0 {'date': '* *', 'user': 'test'} (glob)
 
 Check conflict detection
 
@@ -595,7 +641,8 @@
   branch: default
   commit: (clean)
   update: 9 new changesets, 9 branch heads (merge)
-  $ hg debugobsolete 50f11e5e3a63 a7a6f2b5d8a5
+  latecomer: 1 changesets
+  $ hg debugobsolete `getid a7a6f2b5d8a5` `getid 50f11e5e3a63`
   $ hg log -r 'conflicting()'
   changeset:   14:50f11e5e3a63
   tag:         tip
--- a/tests/test-oldconvert.t	Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-oldconvert.t	Fri Aug 24 11:53:55 2012 +0200
@@ -53,8 +53,8 @@
      date:        Thu Jan 01 00:00:00 1970 +0000
      summary:     add a
   
-  $ hg debugsuccessors
-  7c3bad9141dc d67cd0334eee
+  $ hg debugobsolete
+  7c3bad9141dcb46ff89abf5f61856facd56e476c d67cd0334eeecfded222fed9009f0db4beb57585 0 {'date': '* *', 'user': 'test'} (glob)
   $ hg debugconvertobsolete
   nothing to do
   0 obsolete marker converted
@@ -107,8 +107,8 @@
   [255]
   $ hg debugconvertobsolete --traceback
   3 obsolete marker converted
-  $ hg debugsuccessors
-  2c3784e102bb
-  3e03d82708d4 3218406b50ed
-  5c722672795c
-  7c3bad9141dc d67cd0334eee
+  $ hg debugobsolete
+  7c3bad9141dcb46ff89abf5f61856facd56e476c d67cd0334eeecfded222fed9009f0db4beb57585 0 {'date': '* *', 'user': 'test'} (glob)
+  3e03d82708d4da97a92158558dd13386d8f09ad5 3218406b50ed13480765e7c260669620f37fba6e 0 {'date': '* *', 'user': 'Pierre-Yves David <pierre-yves.david@ens-lyon.org>'} (glob)
+  5c722672795c3a2cb94d0cc9a821c394c1475f87 0 {'date': '* *', 'user': 'Pierre-Yves David <pierre-yves.david@logilab.fr>'} (glob)
+  2c3784e102bb34ccc93862af5bd6d609ee30c577 0 {'date': '* *', 'user': 'Pierre-Yves David <pierre-yves.david@logilab.fr>'} (glob)
--- a/tests/test-qsync.t	Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-qsync.t	Fri Aug 24 11:53:55 2012 +0200
@@ -17,7 +17,6 @@
   > hgext.graphlog=
   > hgext.mq=
   > EOF
-  $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
   $ echo "qsync=$(echo $(dirname $TESTDIR))/hgext/qsync.py" >> $HGRCPATH
   $ mkcommit() {
@@ -84,7 +83,7 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ echo "a" >> a
   $ hg amend
-  1 new unstables changesets
+  1 new unstable changesets
   $ hg graft -O 3
   grafting revision 3
   $ hg qsync -a
@@ -182,7 +181,6 @@
   pulling from ../local2
   searching for changes
   no changes found
-  (run 'hg update' to get a working copy)
   $ hg pull --mq ../local2/.hg/patches
   pulling from ../local2/.hg/patches
   searching for changes
--- a/tests/test-stabilize-order.t	Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-stabilize-order.t	Fri Aug 24 11:53:55 2012 +0200
@@ -5,7 +5,6 @@
   > hgext.rebase=
   > hgext.graphlog=
   > EOF
-  $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
 
   $ glog() {
@@ -40,13 +39,13 @@
   [2] addb
   $ echo b >> b
   $ hg amend
-  1 new unstables changesets
+  1 new unstable changesets
   $ hg gdown
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   [1] adda
   $ echo a >> a
   $ hg amend
-  1 new unstables changesets
+  1 new unstable changesets
   $ glog
   @  7:f5ff10856e5a@default(draft) adda
   |
@@ -54,16 +53,16 @@
   | |
   | | o  3:7a7552255fb5@default(draft) addc
   | | |
-  | | o  2:ef23d6ef94d6@default(draft) addb
+  | | x  2:ef23d6ef94d6@default(draft) addb
   | |/
-  | o  1:93418d2c0979@default(draft) adda
+  | x  1:93418d2c0979@default(draft) adda
   |/
   o  0:c471ef929e6a@default(draft) addroot
   
 
 Test stabilizing a predecessor child
 
-  $ hg stabilize -v
+  $ hg evolve -v
   move:[5] addb
   atop:[7] adda
   hg rebase -Dr ab8cbb6d87ff -d f5ff10856e5a
@@ -77,9 +76,9 @@
   |
   | o  3:7a7552255fb5@default(draft) addc
   | |
-  | o  2:ef23d6ef94d6@default(draft) addb
+  | x  2:ef23d6ef94d6@default(draft) addb
   | |
-  | o  1:93418d2c0979@default(draft) adda
+  | x  1:93418d2c0979@default(draft) adda
   |/
   o  0:c471ef929e6a@default(draft) addroot
   
@@ -88,8 +87,8 @@
 
   $ hg up 7
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ hg debugsuccessors > successors.old
-  $ hg stabilize -v
+  $ hg debugobsolete > successors.old
+  $ hg evolve -v
   move:[3] addc
   atop:[8] addb
   hg rebase -Dr 7a7552255fb5 -d 6bf44048e43f
@@ -98,17 +97,15 @@
   resolving manifests
   getting c
   c
-  $ hg debugsuccessors > successors.new
+  $ hg debugobsolete > successors.new
   $ diff -u successors.old successors.new
   --- successors.old* (glob)
   +++ successors.new* (glob)
-  @@ -1,5 +1,6 @@
-   3a4a591493f8 f5ff10856e5a
-   3ca0ded0dc50 ab8cbb6d87ff
-  +7a7552255fb5 5e819fbb0d27
-   93418d2c0979 f5ff10856e5a
-   ab8cbb6d87ff 6bf44048e43f
-   ef23d6ef94d6 ab8cbb6d87ff
+  @@ -3,3 +3,4 @@
+   3a4a591493f80708e46f2bf6d3b4debfad8ff91e f5ff10856e5ab3c8dc420b9c11460e6832a3b78c 0 {'date': '* *', 'user': 'test'} (glob)
+   93418d2c0979643ad446f621195e78720edb05b4 f5ff10856e5ab3c8dc420b9c11460e6832a3b78c 0 {'date': '* *', 'user': 'test'} (glob)
+   ab8cbb6d87ff3ab5526735a051cba6b63f3d6775 6bf44048e43f830accbf7d2bd7bc252ad7a3b99c 0 {'date': '* *', 'user': 'test'} (glob)
+  +7a7552255fb5f8bd745e46fba6f0ca633a4dd716 5e819fbb0d278117c0a83b7f6f6486689732cfb2 0 {'date': '* *', 'user': 'test'} (glob)
   [1]
   $ glog
   @  9:5e819fbb0d27@default(draft) addc
@@ -119,8 +116,8 @@
   |
   o  0:c471ef929e6a@default(draft) addroot
   
-  $ hg stabilize -v
-  no unstable changeset
+  $ hg evolve -v
+  no troubled changeset
   [1]
 
 Test behaviour with --any
@@ -129,13 +126,13 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ echo b >> b
   $ hg amend
-  1 new unstables changesets
+  1 new unstable changesets
   $ glog
   @  11:4e7cec6b4afe@default(draft) addb
   |
   | o  9:5e819fbb0d27@default(draft) addc
   | |
-  | o  8:6bf44048e43f@default(draft) addb
+  | x  8:6bf44048e43f@default(draft) addb
   |/
   o  7:f5ff10856e5a@default(draft) adda
   |
@@ -143,11 +140,11 @@
   
   $ hg up 9
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg stabilize -v
-  nothing to stabilize here
-  (1 unstable changesets, do you want --any ?)
+  $ hg evolve -v
+  nothing to evolve here
+  (1 troubled changesets, do you want --any ?)
   [2]
-  $ hg stabilize --any -v
+  $ hg evolve --any -v
   move:[9] addc
   atop:[11] addb
   hg rebase -Dr 5e819fbb0d27 -d 4e7cec6b4afe
@@ -166,6 +163,6 @@
   |
   o  0:c471ef929e6a@default(draft) addroot
   
-  $ hg stabilize --any -v
-  no unstable changeset
+  $ hg evolve --any -v
+  no troubled changeset
   [1]
--- a/tests/test-stabilize-result.t	Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-stabilize-result.t	Fri Aug 24 11:53:55 2012 +0200
@@ -5,7 +5,6 @@
   > hgext.rebase=
   > hgext.graphlog=
   > EOF
-  $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
 
   $ glog() {
@@ -13,7 +12,7 @@
   >     '{rev}:{node|short}@{branch}({phase}) bk:[{bookmarks}] {desc|firstline}\n' "$@"
   > }
 
-Test stabilize removing the changeset being stabilized
+Test evolve removing the changeset being evolved
 
   $ hg init empty
   $ cd empty
@@ -28,8 +27,8 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo a >> a
   $ hg amend -m changea
-  1 new unstables changesets
-  $ hg stabilize -v
+  1 new unstable changesets
+  $ hg evolve -v
   move:[2] changea
   atop:[4] changea
   hg rebase -Dr cce2c55b8965 -d 1447e1c4828d
@@ -37,15 +36,255 @@
   $ glog --hidden
   @  4:1447e1c4828d@default(draft) bk:[changea] changea
   |
-  | o  3:41ad4fe8c795@default(secret) bk:[] amends 102a90ea7b4a3361e4082ed620918c261189a36a
+  | x  3:41ad4fe8c795@default(draft) bk:[] amends 102a90ea7b4a3361e4082ed620918c261189a36a
   | |
-  | | o  2:cce2c55b8965@default(secret) bk:[] changea
+  | | x  2:cce2c55b8965@default(draft) bk:[] changea
   | |/
-  | o  1:102a90ea7b4a@default(secret) bk:[] addb
+  | x  1:102a90ea7b4a@default(draft) bk:[] addb
   |/
   o  0:07f494440405@default(draft) bk:[] adda
   
-  $ hg debugsuccessors
-  102a90ea7b4a 1447e1c4828d
-  41ad4fe8c795 1447e1c4828d
-  cce2c55b8965 000000000000
+  $ hg debugobsolete
+  41ad4fe8c79565a06c89f032ef0937b3cbd68a04 1447e1c4828d2347df8f858aa041305fa4cf7db1 0 {'date': '* *', 'user': 'test'} (glob)
+  102a90ea7b4a3361e4082ed620918c261189a36a 1447e1c4828d2347df8f858aa041305fa4cf7db1 0 {'date': '* *', 'user': 'test'} (glob)
+  cce2c55b896511e0b6e04173c9450ba822ebc740 0 {'date': '* *', 'user': 'test'} (glob)
+
+Test evolve with conflict
+
+  $ ls
+  a
+  b
+  $ hg pdiff a
+  diff -r 07f494440405 a
+  --- a/a	* (glob)
+  +++ b/a	* (glob)
+  @@ -1,1 +1,2 @@
+   a
+  +a
+  $ echo 'newer a' >> a
+  $ hg ci -m 'newer a'
+  $ hg gdown
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  [4] changea
+  $ echo 'a' > a
+  $ hg amend
+  1 new unstable changesets
+  $ hg evolve
+  move:[5] newer a
+  atop:[7] changea
+  merging a
+  warning: conflicts during merge.
+  merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
+  evolve failed!
+  fix conflict and run "hg evolve --continue"
+  abort: unresolved merge conflicts (see hg help resolve)
+  [255]
+  $ hg revert -r 'unstable()' a
+  $ hg diff
+  diff -r e8cc1b534401 a
+  --- a/a	* (glob)
+  +++ b/a	* (glob)
+  @@ -1,1 +1,3 @@
+   a
+  +a
+  +newer a
+  $ hg evolve --continue
+  grafting revision 5
+  abort: unresolved merge conflicts (see hg help resolve)
+  [255]
+  $ hg resolve -m a
+  $ hg evolve --continue
+  grafting revision 5
+
+Stabilize of late comer with different parent
+==================================================
+
+(the with same parent is handled in test-evolve.t)
+
+  $ glog
+  @  8:e3183e9c0961@default(draft) bk:[] newer a
+  |
+  o  7:e8cc1b534401@default(draft) bk:[changea] changea
+  |
+  o  0:07f494440405@default(draft) bk:[] adda
+  
+Add another commit
+
+  $ hg gdown
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  [7] changea
+  $ echo 'c' > c
+  $ hg add c
+  $ hg commit -m 'add c'
+  created new head
+
+Get a successors of 8 on it
+
+  $ hg graft -O 8
+  grafting revision 8
+
+Add real change to the successors
+
+  $ echo 'babar' >> a
+  $ hg amend
+
+Make precursors public
+
+  $ hg phase --public 8
+  1 new latecomer changesets
+  $ glog
+  @  12:15c83af6f3a3@default(draft) bk:[] newer a
+  |
+  o  9:355c5cda4de1@default(draft) bk:[] add c
+  |
+  | o  8:e3183e9c0961@default(public) bk:[] newer a
+  |/
+  o  7:e8cc1b534401@default(public) bk:[changea] changea
+  |
+  o  0:07f494440405@default(public) bk:[] adda
+  
+
+Stabilize !
+
+  $ hg evolve --any --dry-run
+  recreate:[12] newer a
+  atop:[8] newer a
+  hg rebase --rev 15c83af6f3a3 --detach e8cc1b534401;
+  hg update e3183e9c0961;
+  hg revert --all --rev 15c83af6f3a3;
+  hg commit --msg "latecomer update to %s" (no-eol)
+  $ hg evolve --any
+  recreate:[12] newer a
+  atop:[8] newer a
+  rebasing to destination parent: e8cc1b534401
+  computing new diff
+  commited as 1d94fef80e85
+  $ glog
+  @  14:1d94fef80e85@default(draft) bk:[] latecomer update to e3183e9c0961:
+  |
+  | o  9:355c5cda4de1@default(draft) bk:[] add c
+  | |
+  o |  8:e3183e9c0961@default(public) bk:[] newer a
+  |/
+  o  7:e8cc1b534401@default(public) bk:[changea] changea
+  |
+  o  0:07f494440405@default(public) bk:[] adda
+  
+
+Stabilize of conflicting changeset with same parent
+====================================================
+
+  $ rm a.orig
+  $ hg up 9
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat << EOF >> a
+  > flore
+  > arthur
+  > zephir
+  > some
+  > less
+  > conflict
+  > EOF
+  $ hg ci -m 'More addition'
+  created new head
+  $ glog
+  @  15:7391601a4bfa@default(draft) bk:[] More addition
+  |
+  | o  14:1d94fef80e85@default(draft) bk:[] latecomer update to e3183e9c0961:
+  | |
+  o |  9:355c5cda4de1@default(draft) bk:[] add c
+  | |
+  | o  8:e3183e9c0961@default(public) bk:[] newer a
+  |/
+  o  7:e8cc1b534401@default(public) bk:[changea] changea
+  |
+  o  0:07f494440405@default(public) bk:[] adda
+  
+  $ echo 'babar' >> a
+  $ hg amend
+  $ hg up 15
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  Working directory parent is obsolete
+  $ mv a a.old
+  $ echo 'jungle' > a
+  $ cat a.old >> a
+  $ rm a.old
+  $ hg amend
+  2 new conflicting changesets
+  $ glog
+  @  19:3883461cc228@default(draft) bk:[] More addition
+  |
+  | o  17:4754d61bc2db@default(draft) bk:[] More addition
+  |/
+  | o  14:1d94fef80e85@default(draft) bk:[] latecomer update to e3183e9c0961:
+  | |
+  o |  9:355c5cda4de1@default(draft) bk:[] add c
+  | |
+  | o  8:e3183e9c0961@default(public) bk:[] newer a
+  |/
+  o  7:e8cc1b534401@default(public) bk:[changea] changea
+  |
+  o  0:07f494440405@default(public) bk:[] adda
+  
+
+Stabilize It
+
+  $ hg evolve -qn
+  hg update -c 3883461cc228 &&
+  hg merge 4754d61bc2db && 
+  hg commit -m "auto merge resolving conflict between 3883461cc228 and 4754d61bc2db"&&
+  hg up -C 7391601a4bfa &&
+  hg revert --all --rev tip &&
+  hg commit -m "`hg log -r 3883461cc228 --template={desc}`";
+  $ hg evolve -v
+  merge:[19] More addition
+  with: [17] More addition
+  base: [15] More addition
+  merging conflicting changeset
+  resolving manifests
+  merging a
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  a
+  a
+  $ hg st
+  $ hg amend -d '0 0' -m 'More addition' # kill date variation XXX should be done in evolve
+  $ glog
+  @  22:ac6d600735a4@default(draft) bk:[] More addition
+  |
+  | o  14:1d94fef80e85@default(draft) bk:[] latecomer update to e3183e9c0961:
+  | |
+  o |  9:355c5cda4de1@default(draft) bk:[] add c
+  | |
+  | o  8:e3183e9c0961@default(public) bk:[] newer a
+  |/
+  o  7:e8cc1b534401@default(public) bk:[changea] changea
+  |
+  o  0:07f494440405@default(public) bk:[] adda
+  
+  $ hg summary
+  parent: 22:ac6d600735a4 tip
+   More addition
+  branch: default
+  commit: (clean)
+  update: 19 new changesets, 14 branch heads (merge)
+  $ hg export .
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  # Node ID ac6d600735a49ee377e29d1f74a0576e8c972e7b
+  # Parent  355c5cda4de162658ed9f961a98a73a10b3167b1
+  More addition
+  
+  diff -r 355c5cda4de1 -r ac6d600735a4 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,9 @@
+  +jungle
+   a
+  +flore
+  +arthur
+  +zephir
+  +some
+  +less
+  +conflict
+  +babar
--- a/tests/test-tutorial.t	Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-tutorial.t	Fri Aug 24 11:53:55 2012 +0200
@@ -1,1 +1,772 @@
-../docs/tutorials/tutorial.t
\ No newline at end of file
+
+Initial setup
+-------------
+
+This Mercurial configuration example is used for testing.
+.. Various setup
+
+  $ cat >> $HGRCPATH << EOF
+  > [ui]
+  > logtemplate ="{node|short} ({phase}): {desc}\n"
+  > [diff]
+  > git = 1
+  > [alias]
+  > # "-d '0 0'" means that the new commit will be at January 1st 1970.
+  > # This is used for stable hash during test
+  > amend = amend -d '0 0'
+  > [extensions]
+  > hgext.graphlog=
+  > EOF
+
+  $ hg init local
+  $ cat >> local/.hg/hgrc << EOF
+  > [paths]
+  > remote = ../remote
+  > other = ../other
+  > [ui]
+  > user = Babar the King
+  > EOF
+
+  $ hg init remote
+  $ cat >> remote/.hg/hgrc << EOF
+  > [paths]
+  > local = ../local
+  > [ui]
+  > user = Celestine the Queen
+  > EOF
+
+  $ hg init other
+  $ cat >> other/.hg/hgrc << EOF
+  > [ui]
+  > user = Princess Flore
+  > EOF
+
+
+This tutorial use the following configuration for Mercurial:
+
+A compact log template with phase data:
+
+  $ hg showconfig ui
+  ui.slash=True
+  ui.logtemplate="{node|short} ({phase}): {desc}\n"
+
+Improved git format diff:
+
+  $ hg showconfig diff
+  diff.git=1
+
+And the graphlog extension
+  $ hg showconfig extensions
+  extensions.hgext.graphlog=
+
+And of course, we anabled the experimental extensions for mutable history:
+
+  $ $(dirname $TESTDIR)/enable.sh >> $HGRCPATH 2> /dev/null
+
+
+-----------------------
+Single Developer Usage
+-----------------------
+
+This tutorial shows how to use evolution to rewrite history locally.
+
+
+Fixing mistake with `hg amend`
+--------------------------------
+
+We are versionning a shopping list
+
+  $ cd local
+  $ cat  >> shopping << EOF
+  > Spam
+  > Whizzo butter
+  > Albatross
+  > Rat (rather a lot)
+  > Jugged fish
+  > Blancmange
+  > Salmon mousse
+  > EOF
+  $ hg commit -A -m "Monthy Python Shopping list"
+  adding shopping
+
+Its first version is shared with the outside.
+
+  $ hg push remote
+  pushing to $TESTTMP/remote
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+
+Later I add additional item to my list
+
+  $ cat >> shopping << EOF
+  > Egg
+  > Suggar
+  > Vinegar
+  > Oil
+  > EOF
+  $ hg commit -m "adding condiment"
+  $ cat >> shopping << EOF
+  > Bananos
+  > Pear
+  > Apple
+  > EOF
+  $ hg commit -m "adding fruit"
+
+This history is very linear
+
+  $ hg glog
+  @  d85de4546133 (draft): adding fruit
+  |
+  o  4d5dc8187023 (draft): adding condiment
+  |
+  o  7e82d3f3c2cb (public): Monthy Python Shopping list
+  
+
+But a typo was made in Babanas!
+
+  $ hg export tip
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  # Node ID d85de4546133030c82d257bbcdd9b1b416d0c31c
+  # Parent  4d5dc81870237d492284826e21840b2ca00e26d1
+  adding fruit
+  
+  diff --git a/shopping b/shopping
+  --- a/shopping
+  +++ b/shopping
+  @@ -9,3 +9,6 @@
+   Suggar
+   Vinegar
+   Oil
+  +Bananos
+  +Pear
+  +Apple
+
+The faulty changeset is in the "draft" phase because he was not exchanged with
+the outside. The first one have been exchanged and is an immutable public
+changeset.
+
+  $ hg glog
+  @  d85de4546133 (draft): adding fruit
+  |
+  o  4d5dc8187023 (draft): adding condiment
+  |
+  o  7e82d3f3c2cb (public): Monthy Python Shopping list
+  
+
+hopefully. I can use hg amend to rewrite my faulty changeset!
+
+  $ sed -i'' -e s/Bananos/Banana/ shopping
+  $ hg diff
+  diff --git a/shopping b/shopping
+  --- a/shopping
+  +++ b/shopping
+  @@ -9,6 +9,6 @@
+   Suggar
+   Vinegar
+   Oil
+  -Bananos
+  +Banana
+   Pear
+   Apple
+  $ hg amend
+
+A new changeset with the right diff replace the wrong one.
+
+  $ hg glog
+  @  0cacb48f4482 (draft): adding fruit
+  |
+  o  4d5dc8187023 (draft): adding condiment
+  |
+  o  7e82d3f3c2cb (public): Monthy Python Shopping list
+  
+  $ hg export tip
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  # Node ID 0cacb48f44828d2fd31c4e45e18fde32a5b2f07b
+  # Parent  4d5dc81870237d492284826e21840b2ca00e26d1
+  adding fruit
+  
+  diff --git a/shopping b/shopping
+  --- a/shopping
+  +++ b/shopping
+  @@ -9,3 +9,6 @@
+   Suggar
+   Vinegar
+   Oil
+  +Banana
+  +Pear
+  +Apple
+
+Getting Ride of branchy history
+----------------------------------
+
+While I was working on my list. someone help made a change remotly.
+
+  $ cd ../remote
+  $ hg up -q
+  $ sed -i'' -e 's/Spam/Spam Spam Spam/' shopping
+  $ hg ci -m 'SPAM'
+  $ cd ../local
+
+I'll get this remote changeset when pulling
+
+  $ hg pull remote
+  pulling from $TESTTMP/remote
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  (run 'hg heads .' to see heads, 'hg merge' to merge)
+
+I now have a new heads. Note that this remote head is immutable
+
+  $ hg log -G
+  o  9ca060c80d74 (public): SPAM
+  |
+  | @  0cacb48f4482 (draft): adding fruit
+  | |
+  | o  4d5dc8187023 (draft): adding condiment
+  |/
+  o  7e82d3f3c2cb (public): Monthy Python Shopping list
+  
+
+instead of merging my head with the new one. I'm going to rebase my work
+
+  $ hg diff
+  $ hg rebase -d 9ca060c80d74 -s 4d5dc8187023
+  merging shopping
+  merging shopping
+
+
+My local work is now rebased on the remote one.
+
+  $ hg log -G
+  @  387187ad9bd9 (draft): adding fruit
+  |
+  o  dfd3a2d7691e (draft): adding condiment
+  |
+  o  9ca060c80d74 (public): SPAM
+  |
+  o  7e82d3f3c2cb (public): Monthy Python Shopping list
+  
+
+Removing changeset
+------------------------
+
+I add new item to my list
+
+  $ cat >> shopping << EOF
+  > car
+  > bus
+  > plane
+  > boat
+  > EOF
+  $ hg ci -m 'transport'
+  $ hg log -G
+  @  d58c77aa15d7 (draft): transport
+  |
+  o  387187ad9bd9 (draft): adding fruit
+  |
+  o  dfd3a2d7691e (draft): adding condiment
+  |
+  o  9ca060c80d74 (public): SPAM
+  |
+  o  7e82d3f3c2cb (public): Monthy Python Shopping list
+  
+
+I have a new commit but I realize that don't want it. (transport shop list does
+not fit well in my standard shopping list)
+
+  $ hg prune . # . is for working directory parent
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory now at 387187ad9bd9
+
+The silly changeset is gone.
+
+  $ hg log -G
+  @  387187ad9bd9 (draft): adding fruit
+  |
+  o  dfd3a2d7691e (draft): adding condiment
+  |
+  o  9ca060c80d74 (public): SPAM
+  |
+  o  7e82d3f3c2cb (public): Monthy Python Shopping list
+  
+
+Reordering changeset
+------------------------
+
+
+We create two changesets.
+
+
+  $ cat >> shopping << EOF
+  > Shampoo
+  > Toothbrush
+  > ... More bathroom stuff to come
+  > Towel
+  > Soap
+  > EOF
+  $ hg ci -m 'bathroom stuff' -q # XXX remove the -q
+
+  $ sed -i'' -e 's/Spam/Spam Spam Spam/g' shopping
+  $ hg ci -m 'SPAM SPAM'
+  $ hg log -G
+  @  c48f32fb1787 (draft): SPAM SPAM
+  |
+  o  8d39a843582d (draft): bathroom stuff
+  |
+  o  387187ad9bd9 (draft): adding fruit
+  |
+  o  dfd3a2d7691e (draft): adding condiment
+  |
+  o  9ca060c80d74 (public): SPAM
+  |
+  o  7e82d3f3c2cb (public): Monthy Python Shopping list
+  
+
+.. note: don't amend changeset 7e82d3f3c2cb or 9ca060c80d74 as they are immutable.
+
+I now want to push to remote all my change but the bathroom one that i'm not
+totally happy with yet. To be able to push "SPAM SPAM" I need a version of "SPAM SPAM" not children of
+"bathroom stuff"
+
+You can use 'rebase -r' or 'graft -O' for that:
+
+  $ hg up 'p1(8d39a843582d)' # going on "bathroom stuff" parent
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg graft -O c48f32fb1787 # moving "SPAM SPAM" to the working directory parent
+  grafting revision 10
+  merging shopping
+  $ hg log -G
+  @  a2fccc2e7b08 (draft): SPAM SPAM
+  |
+  | o  8d39a843582d (draft): bathroom stuff
+  |/
+  o  387187ad9bd9 (draft): adding fruit
+  |
+  o  dfd3a2d7691e (draft): adding condiment
+  |
+  o  9ca060c80d74 (public): SPAM
+  |
+  o  7e82d3f3c2cb (public): Monthy Python Shopping list
+  
+
+We have a new SPAM SPAM version without the bathroom stuff
+
+  $ grep Spam shopping  # enouth spam
+  Spam Spam Spam Spam Spam Spam Spam Spam Spam
+  $ grep Toothbrush shopping # no Toothbrush
+  [1]
+  $ hg export .
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  # Node ID a2fccc2e7b08bbce6af7255b989453f7089e4cf0
+  # Parent  387187ad9bd9d8f9a00a9fa804a26231db547429
+  SPAM SPAM
+  
+  diff --git a/shopping b/shopping
+  --- a/shopping
+  +++ b/shopping
+  @@ -1,4 +1,4 @@
+  -Spam Spam Spam
+  +Spam Spam Spam Spam Spam Spam Spam Spam Spam
+   Whizzo butter
+   Albatross
+   Rat (rather a lot)
+
+To make sure I do not push unready changeset by mistake I set the "bathroom
+stuff" changeset in the secret phase.
+
+  $ hg phase --force --secret 8d39a843582d
+
+we can now push our change:
+
+  $ hg push remote
+  pushing to $TESTTMP/remote
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 1 files
+
+for simplicity shake we get the bathroom change in line again
+
+  $ hg rebase -Dr 8d39a843582d -d a2fccc2e7b08
+  merging shopping
+  $ hg phase --draft .
+  $ hg log -G
+  @  8a79ae8b029e (draft): bathroom stuff
+  |
+  o  a2fccc2e7b08 (public): SPAM SPAM
+  |
+  o  387187ad9bd9 (public): adding fruit
+  |
+  o  dfd3a2d7691e (public): adding condiment
+  |
+  o  9ca060c80d74 (public): SPAM
+  |
+  o  7e82d3f3c2cb (public): Monthy Python Shopping list
+  
+
+
+
+Splitting change
+------------------
+
+To be done (currently achieve with "two commit + debugobsolete")
+
+Collapsing change
+------------------
+
+To be done (currently achieve with "revert + debugobsolete" or "rebase --collapse")
+
+
+
+
+
+
+-----------------------
+Collaboration
+-----------------------
+
+
+sharing mutable changeset
+----------------------------
+
+To share mutable changeset with other just check that the repo you interact
+with is "not publishing". Otherwise you will get the previously observe
+behavior where exchanged changeset are automatically published.
+
+  $ cd ../remote
+  $ hg -R ../local/ showconfig phases
+
+the localrepo does not have any specific configuration for `phases.publish`. It
+is ``true`` by default.
+
+  $ hg pull local
+  pulling from $TESTTMP/local
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  (run 'hg update' to get a working copy)
+  $ hg log -G
+  o  8a79ae8b029e (public): bathroom stuff
+  |
+  o  a2fccc2e7b08 (public): SPAM SPAM
+  |
+  o  387187ad9bd9 (public): adding fruit
+  |
+  o  dfd3a2d7691e (public): adding condiment
+  |
+  @  9ca060c80d74 (public): SPAM
+  |
+  o  7e82d3f3c2cb (public): Monthy Python Shopping list
+  
+
+
+
+We do not want to publish the "bathroom changeset". Let's rollback the last transaction
+
+  $ hg rollback
+  repository tip rolled back to revision 4 (undo pull)
+  $ hg log -G
+  o  a2fccc2e7b08 (public): SPAM SPAM
+  |
+  o  387187ad9bd9 (public): adding fruit
+  |
+  o  dfd3a2d7691e (public): adding condiment
+  |
+  @  9ca060c80d74 (public): SPAM
+  |
+  o  7e82d3f3c2cb (public): Monthy Python Shopping list
+  
+
+Let's make the local repo "non publishing"
+
+  $ echo '[phases]' >> ../local/.hg/hgrc
+  $ echo 'publish=false' >> ../local/.hg/hgrc
+  $ echo '[phases]' >> .hg/hgrc
+  $ echo 'publish=false' >> .hg/hgrc
+  $ hg showconfig phases
+  phases.publish=false
+  $ hg -R ../local/ showconfig phases
+  phases.publish=false
+
+
+I can now exchange mutable changeset between "remote" and "local" repository.
+
+  $ hg pull local
+  pulling from $TESTTMP/local
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  (run 'hg update' to get a working copy)
+  $ hg log -G
+  o  8a79ae8b029e (draft): bathroom stuff
+  |
+  o  a2fccc2e7b08 (public): SPAM SPAM
+  |
+  o  387187ad9bd9 (public): adding fruit
+  |
+  o  dfd3a2d7691e (public): adding condiment
+  |
+  @  9ca060c80d74 (public): SPAM
+  |
+  o  7e82d3f3c2cb (public): Monthy Python Shopping list
+  
+
+Rebasing unstable change after pull
+----------------------------------------------
+
+Remotely someone add a new changeset on top of the mutable "bathroom" on.
+
+  $ hg up 8a79ae8b029e -q
+  $ cat >> shopping << EOF
+  > Giraffe
+  > Rhino
+  > Lion
+  > Bear
+  > EOF
+  $ hg ci -m 'animals'
+
+But at the same time, locally, this same "bathroom changeset" was updated.
+
+  $ cd ../local
+  $ hg up 8a79ae8b029e -q
+  $ sed -i'' -e 's/... More bathroom stuff to come/Bath Robe/' shopping
+  $ hg amend
+  $ hg log -G
+  @  ffa278c50818 (draft): bathroom stuff
+  |
+  o  a2fccc2e7b08 (public): SPAM SPAM
+  |
+  o  387187ad9bd9 (public): adding fruit
+  |
+  o  dfd3a2d7691e (public): adding condiment
+  |
+  o  9ca060c80d74 (public): SPAM
+  |
+  o  7e82d3f3c2cb (public): Monthy Python Shopping list
+  
+
+
+When we pull from remote again we get an unstable state!
+
+
+  $ hg pull remote
+  pulling from $TESTTMP/remote
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  (run 'hg heads .' to see heads, 'hg merge' to merge)
+  1 new unstable changesets
+
+
+The new changeset "animal" is based one an old changeset of "bathroom". You can
+see both version showing up in the log.
+
+  $ hg log -G
+  o  9ac5d0e790a2 (draft): animals
+  |
+  | @  ffa278c50818 (draft): bathroom stuff
+  | |
+  x |  8a79ae8b029e (draft): bathroom stuff
+  |/
+  o  a2fccc2e7b08 (public): SPAM SPAM
+  |
+  o  387187ad9bd9 (public): adding fruit
+  |
+  o  dfd3a2d7691e (public): adding condiment
+  |
+  o  9ca060c80d74 (public): SPAM
+  |
+  o  7e82d3f3c2cb (public): Monthy Python Shopping list
+  
+
+The older version 8a79ae8b029e never ceased to exist in the local repo. It was
+jsut hidden and excluded from pull and push.
+
+.. note:: In hgview there is a nice doted relation highlighting ffa278c50818 as a new version of 8a79ae8b029e. this is not yet ported to graphlog.
+
+Their is **unstable** changeset in this history now. Mercurial will refuse to
+share it with the outside:
+
+  $ hg push other
+  pushing to $TESTTMP/other
+  searching for changes
+  abort: push includes an unstable changeset: 9ac5d0e790a2!
+  (use 'hg evolve' to get a stable history or --force to ignore warnings)
+  [255]
+ 
+
+
+
+To resolve this unstable state, you need to rebase 9ac5d0e790a2 onto
+ffa278c50818 the "hg evolve" command will make this for you.
+
+It has a --dry-run option to only suggest the next move.
+
+  $ hg evolve --dry-run
+  move:[15] animals
+  atop:[14] bathroom stuff
+  hg rebase -Dr 9ac5d0e790a2 -d ffa278c50818
+
+Let's do it
+
+  $ hg rebase -Dr 9ac5d0e790a2 -d ffa278c50818
+  merging shopping
+
+The old version of bathroom is hidden again.
+
+  $ hg log -G
+  @  437efbcaf700 (draft): animals
+  |
+  o  ffa278c50818 (draft): bathroom stuff
+  |
+  o  a2fccc2e7b08 (public): SPAM SPAM
+  |
+  o  387187ad9bd9 (public): adding fruit
+  |
+  o  dfd3a2d7691e (public): adding condiment
+  |
+  o  9ca060c80d74 (public): SPAM
+  |
+  o  7e82d3f3c2cb (public): Monthy Python Shopping list
+  
+
+
+We can push this evolution to remote
+
+  $ hg push remote
+  pushing to $TESTTMP/remote
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 1 files (+1 heads)
+
+remote get a warning that current working directory is based on an obsolete changeset
+
+  $ cd ../remote
+  $ hg pull local # we up again to trigger the warning. it was displayed during the push
+  pulling from $TESTTMP/local
+  searching for changes
+  no changes found
+  Working directory parent is obsolete
+
+  $ hg up 437efbcaf700
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Relocating unstable change after prune
+----------------------------------------------
+
+The remote guy keep working
+
+  $ sed -i'' -e 's/Spam/Spam Spam Spam Spam/g' shopping
+  $ hg commit -m "SPAM SPAM SPAM"
+
+I'm pulling its work locally.
+
+  $ cd ../local
+  $ hg pull remote
+  pulling from $TESTTMP/remote
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  (run 'hg update' to get a working copy)
+  $ hg log -G
+  o  ae45c0c3092a (draft): SPAM SPAM SPAM
+  |
+  @  437efbcaf700 (draft): animals
+  |
+  o  ffa278c50818 (draft): bathroom stuff
+  |
+  o  a2fccc2e7b08 (public): SPAM SPAM
+  |
+  o  387187ad9bd9 (public): adding fruit
+  |
+  o  dfd3a2d7691e (public): adding condiment
+  |
+  o  9ca060c80d74 (public): SPAM
+  |
+  o  7e82d3f3c2cb (public): Monthy Python Shopping list
+  
+
+In the mean time I noticed you can't buy animals in a super market and I prune the animal changeset:
+
+  $ hg prune 437efbcaf700
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory now at ffa278c50818
+  1 new unstable changesets
+
+
+The animals changeset is still displayed because the "SPAM SPAM SPAM" changeset
+is neither dead or obsolete.  My repository is in an unstable state again.
+
+  $ hg log -G
+  o  ae45c0c3092a (draft): SPAM SPAM SPAM
+  |
+  x  437efbcaf700 (draft): animals
+  |
+  @  ffa278c50818 (draft): bathroom stuff
+  |
+  o  a2fccc2e7b08 (public): SPAM SPAM
+  |
+  o  387187ad9bd9 (public): adding fruit
+  |
+  o  dfd3a2d7691e (public): adding condiment
+  |
+  o  9ca060c80d74 (public): SPAM
+  |
+  o  7e82d3f3c2cb (public): Monthy Python Shopping list
+  
+
+  $ hg log -r 'unstable()'
+  ae45c0c3092a (draft): SPAM SPAM SPAM
+
+  $ hg evolve --any
+  move:[17] SPAM SPAM SPAM
+  atop:[14] bathroom stuff
+  merging shopping
+
+  $ hg log -G
+  @  d6717f710962 (draft): SPAM SPAM SPAM
+  |
+  o  ffa278c50818 (draft): bathroom stuff
+  |
+  o  a2fccc2e7b08 (public): SPAM SPAM
+  |
+  o  387187ad9bd9 (public): adding fruit
+  |
+  o  dfd3a2d7691e (public): adding condiment
+  |
+  o  9ca060c80d74 (public): SPAM
+  |
+  o  7e82d3f3c2cb (public): Monthy Python Shopping list
+  
+
+
+Handling Conflicting amend
+----------------------------------------------
+
+We can detect that multiple diverging//conflicting amend have been made. There
+will be a "evol-merge" command to merge conflicting amend
+
+This command is not ready yet.
--- a/tests/test-uncommit.t	Tue Aug 21 12:43:21 2012 +0200
+++ b/tests/test-uncommit.t	Fri Aug 24 11:53:55 2012 +0200
@@ -3,7 +3,6 @@
   > hgext.rebase=
   > hgext.graphlog=
   > EOF
-  $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
 
   $ glog() {
@@ -220,7 +219,7 @@
   $ glog --hidden
   @  4:e8db4aa611f6@bar(stable/draft) touncommit
   |
-  | o  3:5eb72dbe0cb4@bar(extinct/secret) touncommit
+  | x  3:5eb72dbe0cb4@bar(extinct/draft) touncommit
   |/
   o    2:f63b90038565@default(stable/draft) merge
   |\
@@ -232,8 +231,8 @@
    * touncommit-bm             4:e8db4aa611f6
      touncommit-bm-inactive    4:e8db4aa611f6
      unrelated                 2:f63b90038565
-  $ hg debugsuccessors
-  5eb72dbe0cb4 e8db4aa611f6
+  $ hg debugobsolete
+  5eb72dbe0cb409d094e3b4ae8eaa30071c1b8730 e8db4aa611f6d5706374288e6898e498f5c44098 0 {'date': '* *', 'user': 'test'} (glob)
 
 Test phase is preserved, no local changes
 
@@ -242,7 +241,7 @@
   Working directory parent is obsolete
   $ hg --config extensions.purge= purge
   $ hg uncommit -I 'set:added() and e'
-  2 new conflictings changesets
+  2 new conflicting changesets
   $ hg st --copies
   A e
   $ hg st --copies --change .
@@ -263,11 +262,11 @@
   R m
   R n
   $ glog --hidden
-  @  5:c706fe2c12f8@bar(stable/secret) touncommit
+  @  5:c706fe2c12f8@bar(stable/draft) touncommit
   |
   | o  4:e8db4aa611f6@bar(stable/draft) touncommit
   |/
-  | o  3:5eb72dbe0cb4@bar(extinct/secret) touncommit
+  | x  3:5eb72dbe0cb4@bar(extinct/draft) touncommit
   |/
   o    2:f63b90038565@default(stable/draft) merge
   |\
@@ -275,9 +274,9 @@
   |
   o  0:07f494440405@default(stable/draft) adda
   
-  $ hg debugsuccessors
-  5eb72dbe0cb4 c706fe2c12f8
-  5eb72dbe0cb4 e8db4aa611f6
+  $ hg debugobsolete
+  5eb72dbe0cb409d094e3b4ae8eaa30071c1b8730 e8db4aa611f6d5706374288e6898e498f5c44098 0 {'date': '* *', 'user': 'test'} (glob)
+  5eb72dbe0cb409d094e3b4ae8eaa30071c1b8730 c706fe2c12f83ba5010cb60ea6af3bd1f0c2d6d3 0 {'date': '* *', 'user': 'test'} (glob)
 
 Test --all
 
@@ -286,7 +285,7 @@
   Working directory parent is obsolete
   $ hg --config extensions.purge= purge
   $ hg uncommit --all -X e
-  1 new conflictings changesets
+  1 new conflicting changesets
   $ hg st --copies
   M b
   M d
@@ -308,21 +307,21 @@
   $ hg st --copies --change .
   A e
 
-  $ hg debugsuccessors
-  5eb72dbe0cb4 c4cbebac3751
-  5eb72dbe0cb4 c706fe2c12f8
-  5eb72dbe0cb4 e8db4aa611f6
+  $ hg debugobsolete
+  5eb72dbe0cb409d094e3b4ae8eaa30071c1b8730 e8db4aa611f6d5706374288e6898e498f5c44098 0 {'date': '* *', 'user': 'test'} (glob)
+  5eb72dbe0cb409d094e3b4ae8eaa30071c1b8730 c706fe2c12f83ba5010cb60ea6af3bd1f0c2d6d3 0 {'date': '* *', 'user': 'test'} (glob)
+  5eb72dbe0cb409d094e3b4ae8eaa30071c1b8730 c4cbebac3751269bdf12d1466deabcc78521d272 0 {'date': '* *', 'user': 'test'} (glob)
 
 Display a warning if nothing left
 
   $ hg uncommit e
   new changeset is empty
   (use "hg kill ." to remove it)
-  $ hg debugsuccessors
-  5eb72dbe0cb4 c4cbebac3751
-  5eb72dbe0cb4 c706fe2c12f8
-  5eb72dbe0cb4 e8db4aa611f6
-  c4cbebac3751 4f1c269eab68
+  $ hg debugobsolete
+  5eb72dbe0cb409d094e3b4ae8eaa30071c1b8730 e8db4aa611f6d5706374288e6898e498f5c44098 0 {'date': '* *', 'user': 'test'} (glob)
+  5eb72dbe0cb409d094e3b4ae8eaa30071c1b8730 c706fe2c12f83ba5010cb60ea6af3bd1f0c2d6d3 0 {'date': '* *', 'user': 'test'} (glob)
+  5eb72dbe0cb409d094e3b4ae8eaa30071c1b8730 c4cbebac3751269bdf12d1466deabcc78521d272 0 {'date': '* *', 'user': 'test'} (glob)
+  c4cbebac3751269bdf12d1466deabcc78521d272 4f1c269eab68720f54e88ce3c1dc02b2858b6b89 0 {'date': '* *', 'user': 'test'} (glob)
 
 Test instability warning
 
@@ -333,4 +332,4 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   [8] touncommit
   $ hg uncommit aa
-  1 new unstables changesets
+  1 new unstable changesets