remote the states extention. (phase are now in mercurial core)
authorPierre-Yves David <pierre-yves.david@logilab.fr>
Mon, 19 Dec 2011 12:25:19 +0100
changeset 110 6d461c2143a0
parent 109 a2e8057117d3
child 111 ab4cef4fbd03
remote the states extention. (phase are now in mercurial core)
hgext/states.py
tests/test-draft.t
tests/test-ready.t
tests/test-state-strip.t
tests/test-states-enable.t
tests/test-states-exact.t
tests/test-states.t
--- a/hgext/states.py	Mon Dec 19 12:19:00 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1074 +0,0 @@
-# states.py - introduce the state concept for mercurial changeset
-#
-# Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
-#                Logilab SA        <contact@logilab.fr>
-#                Augie Fackler     <durin42@gmail.com>
-#
-# 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 state concept for mercurial changeset
-
-(see http://mercurial.selenic.com/wiki/StatesPlan)
-
-General concept
-===============
-
-This extension adds the state concept. A changeset are now in a specific state
-that control they mutability and they exchange.
-
-States properties
-.................
-
-The states extension currently alter two property for changeset
-
-:mutability:  history rewritten tool should refuse to work on immutable changeset
-:sharing:     shared changeset are exchanged during pull and push. other are not
-
-Here is a small summary of the current property of state existing state::
-
-    ||           || mutable || shared ||
-    || published ||         ||   x    ||
-    || ready     ||    x    ||   x    ||
-    || draft     ||    x    ||        ||
-
-States consistency and ordering
-...............................
-
-States of changesets have to be consistent with each other. A changeset can only have ancestors of it's state (or a compatible states)
-
-Example:
-
-    A ``published`` changeset can't have a ``draft`` parent.
-
-a state is compatible with itself and all "smaller" states. Order is as follow::
-
-    published < ready < draft
-
-
-.. note:
-
-    This section if probably far too conceptual for people. The result is just
-    that: A ``published`` changeset can only have ``published`` ancestors. A
-    ``ready`` changeset can only have ``published`` or ``ready`` ancestors.
-
-    Moreover There is a need for a nice word to refer to "a state smaller than another"
-
-
-States details
-==============
-
-
-published
-    Changesets in the ``published`` state are the core of the history.  They are
-    changesets that you published to the world. People can expect them to always
-    exist. They are changesets as you know them. **By default all changesets
-    are published**
-
-    - They are exchanged with other repositories (included in pull//push).
-
-    - They are not mutable, extensions rewriting history should refuse to
-      rewrite them.
-
-ready
-    Changesets in the ``ready`` state have not yet been accepted in the
-    immutable history. You can share them with others for review, testing or
-    improvement. Any ``ready`` changeset can either be included in the
-    published history (and become immutable) or be rewritten and never make it
-    to the published history.
-
-    - They are exchanged with other repositories (included in pull//push).
-
-    - They are mutable, extensions rewriting history accept to work on them.
-
-draft
-
-    Changesets in the ``draft`` state are heavy work in progress you are not
-    yet willing to share with others.
-
-    - They are not exchanged with other repositories. pull//push do not see them.
-    - They are mutable, extensions rewriting history accept to work on them.
-
---
-
-.. note:
-
-    The Dead states mentionned in on the wiki page are missing. There is two main reason for it:
-
-    1. The ``dead`` state has a different behaviour that requires more work to be
-       implemented.
-
-    2. I believe that the use cases of ``dead changeset`` are better covered by
-       the ``obsolete`` extension.
-
---
-
-.. note:
-
-    I'm tempted to add a state with the same property that ``ready`` for review
-    workflow.::
-
-        ||           || mutable || shared ||
-        || published ||         ||   x    ||
-        || ready     ||    x    ||   x    ||
-        || inprogress||    x    ||   x    ||
-        || draft     ||    x    ||        ||
-
-    The ``ready`` state would be for changeset that wait review of someone that
-    can "publish" them.
-
-
-
-Current Feature and usage
-=========================
-
-
-Enabling states
-...............
-
-The extension adds a :hg:`hg states` command to display and choose which states
-are used by a repository, see :hg:`hg states` for details.
-
-By default all changesets in the repository are ``published``. Other states
-must be explicitly activated. Changeset in a remote repository that doesn't
-support states are all seen as ``published``.
-
-.. note:
-
-    When a state is not activated, changesets in this state are handled as
-    changesets of the previous state it (``draft`` are handled as ``ready``,
-    ``ready`` are handled as ``published``).
-
-TODO:
-
-- have a configuration in hgrc::
-
-    [states]
-    ready=(off|on)(-inherit)?
-    <state>=(off|on)(-inherit)?
-
- :off:     state disabled for new repo
- :on:      state enabled  for new repo
- :inherit: if present, inherit states of source on :hg:`clone`.
-
-- display the number of changesets that change state when activating a state.
-
-
-
-State transition
-................
-
-Changeset you create locally will be in the ``draft`` state. (or any previous
-state if draft isn't enabled)
-
-There is some situation where the state of a changeset will change
-automatically. Automatic movement always go in the same direction.: ``draft ->
-``ready`` -> ``published``
-
-1. When you pull or push boundary move. Common changeset that are ``published`` in
-one of the two repository are set to ``published``. Same goes for ``ready`` etc
-(states are evaluated from in increasing order XXX I bet no one understand this
-parenthesis. Pull operation alter the local repository. push alter both local
-and remote repository.
-
-.. note:
-
-    As Repository without any specific state have all their changeset
-    ``published``, Pushing to such repo will ``publish`` all common changeset.
-
-2. Tagged changeset get automatically Published. The tagging changeset is
-tagged too... This doesn't apply to local tag.
-
-
-You can also manually change changeset state with a dedicated command for each
-state. See :hg:`published`, :hg:`ready` and :hg:`draft` for details.
-
-XXX maybe we can details the general behaviour here
-
-:hg <state> revs:                 move boundary of state so it includes revs
-                                  ( revs included in ::<state>heads())
-:hg --exact <state> revs:         move boundary so that revs are exactly in state
-                                  <state> ( all([rev.state == <state> for rev in
-                                  revs]))
-:hg --exact --force <state> revs: move boundary event if it create inconsistency
-                                  (with tag for example)
-
-TODO:
-
-- implement consistency check
-
-- implement --force
-
-
-Existing command change
-.......................
-
-As said in the previous section:
-
-:commit:    Create draft changeset (or the first enabled previous changeset).
-:tag:       Move tagged and tagging changeset in the ``published`` state.
-:incoming:  Exclude ``draft`` changeset of remote repository.
-:outgoing:  Exclude ``draft`` changeset of local repository.
-:pull:      As :hg:`in`  + change state of local changeset according to remote side.
-:push:      As :hg:`out` + sync state of common changeset on both side
-:rollback:  rollback restore states heads as before the last transaction (see bookmark)
-
-State Transition control
-.........................
-
-There is currently no way to control who can alter boundary (The most notable
-usecase is about the published one).
-
-This is probably needed quickly
-
-XXX TODO: Proper behaviour when heads file are chmoded whould be a first step.
-
-XXX We are going to need hooks (pre and post) hook on state transition too.
-
-Template
-........
-
-A new template keyword ``{state}`` has been added.
-
-Revset
-......
-
-We add new ``readyheads()`` and ``publishedheads()`` revset directives. This
-returns the heads of each state **as if all of them were activated**.
-
-XXX TODO - I would like to
-
-- move the current ``<state>heads()`` directives to
-  _``<state>heads()``
-
-- add ``<state>heads()`` directives to that return the currently in used heads
-
-- add ``<state>()`` directives that match all node in a state.
-
-Context
-.......
-
-The ``context`` class gain a new method  ``states()`` that return a ``state`` object. The
-most notable property of this states object are ```name`` and ``mutable``.
-
-Other extensions
-................
-
-:rebase:     can't rebase immutable changeset.
-:mq:         can't qimport immutable changeset.
-
-TODO: publishing a changeset should qfinish mq patches.
-
-
-
-Implementation
-==============
-
-State definition
-................
-
-Conceptually:
-
-The set of node in the states are defined by the set of the state heads. This allow
-easy storage, exchange and consistency.
-
-.. note: A cache of the complete set of node that belong to a states will
-         probably be need for performance.
-
-Code wise:
-
-There is a ``state`` class that hold the state property and several useful
-logic (name, revset entry etc).
-
-All defined states are accessible thought the STATES tuple at the ROOT of the
-module. Or the STATESMAP dictionary that allow to fetch a state from it's
-name.
-
-You can get and edit the list head node that define a state with two methods on
-repo.
-
-:stateheads(<state>):        Returns the list of heads node that define a states
-:setstate(<state>, [nodes]): Move states boundary forward to include the given
-                             nodes in the given states.
-
-Those methods handle ``node`` and not rev as it seems more resilient to me that
-rev in a mutable world. Maybe it' would make more sens to have ``node`` store
-on disk but revision in the code.
-
-Storage
-.......
-
-States related data are stored in the ``.hg/states/`` directory.
-
-The ``.hg/states/Enabled`` file list the states enabled in this
-repository. This data is *not* stored in the .hg/hgrc because the .hg/hgrc
-might be ignored for trust reason. As missing und with states can be pretty
-annoying. (publishing unfinalized changeset, pulling draft one etc) we don't
-want trust issue to interfer with enabled states information.
-
-``.hg/states/<state>-heads`` file list the nodes that define a states.
-
-_NOSHARE filtering
-..................
-
-Any changeset in a state with a _NOSHARE property will be exclude from pull,
-push, clone, incoming, outgoing and bundle. It is done through three mechanism:
-
-1. Wrapping the findcommonincoming and findcommonoutgoing code with (not very
-   efficient) logic that recompute the exchanged heads.
-
-2. Altering ``heads`` wireprotocol command to return sharead heads.
-
-3. Disabling hardlink cloning when there is _NOSHARE changeset available.
-
-Internal plumbery
------------------
-
-sum up of what we do:
-
-* state are object
-
-* repo.__class__ is extended
-
-* discovery is wrapped up
-
-* wire protocol is patched
-
-* transaction and rollback mechanism are wrapped up.
-
-* XXX we write new version of the boundard whenever something happen. We need a
-  smarter and faster way to do this.
-
-
-'''
-import os
-from functools import partial
-from operator import or_
-
-from mercurial.i18n import _
-from mercurial import cmdutil
-from mercurial import scmutil
-from mercurial import context
-from mercurial import revset
-from mercurial import templatekw
-from mercurial import util
-from mercurial import node
-from mercurial.node import nullid, hex, short
-from mercurial import discovery
-from mercurial import extensions
-from mercurial import wireproto
-from mercurial import pushkey
-from mercurial import error
-from mercurial import repair
-from mercurial.lock import release
-
-
-
-# states property constante
-_NOSHARE=2
-_MUTABLE=1
-
-class state(object):
-    """State of changeset
-
-    An utility object that handle several behaviour and containts useful code
-
-    A state is defined by:
-        - It's name
-        - It's property (defined right above)
-
-        - It's next state.
-
-    XXX maybe we could stick description of the state semantic here.
-    """
-
-    # plumbery utily
-    def __init__(self, name, properties=0, next=None):
-        self.name = name
-        self.properties = properties
-        assert next is None or self < next
-        self.next = next
-    @util.propertycache
-    def trackheads(self):
-        """Do we need to track heads of changeset in this state ?
-
-        We don't need to track heads for the last state as this is repo heads"""
-        return self.next is not None
-
-    # public utility
-    def __cmp__(self, other):
-        """Use property to compare states.
-
-        This is a naiv approach that assume the  the next state are strictly
-        more property than the one before
-        # assert min(self, other).properties = self.properties & other.properties
-        """
-        return cmp(self.properties, other.properties)
-
-    @property
-    def mutable(self):
-        return bool(self.properties & _MUTABLE)
-
-    # display code
-    def __repr__(self):
-        return 'state(%s)' % self.name
-
-    def __str__(self):
-        return self.name
-
-
-    # revset utility
-    @util.propertycache
-    def _revsetheads(self):
-        """function to be used by revset to finds heads of this states"""
-        assert self.trackheads
-        def revsetheads(repo, subset, x):
-            args = revset.getargs(x, 0, 0, 'publicheads takes no arguments')
-            heads = []
-            for h in repo._statesheads[self]:
-                try:
-                    heads.append(repo.changelog.rev(h))
-                except error.LookupError:
-                    pass
-            heads.sort()
-            return heads
-        return revsetheads
-
-    @util.propertycache
-    def headssymbol(self):
-        """name of the revset symbols"""
-        if self.trackheads:
-            return "%sheads" % self.name
-        else:
-            return 'heads'
-
-# Actual state definition
-
-ST2 = state('draft', _NOSHARE | _MUTABLE)
-ST1 = state('ready', _MUTABLE, next=ST2)
-ST0 = state('published', next=ST1)
-
-# all available state
-STATES = (ST0, ST1, ST2)
-# all available state by name
-STATESMAP =dict([(st.name, st) for st in STATES])
-
-@util.cachefunc
-def laststatewithout(prop):
-    """Find the states with the most property but <prop>
-
-    (This function is necessary because the whole state stuff are abstracted)"""
-    for state in STATES:
-        if not state.properties & prop:
-            candidate = state
-        else:
-            return candidate
-
-# util function
-#############################
-def noderange(repo, revsets):
-    """The same as revrange but return node"""
-    return map(repo.changelog.node,
-               scmutil.revrange(repo, revsets))
-
-# Patch changectx
-#############################
-
-def state(ctx):
-    """return the state objet associated to the context"""
-    if ctx.node()is None:
-        return STATES[-1]
-    return ctx._repo.nodestate(ctx.node())
-context.changectx.state = state
-
-# improve template
-#############################
-
-def showstate(ctx, **args):
-    """Show the name of the state associated with the context"""
-    return ctx.state()
-
-
-# New commands
-#############################
-
-
-def cmdstates(ui, repo, *states, **opt):
-    """view and modify activated states.
-
-    With no argument, list activated state.
-
-    With argument, activate the state in argument.
-
-    With argument plus the --off switch, deactivate the state in argument.
-
-    note: published state are alway activated."""
-
-    if not states:
-        for st in sorted(repo._enabledstates):
-            ui.write('%s\n' % st)
-    else:
-        off = opt.get('off', False)
-        for state_name in states:
-            for st in STATES:
-                if st.name == state_name:
-                    break
-            else:
-                ui.write_err(_('no state named %s\n') % state_name)
-                return 1
-            if off:
-                if st in repo._enabledstates:
-                    repo.disablestate(st)
-                else:
-                    ui.write_err(_('state %s already deactivated\n') %
-                                 state_name)
-
-            else:
-                repo.enablestate(st, not opt.get('clever'))
-        repo._writeenabledstates()
-    return 0
-
-cmdtable = {'states': (cmdstates, [
-    ('', 'off', False, _('desactivate the state') ),
-    ('', 'clever', False, _('do not fix lower when activating the state') )],
-    '<state>')}
-
-# automatic generation of command that set state
-def makecmd(state):
-    def cmdmoveheads(ui, repo, *changesets, **opts):
-        """set revisions in %s state
-
-        This command also alter state of ancestors if necessary.
-        """ % state
-        if not state in repo._enabledstates:
-            raise error.Abort(
-                    _('state %s is not activated' % state),
-                    hint=_('try ``hg states %s`` before' % state))
-        if opts.get('exact'):
-            repo.setstate_unsafe(state, changesets)
-            return 0
-        revs = scmutil.revrange(repo, changesets)
-        repo.setstate(state, [repo.changelog.node(rev) for rev in revs])
-        return 0
-    return cmdmoveheads
-
-for state in STATES:
-    cmdmoveheads = makecmd(state)
-    cmdtable[state.name] = (cmdmoveheads, [
-        ('e', 'exact', False, _('move boundary so that revs are exactly in '
-                               'state <state> ( all([rev.state == <state> for '
-                                'rev in revs]))'))
-        ], '<revset>')
-
-# Pushkey mechanism for mutable
-#########################################
-
-def pushstatesheads(repo, key, old, new):
-    """receive a new state for a revision via pushkey
-
-    It only move revision from a state to a <= one
-
-    Return True if the <key> revision exist in the repository
-    Return False otherwise. (and doesn't alter any state)"""
-    st = STATESMAP[new]
-    w = repo.wlock()
-    try:
-        newhead = node.bin(key)
-        try:
-            repo[newhead]
-        except error.RepoLookupError:
-            return False
-        repo.setstate(st, [newhead])
-        return True
-    finally:
-        w.release()
-
-def liststatesheads(repo):
-    """List the boundary of all states.
-
-    {"node-hex" -> "comma separated list of state",}
-    """
-    keys = {}
-    for state in [st for st in STATES if st.trackheads]:
-        for head in repo.stateheads(state):
-            head = node.hex(head)
-            if head in keys:
-                keys[head] += ',' + state.name
-            else:
-                keys[head] = state.name
-    return keys
-
-pushkey.register('states-heads', pushstatesheads, liststatesheads)
-
-
-# Wrap discovery
-####################
-def filterprivateout(orig, repo, *args,**kwargs):
-    """wrapper for findcommonoutgoing that remove _NOSHARE"""
-    common, heads = orig(repo, *args, **kwargs)
-    if getattr(repo, '_reducehead', None) is not None:
-        return common, repo._reducehead(heads)
-def filterprivatein(orig, repo, remote, *args, **kwargs):
-    """wrapper for findcommonincoming that remove _NOSHARE"""
-    common, anyinc, heads = orig(repo, remote, *args, **kwargs)
-    if getattr(remote, '_reducehead', None) is not None:
-        heads = remote._reducehead(heads)
-    return common, anyinc, heads
-
-# states boundary IO
-#####################
-
-def _readheadsfile(repo, filename):
-    """read head from the given file
-
-    XXX move me elsewhere"""
-    heads = [nullid]
-    try:
-        f = repo.opener(filename)
-        try:
-            heads = sorted([node.bin(n) for n in f.read().split() if n])
-        finally:
-            f.close()
-    except IOError:
-        pass
-    return heads
-
-def _readstatesheads(repo, undo=False):
-    """read all state heads
-
-    XXX move me elsewhere"""
-    statesheads = {}
-    for state in STATES:
-        if state.trackheads:
-            filemask = 'states/%s-heads'
-            filename = filemask % state.name
-            statesheads[state] = _readheadsfile(repo, filename)
-    return statesheads
-
-def _writeheadsfile(repo, filename, heads):
-    """write given <heads> in the file with at <filename>
-
-    XXX move me elsewhere"""
-    f = repo.opener(filename, 'w', atomictemp=True)
-    try:
-        for h in heads:
-            f.write(hex(h) + '\n')
-        try:
-            f.rename()
-        except AttributeError: # old version
-            f.close()
-    finally:
-        f.close()
-
-def _writestateshead(repo):
-    """write all heads
-
-    XXX move me elsewhere"""
-    # XXX transaction!
-    for state in STATES:
-        if state.trackheads:
-            filename = 'states/%s-heads' % state.name
-            _writeheadsfile(repo, filename, repo._statesheads[state])
-
-# WireProtocols
-####################
-def wireheads(repo, proto):
-    """Altered head command that doesn't include _NOSHARE
-
-    This is a write protocol command"""
-    st = laststatewithout(_NOSHARE)
-    h = repo.stateheads(st)
-    return wireproto.encodelist(h) + "\n"
-
-# Other extension support
-#########################
-
-def wraprebasebuildstate(orig, repo, *args, **kwargs):
-    """Wrapped rebuild state that check for immutable changeset
-
-    buildstate are the best place i found to hook :-/"""
-    result = orig(repo, *args, **kwargs)
-    if result is not None:
-        # rebase.nullmerge is issued in the detach case
-        rebase = extensions.find('rebase')
-        rebased = [rev for rev, rbst in result[2].items() if rbst != rebase.nullmerge]
-        base = repo.changelog.node(min(rebased))
-        state = repo.nodestate(base)
-        if not state.mutable:
-            raise util.Abort(_('can not rebase published changeset %s')
-                             % node.short(base),
-                             hint=_('see `hg help --extension states` for details'))
-    return result
-
-def wrapmqqimport(orig, queue, repo, *args, **kwargs):
-    """Wrapper for rebuild state that deny importing immutable changeset
-    """
-    if 'rev' in kwargs:
-       # we can take the min as non linear import will break
-       # anyway
-       revs = scmutil.revrange(repo, kwargs['rev'])
-       if revs:
-           base = min(revs)
-           basenode = repo.changelog.node(base)
-           state = repo.nodestate(basenode)
-           if not state.mutable:
-               raise util.Abort(_('can not qimport published changeset %s')
-                    % node.short(basenode),
-                    hint=_('see `hg help --extension states` for details'))
-    return orig(queue, repo, *args, **kwargs)
-
-def strip(orig, ui, repo, node, backup="all"):
-    cl = repo.changelog
-    striprev = cl.rev(node)
-    revstostrip = set(cl.descendants(striprev))
-    revstostrip.add(striprev)
-    tostrip = set(map(cl.node, revstostrip))
-    # compute the potentially new created states bondaries which are any
-    # parent of the stripped node that are not stripped (may not be heads)
-    newbondaries = set(par for nod in tostrip for par in cl.parents(nod)
-                       if par not in tostrip)
-    # save the current states of newbondaries in a chache as repo.nodestate
-    # must work along the loop. We will use the next loop to add them.
-    statesheads={}
-    for nd in newbondaries:
-        state = repo.nodestate(nd)
-        if state.trackheads:
-            statesheads.setdefault(state, set([])).add(nd)
-
-    for state, heads in repo._statesheads.iteritems():
-        if not state.trackheads:
-            continue
-        heads = set(heads) - tostrip | statesheads.get(state, set([]))
-        # reduce heads (make them really heads)
-        revs = set(map(cl.rev, heads))
-        minrev = min(revs)
-        for rev in cl.ancestors(*revs):
-            if rev >= minrev:
-                revs.discard(rev)
-        repo._statesheads[state] = map(cl.node, revs)
-    _writestateshead(repo)
-
-    return orig(ui, repo, node, backup)
-
-
-def uisetup(ui):
-    """
-    * patch stuff for the _NOSHARE property
-    * add template keyword
-    """
-    # patch discovery
-    extensions.wrapfunction(discovery, 'findcommonoutgoing', filterprivateout)
-    extensions.wrapfunction(discovery, 'findcommonincoming', filterprivatein)
-    extensions.wrapfunction(repair, 'strip', strip)
-
-    # patch wireprotocol
-    wireproto.commands['heads'] = (wireheads, '')
-
-    # add template keyword
-    templatekw.keywords['state'] = showstate
-
-def extsetup(ui):
-    """Extension setup
-
-    * add revset entry"""
-    for state in STATES:
-        if state.trackheads:
-            revset.symbols[state.headssymbol] = state._revsetheads
-    # wrap rebase
-    try:
-        rebase = extensions.find('rebase')
-        if rebase:
-            extensions.wrapfunction(rebase, 'buildstate', wraprebasebuildstate)
-    except KeyError:
-        pass # rebase not found
-    # wrap mq
-    try:
-        mq = extensions.find('mq')
-        if mq:
-            extensions.wrapfunction(mq.queue, 'qimport', wrapmqqimport)
-    except KeyError:
-        pass # mq not found
-
-
-
-def reposetup(ui, repo):
-    """Repository setup
-
-    * extend repo class with states logic"""
-
-    if not repo.local():
-        return
-
-    ocancopy =repo.cancopy
-    opull = repo.pull
-    opush = repo.push
-    o_tag = repo._tag
-    orollback = repo.rollback
-    o_writejournal = repo._writejournal
-    class statefulrepo(repo.__class__):
-        """An extension of repo class that handle state logic
-
-        - nodestate
-        - stateheads
-        """
-
-        def nodestate(self, node):
-            """return the state object associated to the given node"""
-            rev = self.changelog.rev(node)
-            for state in STATES:
-                # avoid for untracked heads
-                if state.next is not None:
-                    ancestors = map(self.changelog.rev, self.stateheads(state))
-                    ancestors.extend(self.changelog.ancestors(*ancestors))
-                    if rev in ancestors:
-                        break
-            return state
-
-        def enablestate(self, state, fix_lower=True):
-            if fix_lower:
-                # at least published which is always activated
-                lower = max(st for st in self._enabledstates if st < state)
-                self.setstate(lower, self.stateheads(state))
-            self._enabledstates.add(state)
-
-        def disablestate(self, state):
-            """Disable empty state.
-            Raise error.Abort if the state is not empty.
-            """
-            # the lowest is mandatory
-            if state == ST0:
-                raise error.Abort(_('could not disable %s' % state.name))
-            enabled =  self._enabledstates
-            # look up for lower state that is enabled (at least published)
-            lower = max(st for st in self._enabledstates if st < state)
-            if repo.stateheads(state) != repo.stateheads(lower):
-                raise error.Abort(
-                    _('could not disable non empty state %s' % state.name),
-                    hint=_("You may want to use `hg %s '%sheads()'`"
-                            % (lower.name, state.name))
-                    )
-            else:
-                enabled.remove(state)
-
-        def stateheads(self, state):
-            """Return the set of head that define the state"""
-            # look for a relevant state
-            while state.trackheads and state.next not in self._enabledstates:
-                state = state.next
-            # last state have no cached head.
-            if state.trackheads:
-                return self._statesheads[state]
-            return self.heads()
-
-        @util.propertycache
-        def _statesheads(self):
-            """{ state-object -> set(defining head)} mapping"""
-            return _readstatesheads(self)
-
-        def setstate_unsafe(self, state, changesets):
-            """Change state of targets changesets and it's ancestors.
-
-            Simplify the list of heads.
-
-            Unlike ``setstate``, the "lower" states are also changed
-            """
-            #modify "lower" states
-            req_nodes_rst = '|'.join('((%s)::)' % rst for rst in changesets)
-            for st in STATES:
-                if st >= state: # only modify lower state heads for now
-                    continue
-                try:
-                    heads = self._statesheads[st]
-                except KeyError: # forget non-activated states
-                    continue
-                olds = heads[:]
-                rst = "heads((::%s()) - (%s))" % (st.headssymbol, req_nodes_rst)
-                heads[:] = noderange(repo, [rst])
-                if olds != heads:
-                    _writestateshead(self)
-            #modify the state
-            if state in self._statesheads:
-                revs = scmutil.revrange(repo, changesets)
-                repo.setstate(state, [repo.changelog.node(rev) for rev in revs])
-
-        def setstate(self, state, nodes):
-            """change state of targets changeset and it's ancestors.
-
-            Simplify the list of head."""
-            assert not isinstance(nodes, basestring), repr(nodes)
-            if not state.trackheads:
-                return
-            heads = self._statesheads[state]
-            olds = heads[:]
-            heads.extend(nodes)
-            heads[:] = set(heads)
-            heads.sort()
-            if olds != heads:
-                heads[:] = noderange(repo, ["heads(::%s())" % state.headssymbol])
-                heads.sort()
-            if olds != heads:
-                _writestateshead(self)
-            if state.next is not None and state.next.trackheads:
-                self.setstate(state.next, nodes) # cascading
-
-        def _reducehead(self, candidates):
-            """recompute a set of heads so it doesn't include _NOSHARE changeset
-
-            This is basically a complicated method that compute
-            heads(::candidates - _NOSHARE)
-            """
-            selected = set()
-            st = laststatewithout(_NOSHARE)
-            candidates = set(map(self.changelog.rev, candidates))
-            heads = set(map(self.changelog.rev, self.stateheads(st)))
-            shareable = set(self.changelog.ancestors(*heads))
-            shareable.update(heads)
-            selected = candidates & shareable
-            unselected = candidates - shareable
-            for rev in unselected:
-                for revh in heads:
-                    if self.changelog.descendant(revh, rev):
-                        selected.add(revh)
-            return sorted(map(self.changelog.node, selected))
-
-        ### enable // disable logic
-
-        @util.propertycache
-        def _enabledstates(self):
-            """The set of state enabled in this repository"""
-            return self._readenabledstates()
-
-        def _readenabledstates(self):
-            """read enabled state from disk"""
-            states = set()
-            states.add(ST0)
-            mapping = dict([(st.name, st) for st in STATES])
-            try:
-                f = self.opener('states/Enabled')
-                for line in f:
-                    st =  mapping.get(line.strip())
-                    if st is not None:
-                        states.add(st)
-            finally:
-                return states
-
-        def _writeenabledstates(self):
-            """read enabled state to disk"""
-            f = self.opener('states/Enabled', 'w', atomictemp=True)
-            try:
-                for st in self._enabledstates:
-                    f.write(st.name + '\n')
-                try:
-                    f.rename()
-                except AttributeError: # old version
-                    f.close()
-            finally:
-                f.close()
-
-        ### local clone support
-
-        def cancopy(self):
-            """deny copy if there is _NOSHARE changeset"""
-            st = laststatewithout(_NOSHARE)
-            return ocancopy() and (self.stateheads(st) == self.heads())
-
-        ### pull // push support
-
-        def pull(self, remote, *args, **kwargs):
-            """altered pull that also update states heads on local repo"""
-            result = opull(remote, *args, **kwargs)
-            remoteheads = self._pullstatesheads(remote)
-            for st, heads in remoteheads.iteritems():
-                self.setstate(st, heads)
-            return result
-
-        def push(self, remote, *args, **opts):
-            """altered push that also update states heads on local and remote"""
-            result = opush(remote, *args, **opts)
-            if not self.ui.configbool('states', 'bypass', False):
-                remoteheads = self._pullstatesheads(remote)
-                for st, heads in remoteheads.iteritems():
-                    self.setstate(st, heads)
-                    if heads != self.stateheads(st):
-                        self._pushstatesheads(remote, st,  heads)
-            return result
-
-        def _pushstatesheads(self, remote, state, remoteheads):
-            """push head of a given state for remote
-
-            This handle pushing boundary that does exist on remote host
-            This is done a very naive way"""
-            local = set(self.stateheads(state))
-            missing = local - set(remoteheads)
-            while missing:
-                h = missing.pop()
-                ok = remote.pushkey('states-heads', node.hex(h), '', state.name)
-                if not ok:
-                    missing.update(p.node() for p in repo[h].parents())
-
-
-        def _pullstatesheads(self, remote):
-            """pull all remote states boundary locally
-
-            This can only make the boundary move on a newer changeset"""
-            remoteheads = {}
-            self.ui.debug('checking for states-heads on remote server')
-            if 'states-heads' not in remote.listkeys('namespaces'):
-                self.ui.debug('states-heads not enabled on the remote server, '
-                              'marking everything as published\n')
-                remoteheads[ST0] = remote.heads()
-            else:
-                self.ui.debug('server has states-heads enabled, merging lists')
-                for hex, statenames in  remote.listkeys('states-heads').iteritems():
-                    for stn in statenames.split(','):
-                        remoteheads.setdefault(STATESMAP[stn], []).append(node.bin(hex))
-            return remoteheads
-
-        ### Tag support
-
-        def _tag(self, names, node, *args, **kwargs):
-            """Altered version of _tag that make tag (and tagging) published"""
-            tagnode = o_tag(names, node, *args, **kwargs)
-            if tagnode is not None: # do nothing for local one
-                self.setstate(ST0, [node, tagnode])
-            return tagnode
-
-        ### rollback support
-
-        def _writejournal(self, desc):
-            """extended _writejournal that also save states"""
-            entries = list(o_writejournal(desc))
-            for state in STATES:
-                if state.trackheads:
-                    filename = 'states/%s-heads' % state.name
-                    filepath = self.join(filename)
-                    if  os.path.exists(filepath):
-                        journalname = 'states/journal.%s-heads' % state.name
-                        journalpath = self.join(journalname)
-                        util.copyfile(filepath, journalpath)
-                        entries.append(journalpath)
-            return tuple(entries)
-
-        def rollback(self, dryrun=False):
-            """extended rollback that also restore states"""
-            wlock = lock = None
-            try:
-                wlock = self.wlock()
-                lock = self.lock()
-                ret = orollback(dryrun)
-                if not (ret or dryrun): #rollback did not failed
-                    for state in STATES:
-                        if state.trackheads:
-                            src  = self.join('states/undo.%s-heads') % state.name
-                            dest = self.join('states/%s-heads') % state.name
-                            if os.path.exists(src):
-                                util.rename(src, dest)
-                            elif os.path.exists(dest): #unlink in any case
-                                os.unlink(dest)
-                    self.__dict__.pop('_statesheads', None)
-                return ret
-            finally:
-                release(lock, wlock)
-
-    repo.__class__ = statefulrepo
-
--- a/tests/test-draft.t	Mon Dec 19 12:19:00 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,217 +0,0 @@
-  $ cat >> $HGRCPATH <<EOF
-  > [web]
-  > push_ssl = false
-  > allow_push = *
-  > [extensions]
-  > EOF
-  $ echo "states=$(echo $(dirname $TESTDIR))/hgext/states.py" >> $HGRCPATH
-
-  $ hg init local
-  $ hg init remote1
-  $ hg init remote2
-  $ cd local
-  $ echo "celestine" > babar
-  $ hg add babar
-  $ hg ci -m "add babar"
-  $ echo "la veille dame" > babar
-  $ hg ci -m "add dame"
-  $ hg log --template='{rev}:{node|short}: {state}\n'
-  1:710fe444b3b0: published
-  0:5caa672bac26: published
-  $ hg out  ../remote1 --template='{rev}:{node|short}\n'
-  comparing with ../remote1
-  searching for changes
-  0:5caa672bac26
-  1:710fe444b3b0
-  $ hg push ../remote1
-  pushing to ../remote1
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 2 changesets with 2 changes to 1 files
-  $ echo "tree" >> savanna
-  $ hg add savanna
-  $ hg ci -m "terrain"
-  $ echo "flore" >> babar
-  $ hg ci -m "children"
-  $ hg log --template='{rev}:{node|short}: {state}\n'
-  3:73585b17392a: published
-  2:3c8695235a32: published
-  1:710fe444b3b0: published
-  0:5caa672bac26: published
-
-turn draft on (repo side)
-  $ hg states --clever draft
-  $ hg log --template='{rev}:{node|short}: {state}\n'
-  3:73585b17392a: draft
-  2:3c8695235a32: draft
-  1:710fe444b3b0: published
-  0:5caa672bac26: published
-
-test outgoing and push
-  $ hg out  ../remote1 --template='{rev}:{node|short}\n'
-  comparing with ../remote1
-  searching for changes
-  no changes found
-  [1]
-  $ hg push --traceback  ../remote1
-  pushing to ../remote1
-  searching for changes
-  no changes found
-
-  $ hg out  ../remote2 --template='{rev}:{node|short}\n'
-  comparing with ../remote2
-  searching for changes
-  0:5caa672bac26
-  1:710fe444b3b0
-  $ hg push  ../remote2
-  pushing to ../remote2
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 2 changesets with 2 changes to 1 files
-
-turn draft off again (repo side)
-  $ hg states --off draft
-  abort: could not disable non empty state draft
-  (You may want to use `hg published 'draftheads()'`)
-  [255]
-  $ hg published tip
-  $ hg log --template='{rev}:{node|short}: {state}\n'
-  3:73585b17392a: published
-  2:3c8695235a32: published
-  1:710fe444b3b0: published
-  0:5caa672bac26: published
-  $ hg out  ../remote1 --template='{rev}:{node|short}\n'
-  comparing with ../remote1
-  searching for changes
-  2:3c8695235a32
-  3:73585b17392a
-
-turn draft on again (repo side)
-  $ hg states draft
-  $ hg draft --exact 2
-
-test incoming and pull
-
-  $ hg init ../other1
-  $ cd ../other1
-  $ hg incoming ../local --template='{rev}:{node|short}\n'
-  comparing with ../local
-  0:5caa672bac26
-  1:710fe444b3b0
-  $ hg pull ../local
-  pulling from ../local
-  requesting all changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 2 changesets with 2 changes to 1 files
-  (run 'hg update' to get a working copy)
-  $ hg log --template='{rev}:{node|short}\n'
-  1:710fe444b3b0
-  0:5caa672bac26
-  $ cd ..
-  $ hg clone local other2
-  requesting all changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 2 changesets with 2 changes to 1 files
-  updating to branch default
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg -R other2 log --template='{rev}:{node|short}\n'
-  1:710fe444b3b0
-  0:5caa672bac26
-
-test on http
-
-  $ hg -R local serve -p $HGPORT -d --pid-file=local.pid
-  $ cat local.pid >> "$DAEMON_PIDS"
-  $ hg clone http://localhost:$HGPORT/ fromhttp
-  requesting all changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 2 changesets with 2 changes to 1 files
-  updating to branch default
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg -R fromhttp log --template='{rev}:{node|short}\n'
-  1:710fe444b3b0
-  0:5caa672bac26
-
-  $ hg init fromhttp2
-  $ cd fromhttp2
-  $ hg inc http://localhost:$HGPORT/ --template='{rev}:{node|short}\n'
-  comparing with http://localhost:$HGPORT/
-  0:5caa672bac26
-  1:710fe444b3b0
-  $ hg pull http://localhost:$HGPORT/
-  pulling from http://localhost:$HGPORT/
-  requesting all changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 2 changesets with 2 changes to 1 files
-  (run 'hg update' to get a working copy)
-  $ hg log --template='{rev}:{node|short}\n'
-  1:710fe444b3b0
-  0:5caa672bac26
-  $ hg inc http://localhost:$HGPORT/ --template='{rev}:{node|short}\n'
-  comparing with http://localhost:$HGPORT/
-  searching for changes
-  no changes found
-  [1]
-
-turn draft off again (repo side)
-  $ cd ..
-  $ "$TESTDIR/killdaemons.py"
-  $ hg -R local states --off draft
-  abort: could not disable non empty state draft
-  (You may want to use `hg published 'draftheads()'`)
-  [255]
-  $ hg -R local published tip
-  $ hg -R local states --off draft
-  $ hg -R local serve -p $HGPORT -d --pid-file=local.pid
-  $ cat local.pid >> "$DAEMON_PIDS"
-  $ cd fromhttp2
-
-  $ hg inc http://localhost:$HGPORT/ --template='{rev}:{node|short}\n'
-  comparing with http://localhost:$HGPORT/
-  searching for changes
-  2:3c8695235a32
-  3:73585b17392a
-  $ hg pull http://localhost:$HGPORT/
-  pulling from http://localhost:$HGPORT/
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 2 changesets with 2 changes to 2 files
-  (run 'hg update' to get a working copy)
-  $ cd ..
-  $ "$TESTDIR/killdaemons.py"
-
-turn draft on again (repo side)
-  $ hg -R local states draft
-  $ hg init httpto
-  $ hg -R httpto serve -p $HGPORT -d --pid-file=remote.pid
-  $ cat remote.pid >> "$DAEMON_PIDS"
-  $ cd local
-  $ hg out http://localhost:$HGPORT/  --template='{rev}:{node|short}\n'
-  comparing with http://localhost:$HGPORT/
-  searching for changes
-  0:5caa672bac26
-  1:710fe444b3b0
-  2:3c8695235a32
-  3:73585b17392a
-  $ hg push http://localhost:$HGPORT/
-  pushing to http://localhost:$HGPORT/
-  searching for changes
-  remote: adding changesets
-  remote: adding manifests
-  remote: adding file changes
-  remote: added 4 changesets with 4 changes to 2 files
-  $ "$TESTDIR/killdaemons.py"
--- a/tests/test-ready.t	Mon Dec 19 12:19:00 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,186 +0,0 @@
-  $ cat >> $HGRCPATH <<EOF
-  > [liquid]
-  > publish = False
-  > [extensions]
-  > graphlog=
-  > EOF
-  $ echo "states=$(echo $(dirname $TESTDIR))/hgext/states.py" >> $HGRCPATH
-
-  $ mkcommit() {
-  >    echo "$1" > "$1"
-  >    hg add "$1"
-  >    hg ci -m "add $1"
-  > }
-
-  $ hg init alpha
-  $ cd alpha
-  $ hg states ready
-  $ mkcommit z
-  $ mkcommit a
-  $ mkcommit b
-  $ mkcommit c
-  $ hg published 1
-  $ hg log --graph
-  @  changeset:   3:090483935bca
-  |  tag:         tip
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add c
-  |
-  o  changeset:   2:720fd97246d7
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add b
-  |
-  o  changeset:   1:7a344d213ee2
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add a
-  |
-  o  changeset:   0:d32fd17cb041
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     add z
-  
-  $ cat .hg/states/published-heads
-  7a344d213ee2eb3359d94630d4e076460d59dbf0
-
-publishedheads() should return only revision 1:
-  $ hg log -r 'publishedheads()' --graph
-  o  changeset:   1:7a344d213ee2
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add a
-  |
-
-ancestors of publishedheads shows all frozen revisions:
-  $ hg log -r '::publishedheads()' --graph
-  o  changeset:   1:7a344d213ee2
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add a
-  |
-  o  changeset:   0:d32fd17cb041
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     add z
-  
-  $ cd ..
-  $ hg init beta
-  $ cd beta
-  $ hg states ready
-  $ hg pull ../alpha --update
-  pulling from ../alpha
-  requesting all changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 4 changesets with 4 changes to 4 files
-  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg log --graph -r 'publishedheads()'
-  o  changeset:   1:7a344d213ee2
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add a
-  |
-
-Freeze in beta and push to alpha:
-  $ hg published 3
-  $ mkcommit d
-  $ hg push --traceback ../alpha
-  pushing to ../alpha
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 1 changesets with 1 changes to 1 files
-  $ cd ../alpha
-  $ hg debugrevspec 'publishedheads()'
-  3
-  $ hg log --graph -r 'publishedheads()'
-  @  changeset:   3:090483935bca
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add c
-  |
-
-  $ hg log -r tip
-  changeset:   4:fb98f3f5bba0
-  tag:         tip
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add d
-  
-  $ mkcommit e
-  created new head
-  $ hg up -C 4
-  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ hg log -r 'publishedheads()'
-  changeset:   3:090483935bca
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add c
-  
-  $ hg tag -fr tip babar
-  $ hg log -r 'publishedheads()'
-  changeset:   5:cdaaa31e4239
-  tag:         babar
-  parent:      3:090483935bca
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add e
-  
-  changeset:   6:bd66bf1525ee
-  tag:         tip
-  parent:      4:fb98f3f5bba0
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     Added tag babar for changeset cdaaa31e4239
-  
-Check rollback
-
-  $ hg merge
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
-  $ hg commit -m 'merge'
-  $ hg published tip
-  $ hg log -r 'publishedheads()'
-  changeset:   7:26631d82e09e
-  tag:         tip
-  parent:      6:bd66bf1525ee
-  parent:      5:cdaaa31e4239
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     merge
-  
-test rollback does not mess up the liquid changeset
-  $ mkcommit bibi
-  $ hg rollback
-  repository tip rolled back to revision 7 (undo commit)
-  working directory now based on revision 7
-  $ hg log -r 'publishedheads()'
-  changeset:   7:26631d82e09e
-  tag:         tip
-  parent:      6:bd66bf1525ee
-  parent:      5:cdaaa31e4239
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     merge
-  
-
-test rollback even when rollbacking frozen
-
-  $ mkcommit bubu
-  $ hg published tip
-  $ hg rollback
-  repository tip rolled back to revision 7 (undo commit)
-  working directory now based on revision 7
-  $ hg log -r 'publishedheads()'
-  changeset:   7:26631d82e09e
-  tag:         tip
-  parent:      6:bd66bf1525ee
-  parent:      5:cdaaa31e4239
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     merge
-  
--- a/tests/test-state-strip.t	Mon Dec 19 12:19:00 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,288 +0,0 @@
-
-
-  $ cat >> $HGRCPATH <<EOF
-  > [web]
-  > push_ssl = false
-  > allow_push = *
-  > [extensions]
-  > hgext.mq=
-  > hgext.graphlog=
-  > EOF
-  $ echo "states=$(echo $(dirname $TESTDIR))/hgext/states.py" >> $HGRCPATH
-
-  $ mkcommit() {
-  >    echo "$1" > "$1"
-  >    hg add "$1"
-  >    hg ci -m "$1"
-  > }
-  $ alias hglog='hg glog --template "{desc} {state} {node}\n"'
-
-  $ hg init alpha
-  $ cd alpha
-  $ hg states draft ready
-  $ mkcommit 0
-  $ mkcommit 1
-  $ hg up 0
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ mkcommit 2
-  created new head
-  $ mkcommit 3
-  $ mkcommit 4
-  $ hg up 3
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ mkcommit 5
-  created new head
-  $ hg up 1
-  1 files updated, 0 files merged, 3 files removed, 0 files unresolved
-  $ mkcommit 6
-  $ hg published 6
-  $ hg ready 4
-  $ hglog
-  @  6 published 2a653cad66937648173a936140f09a0e780afd76
-  |
-  | o  5 draft ffe7eb8acef3efeceaa566b85a1ac419b0ecb856
-  | |
-  | | o  4 ready 138777f75ddeb6ee0b527cfdb0eebbd1e0037bf6
-  | |/
-  | o  3 ready 0915e256b0ca7f81dace67bc6fd512bfd1bcab85
-  | |
-  | o  2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88
-  | |
-  o |  1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77
-  |/
-  o  0 published 06254b90631198c9b9e426be9886af92fedc9a2d
-  
-
-We strip a published heads, so published heads 6 -> 1
-  $ hg strip -n 6
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ hglog
-  o  5 draft ffe7eb8acef3efeceaa566b85a1ac419b0ecb856
-  |
-  | o  4 ready 138777f75ddeb6ee0b527cfdb0eebbd1e0037bf6
-  |/
-  o  3 ready 0915e256b0ca7f81dace67bc6fd512bfd1bcab85
-  |
-  o  2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88
-  |
-  | @  1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77
-  |/
-  o  0 published 06254b90631198c9b9e426be9886af92fedc9a2d
-  
-
-Back to the previous configuration.
-Then strip accros branches and remove draft changesets completly, and cut in
-the middle of ready changesets
-  $ hg up 1
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ mkcommit 6
-  $ hg strip -n 3:6
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ hglog
-  o  2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88
-  |
-  | @  1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77
-  |/
-  o  0 published 06254b90631198c9b9e426be9886af92fedc9a2d
-  
-Now merge 1 & 2 then strip merging changeset
-  $ hg up 1
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg merge 2
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
-  $ hg commit -m Merge
-  $ hg strip -n 3
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ hglog
-  o  2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88
-  |
-  | @  1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77
-  |/
-  o  0 published 06254b90631198c9b9e426be9886af92fedc9a2d
-  
-Do the same but with merge changeset as ready
-  $ hg up 1
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg merge 2
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
-  $ hg commit -m Merge
-  $ hg ready 3
-  $ hg strip -n 3
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ hglog
-  o  2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88
-  |
-  | @  1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77
-  |/
-  o  0 published 06254b90631198c9b9e426be9886af92fedc9a2d
-  
-  $ hg log --template "{rev} {node}\n" -r 'readyheads()'
-  2 a00ba83de58390cbbdae1fc580df0bb0db2e8e88
-
-More complecated case: a merging changeset inheritate a ready state of one of
-its child and have another child in draft. And We strip the ready child
-  $ hg up 1
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg merge 2
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
-  $ hg ci -m 3
-  $ mkcommit 4
-  $ hg up 3
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ mkcommit 5
-  created new head
-  $ mkcommit 6
-  $ hg ready 6
-  $ hglog
-  @  6 ready bfd1096b3dd69e57c184e9f43646a9b7e0dd5927
-  |
-  o  5 ready 8195da2a3c382a4acd7ce796b4bc74092f1875eb
-  |
-  | o  4 draft 6aaadc67da669af964adabe681c0a78f46b7ce58
-  |/
-  o    3 ready e7cd12398be70c568cefab9b4ad86a8a2a728a09
-  |\
-  | o  2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88
-  | |
-  o |  1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77
-  |/
-  o  0 published 06254b90631198c9b9e426be9886af92fedc9a2d
-  
-  $ hg strip -n 3
-  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
-  $ hglog
-  o  2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88
-  |
-  | @  1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77
-  |/
-  o  0 published 06254b90631198c9b9e426be9886af92fedc9a2d
-  
-Quitelly the same as before but we strip before the merging node, from another
-branch than the ready changeset, so this changeset is not a parent of the
-the explicitly stripped node.
-  $ hg up 1
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ mkcommit 3
-  $ hg up 2
-  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ hg merge 3
-  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
-  $ hg ci -m '4'
-  $ mkcommit 5
-  $ hg published 3
-  $ hg ready 4
-  $ hglog
-  @  5 draft 0777a3135ec5396c57db4402c71ab8cba2a0ef7e
-  |
-  o    4 ready 667667458ecc8cf7763dee1ae172a5a9ebf115f3
-  |\
-  | o  3 published 03fc50a1c0074093104ff6c5357c486781742b64
-  | |
-  o |  2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88
-  | |
-  | o  1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77
-  |/
-  o  0 published 06254b90631198c9b9e426be9886af92fedc9a2d
-  
-  $ hg strip -n 3
-  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
-  $ hglog
-  o  2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88
-  |
-  | @  1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77
-  |/
-  o  0 published 06254b90631198c9b9e426be9886af92fedc9a2d
-  
-
-pathologic case
-  $ hg up 1
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ mkcommit 3
-  $ hg up 2
-  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ hg merge 3
-  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
-  $ hg ci -m '4'
-  $ mkcommit 5
-  $ hg up 3
-  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ mkcommit 6
-  created new head
-  $ hg published 3
-  $ hg ready 4
-  $ hglog
-  @  6 draft aeb74c71311d9305498bbf371746d095b80ff51f
-  |
-  | o  5 draft 0777a3135ec5396c57db4402c71ab8cba2a0ef7e
-  | |
-  | o  4 ready 667667458ecc8cf7763dee1ae172a5a9ebf115f3
-  |/|
-  o |  3 published 03fc50a1c0074093104ff6c5357c486781742b64
-  | |
-  | o  2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88
-  | |
-  o |  1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77
-  |/
-  o  0 published 06254b90631198c9b9e426be9886af92fedc9a2d
-  
-  $ hg strip -n 3
-  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ hglog
-  o  2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88
-  |
-  | @  1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77
-  |/
-  o  0 published 06254b90631198c9b9e426be9886af92fedc9a2d
-  
-pathologic case
-  $ hg up 1
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg merge 2
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
-  $ hg ci -m 3
-  $ hg up 2
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ mkcommit 4
-  created new head
-  $ hg merge 3
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
-  $ hg ci -m 5
-  $ hg up 4
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ mkcommit 6
-  created new head
-  $ hg ready 4
-  $ hglog
-  @  6 draft 036d507f2b771a3b7cc88580c93d5037bf4bf1bf
-  |
-  | o  5 draft 19788060dab104e9385a14c4be2fc5678b9433f0
-  |/|
-  o |  4 ready 0fc8455e844047eab375a1f51816f697551e34cf
-  | |
-  | o  3 draft e7cd12398be70c568cefab9b4ad86a8a2a728a09
-  |/|
-  o |  2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88
-  | |
-  | o  1 published e8342c9a2ed18fb27f9fdfc48a3105d164a06e77
-  |/
-  o  0 published 06254b90631198c9b9e426be9886af92fedc9a2d
-  
-  $ hg strip -n 1
-  $ hglog
-  @  6 draft 036d507f2b771a3b7cc88580c93d5037bf4bf1bf
-  |
-  o  4 ready 0fc8455e844047eab375a1f51816f697551e34cf
-  |
-  o  2 ready a00ba83de58390cbbdae1fc580df0bb0db2e8e88
-  |
-  o  0 published 06254b90631198c9b9e426be9886af92fedc9a2d
-  
-  $ hg log --template '{desc} {state} {node}\n' -r 'readyheads()'
-  4 ready 0fc8455e844047eab375a1f51816f697551e34cf
--- a/tests/test-states-enable.t	Mon Dec 19 12:19:00 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,199 +0,0 @@
-
-  $ cat >> $HGRCPATH <<EOF
-  > [web]
-  > push_ssl = false
-  > allow_push = *
-  > [extensions]
-  > EOF
-  $ echo "states=$(echo $(dirname $TESTDIR))/hgext/states.py" >> $HGRCPATH
-
-  $ mkcommit() {
-  >    echo "$1" > "$1"
-  >    hg add "$1"
-  >    hg ci -m "$1"
-  > }
-  $ alias hglog='hg log --template "{rev} {state}\n"'
-
-  $ hg init alpha
-  $ cd alpha
-  $ mkcommit 0
-  $ mkcommit 1
-
-
-enable draft: existing changesets stay as published and newer are draft
-  $ hg states draft
-  $ hg states
-  published
-  draft
-  $ hglog
-  1 published
-  0 published
-  $ mkcommit 2
-  $ hglog
-  2 draft
-  1 published
-  0 published
-
-enable ready: existing changset states are the same, newer are draft
-  $ hg states ready
-  $ hg states
-  published
-  ready
-  draft
-  $ hglog
-  2 draft
-  1 published
-  0 published
-  $ mkcommit 3
-  $ hglog
-  3 draft
-  2 draft
-  1 published
-  0 published
-
-
-publish all then enable states in other order
-  $ hg published tip
-  $ hg states --off ready draft
-  $ hglog
-  3 published
-  2 published
-  1 published
-  0 published
-
-enable ready: changesets stay as published and newer are ready
-  $ hg states ready
-  $ hglog
-  3 published
-  2 published
-  1 published
-  0 published
-  $ mkcommit 4
-  $ hglog
-  4 ready
-  3 published
-  2 published
-  1 published
-  0 published
-
-enable draft: changesets stay unchanged and newer are draft
-  $ hg states draft
-  $ hglog
-  4 ready
-  3 published
-  2 published
-  1 published
-  0 published
-  $ mkcommit 5
-  $ hglog
-  5 draft
-  4 ready
-  3 published
-  2 published
-  1 published
-  0 published
-
-disable ready
-  $ hg states --off ready
-  abort: could not disable non empty state ready
-  (You may want to use `hg published 'readyheads()'`)
-  [255]
-  $ hg publish 4
-  $ hg states --off ready
-  $ hg states
-  published
-  draft
-  $ hglog
-  5 draft
-  4 published
-  3 published
-  2 published
-  1 published
-  0 published
-  $ hg ready 4
-  abort: state ready is not activated
-  (try ``hg states ready`` before)
-  [255]
-
-disable draft
-  $ hg states --off draft
-  abort: could not disable non empty state draft
-  (You may want to use `hg published 'draftheads()'`)
-  [255]
-  $ hg publish tip
-  $ hg states --off draft
-  $ hg states
-  published
-  $ hglog
-  5 published
-  4 published
-  3 published
-  2 published
-  1 published
-  0 published
-  $ hg draft 5
-  abort: state draft is not activated
-  (try ``hg states draft`` before)
-  [255]
-
-disable published
-  $ hg states --off published
-  abort: could not disable published
-  [255]
-
-
-enable both draft and ready
-  $ hg states draft ready
-  $ hg states
-  published
-  ready
-  draft
-  $ hglog
-  5 published
-  4 published
-  3 published
-  2 published
-  1 published
-  0 published
-  $ mkcommit 6
-  $ hglog
-  6 draft
-  5 published
-  4 published
-  3 published
-  2 published
-  1 published
-  0 published
-
-disable both draft and ready
-  $ hg published tip
-  $ hg states --off draft ready
-  $ hg states
-  published
-
-clever enabling
-  $ hg states --clever ready
-  $ hglog
-  6 published
-  5 published
-  4 published
-  3 published
-  2 published
-  1 published
-  0 published
-
-  $ cd ..
-  $ hg init beta
-  $ cd beta
-  $ mkcommit 0
-  $ mkcommit 1
-  $ hg states --clever ready
-  $ hglog
-  1 ready
-  0 ready
-  $ hg states --clever draft
-  $ hglog
-  1 draft
-  0 draft
-
-
--- a/tests/test-states-exact.t	Mon Dec 19 12:19:00 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,282 +0,0 @@
-  $ cat >> $HGRCPATH <<EOF
-  > [web]
-  > push_ssl = false
-  > allow_push = *
-  > [extensions]
-  > EOF
-  $ echo "states=$(echo $(dirname $TESTDIR))/hgext/states.py" >> $HGRCPATH
-
-  $ mkcommit() {
-  >    echo "$1" > "$1"
-  >    hg add "$1"
-  >    hg ci -m "$1"
-  > }
-  $ alias hglog='hg log --template "{desc|short} {rev} {state}\n"'
-
-  $ hg init alpha
-  $ cd alpha
-  $ hg states ready draft
-  $ mkcommit a
-  $ mkcommit b
-  $ mkcommit c
-  $ mkcommit d
-  $ mkcommit e
-  $ mkcommit f
-
-|0 - 1 - 2 - 3 - 4 - 5
-
-  $ hg ready 3
-  $ hg published 1
-  $ hglog
-  f 5 draft
-  e 4 draft
-  d 3 ready
-  c 2 ready
-  b 1 published
-  a 0 published
-
-very simple case
-  $ hg ready --exact 1
-  $ hglog
-  f 5 draft
-  e 4 draft
-  d 3 ready
-  c 2 ready
-  b 1 ready
-  a 0 published
-
-required state not in statesheads
-  $ hg draft --exact 3
-  $ hglog
-  f 5 draft
-  e 4 draft
-  d 3 draft
-  c 2 ready
-  b 1 ready
-  a 0 published
-
-remove all other states
-  $ hg draft --exact 0
-  $ hglog
-  f 5 draft
-  e 4 draft
-  d 3 draft
-  c 2 draft
-  b 1 draft
-  a 0 draft
-
-draft was not there before
-  $ hg ready 5
-  $ hg draft --exact 4
-  $ hglog
-  f 5 draft
-  e 4 draft
-  d 3 ready
-  c 2 ready
-  b 1 ready
-  a 0 ready
-
-already in the required state
-  $ hg draft --exact 5
-  $ hglog
-  f 5 draft
-  e 4 draft
-  d 3 ready
-  c 2 ready
-  b 1 ready
-  a 0 ready
-
-backward and foreward
-  $ hg published 1
-  $ hg ready --exact 1:4
-  $ hglog
-  f 5 draft
-  e 4 ready
-  d 3 ready
-  c 2 ready
-  b 1 ready
-  a 0 published
-
-with complex revset
-  $ hg draft --exact 'readyheads()'
-  $ hglog
-  f 5 draft
-  e 4 draft
-  d 3 ready
-  c 2 ready
-  b 1 ready
-  a 0 published
-  $ hg ready --exact 'publishedheads()'
-  $ hglog
-  f 5 draft
-  e 4 draft
-  d 3 ready
-  c 2 ready
-  b 1 ready
-  a 0 ready
-
-Work with branches now
-  $ hg up 1
-  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
-  $ mkcommit g
-  created new head
-  $ mkcommit h
-  $ hg up 3
-  2 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ mkcommit i
-  created new head
-  $ mkcommit j
-
-|              / - - - - 6 - 7
-|0 - 1 - 2 - 3 - 4 - 5
-|      \ - - - - - - - - - - - - 8 - 9
-
-Set the new branches as ready
-  $ hg ready 9 7
-  $ hglog
-  j 9 ready
-  i 8 ready
-  h 7 ready
-  g 6 ready
-  f 5 draft
-  e 4 draft
-  d 3 ready
-  c 2 ready
-  b 1 ready
-  a 0 ready
-
-with composite revset
-  $ hg draft --exact 5:5 7:8
-  $ hglog
-  j 9 draft
-  i 8 draft
-  h 7 draft
-  g 6 ready
-  f 5 draft
-  e 4 draft
-  d 3 ready
-  c 2 ready
-  b 1 ready
-  a 0 ready
-
-cross braches
-  $ hg published 5 7 9
-  $ hg draft --exact 4:6
-  $ hglog
-  j 9 published
-  i 8 published
-  h 7 draft
-  g 6 draft
-  f 5 draft
-  e 4 draft
-  d 3 published
-  c 2 published
-  b 1 published
-  a 0 published
-
-Ok more complicated stuffs
-  $ hg up 7
-  2 files updated, 0 files merged, 4 files removed, 0 files unresolved
-  $ mkcommit k
-  $ hg published 5 9 10
-
-|              / - - - - 6 - 7 - - - - - 10
-|0 - 1 - 2 - 3 - 4 - 5
-|      \ - - - - - - - - - - - - 8 - 9
-
-cross branches and propagation on the same branche
-  $ hg draft --exact 5:8
-  $ hglog
-  k 10 draft
-  j 9 draft
-  i 8 draft
-  h 7 draft
-  g 6 draft
-  f 5 draft
-  e 4 published
-  d 3 published
-  c 2 published
-  b 1 published
-  a 0 published
-
-
-cross branches and propagation on multiple branches
-  $ hg published 10
-  $ hg ready --exact 3 6:8
-  $ hglog
-  k 10 ready
-  j 9 draft
-  i 8 ready
-  h 7 ready
-  g 6 ready
-  f 5 draft
-  e 4 ready
-  d 3 ready
-  c 2 published
-  b 1 published
-  a 0 published
-
-propagate ready on multiple branches taht contains draft states
-  $ hg ready --exact 1
-  $ hglog
-  k 10 ready
-  j 9 draft
-  i 8 ready
-  h 7 ready
-  g 6 ready
-  f 5 draft
-  e 4 ready
-  d 3 ready
-  c 2 ready
-  b 1 ready
-  a 0 published
-
-brute propagation
-  $ hg draft --exact 0
-  $ hglog
-  k 10 draft
-  j 9 draft
-  i 8 draft
-  h 7 draft
-  g 6 draft
-  f 5 draft
-  e 4 draft
-  d 3 draft
-  c 2 draft
-  b 1 draft
-  a 0 draft
-
-forget non activated state
-  $ hg init beta
-  $ cd beta
-  $ hg states draft
-  $ mkcommit a
-  $ mkcommit b
-  $ mkcommit c
-  $ mkcommit d
-  $ mkcommit e
-  $ mkcommit f
-  $ hg published 5
-  $ hg draft --exact 3
-  $ hglog
-  f 5 draft
-  e 4 draft
-  d 3 draft
-  c 2 published
-  b 1 published
-  a 0 published
-  $ hg states
-  published
-  draft
-  $ hg ready 3
-  abort: state ready is not activated
-  (try ``hg states ready`` before)
-  [255]
-  $ hglog
-  f 5 draft
-  e 4 draft
-  d 3 draft
-  c 2 published
-  b 1 published
-  a 0 published
-
--- a/tests/test-states.t	Mon Dec 19 12:19:00 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-
-  $ cat >> $HGRCPATH <<EOF
-  > [web]
-  > push_ssl = false
-  > allow_push = *
-  > [extensions]
-  > EOF
-  $ echo "states=$(echo $(dirname $TESTDIR))/hgext/states.py" >> $HGRCPATH
-
-  $ hg init local
-  $ hg init other
-  $ cd local
-  $ hg states --traceback -v
-  published
-  $ hg states draft
-  $ hg states
-  published
-  draft
-  $ hg states ready
-  $ hg states
-  published
-  ready
-  draft
-  $ hg states --off draft
-  $ hg states
-  published
-  ready
-  $ hg states babar
-  no state named babar
-  [1]
-  $ echo 'babar' >> .hg/states/Enabled
-  $ hg states
-  published
-  ready
-  $ hg -R ../other states
-  published
-
-do nothing if state already deactivated
-  $ hg states --off draft
-  state draft already deactivated
-  $ hg states
-  published
-  ready