hgext/obsolete.py
changeset 491 6989d8fe4ed2
parent 490 8096833e9226
child 519 9825c7da5b54
--- a/hgext/obsolete.py	Thu Aug 23 18:21:18 2012 +0200
+++ b/hgext/obsolete.py	Fri Aug 24 10:44:23 2012 +0200
@@ -5,41 +5,13 @@
 #
 # 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. The relation
-``<changeset B> obsoletes <changeset A>`` denotes that ``<changeset B>``
-is a new version of ``<changeset A>``.
-
-The *obsolete* relations act as an history **orthogonal** to the regular
-changesets history. Regular changesets history versions files. *Obsolete*
-relations version changesets.
+"""Deprecated extension that formely introduces "Changeset Obsolescence".
 
-: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 descendants.
-:extinct:      an obsolete changeset without unstable descendants.
-               (subject to garbage collection)
-
-Another name for unstable could be out of sync.
-
+This concept is now partially in Mercurial core (starting with mercurial 2.3). The remaining logic have been grouped with the evolve extension.
 
-Usage and Feature
-=================
-
-
-New commands
-------------
-
-Note that rebased changesets are now marked obsolete instead of being stripped.
-
+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.
 """
 
-
-
 from mercurial import util
 
 try:
@@ -50,1061 +22,11 @@
     raise util.Abort('Obsolete extension requires Mercurial 2.3 (or later)')
 
 import sys
-from mercurial.i18n import _
-from mercurial import cmdutil
-from mercurial import commands
-from mercurial import context
-from mercurial import discovery
-from mercurial import error
-from mercurial import extensions
-from mercurial import localrepo
-from mercurial import merge
-from mercurial import phases
-from mercurial import revset
-from mercurial import scmutil
-from mercurial import templatekw
-from mercurial.node import bin, short, 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)
+import json
 
-#####################################################################
-### 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")
-
-# - "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()'))
-    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
-        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)
-
-@eh.reposetup
-def _repostabilizesetup(ui, repo):
-    """Add a hint for "hg stabilize" when troubles make push fails
-    """
-    if not repo.local():
-        return
-
-    opush = repo.push
-
-    class stabilizerrepo(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 stabilize' 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__ = stabilizerrepo
-
-@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
+from mercurial import cmdutil
+from mercurial import error
+from mercurial.node import bin, nullid
 
 
 #####################################################################
@@ -1114,10 +36,8 @@
 # Code related to detection and management of older legacy format never
 # handled by core
 
-import json
 
-@eh.reposetup
-def _checkoldobsolete(ui, repo):
+def reposetup(ui, repo):
     """Detect that a repo still contains some old obsolete format
     """
     if not repo.local():