hgext3rd: move 'directaccess' and 'inhibit' in 'evolve.hack'
authorPierre-Yves David <pierre-yves.david@ens-lyon.org>
Tue, 28 Feb 2017 14:36:18 +0100
changeset 1807 a53efee7d8b0
parent 1806 9f42f819267b
child 1808 202ac6c94b7f
hgext3rd: move 'directaccess' and 'inhibit' in 'evolve.hack' We move them in the 'hgext3rd' package. In the same move we put them under the 'evolve' package for clarity. We use a sub-package 'hack' to make their status clearer.
MANIFEST.in
hgext/directaccess.py
hgext/inhibit.py
hgext3rd/evolve/hack/__init__.py
hgext3rd/evolve/hack/directaccess.py
hgext3rd/evolve/hack/inhibit.py
setup.py
tests/test-inhibit.t
--- a/MANIFEST.in	Tue Feb 28 15:09:03 2017 +0100
+++ b/MANIFEST.in	Tue Feb 28 14:36:18 2017 +0100
@@ -1,8 +1,7 @@
 exclude contrib/nopushpublish.py
-exclude hgext/directaccess.py
 exclude hgext/drophack.py
-exclude hgext/inhibit.py
 exclude hgext/obsolete.py
+exclude hgext3rd/evolve/hack/*.py
 exclude Makefile
 exclude tests/test-drop.t
 exclude tests/test-inhibit.t
--- a/hgext/directaccess.py	Tue Feb 28 15:09:03 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,194 +0,0 @@
-""" This extension provides direct access
-It is the ability to refer and access hidden sha in commands provided that you
-know their value.
-For example hg log -r xxx where xxx is a commit has should work whether xxx is
-hidden or not as we assume that the user knows what he is doing when referring
-to xxx.
-"""
-from mercurial import extensions
-from mercurial import cmdutil
-from mercurial import repoview
-from mercurial import branchmap
-from mercurial import revset
-from mercurial import error
-from mercurial import commands
-from mercurial import hg
-from mercurial import util
-from mercurial.i18n import _
-
-cmdtable = {}
-command = cmdutil.command(cmdtable)
-
-# By default, all the commands have directaccess with warnings
-# List of commands that have no directaccess and directaccess with no warning
-directaccesslevel = [
-    # Format:
-    # ('nowarning', 'evolve', 'prune'),
-    # means: no directaccess warning, for the command in evolve named prune
-    #
-    # ('error', None, 'serve'),
-    # means: no directaccess for the command in core named serve
-    #
-    # The list is ordered alphabetically by command names, starting with all
-    # the commands in core then all the commands in the extensions
-    #
-    # The general guideline is:
-    # - remove directaccess warnings for read only commands
-    # - no direct access for commands with consequences outside of the repo
-    # - leave directaccess warnings for all the other commands
-    #
-    ('nowarning', None, 'annotate'),
-    ('nowarning', None, 'archive'),
-    ('nowarning', None, 'bisect'),
-    ('nowarning', None, 'bookmarks'),
-    ('nowarning', None, 'bundle'),
-    ('nowarning', None, 'cat'),
-    ('nowarning', None, 'diff'),
-    ('nowarning', None, 'export'),
-    ('nowarning', None, 'identify'),
-    ('nowarning', None, 'incoming'),
-    ('nowarning', None, 'log'),
-    ('nowarning', None, 'manifest'),
-    ('error', None, 'outgoing'), # confusing if push errors and not outgoing
-    ('error', None, 'push'), # destructive
-    ('nowarning', None, 'revert'),
-    ('error', None, 'serve'),
-    ('nowarning', None, 'tags'),
-    ('nowarning', None, 'unbundle'),
-    ('nowarning', None, 'update'),
-]
-
-def reposetup(ui, repo):
-    repo._explicitaccess = set()
-
-def _computehidden(repo):
-    hidden = repoview.filterrevs(repo, 'visible')
-    cl = repo.changelog
-    dynamic = hidden & repo._explicitaccess
-    if dynamic:
-        blocked = cl.ancestors(dynamic, inclusive=True)
-        hidden = frozenset(r for r in hidden if r not in blocked)
-    return hidden
-
-def setupdirectaccess():
-    """ Add two new filtername that behave like visible to provide direct access
-    and direct access with warning. Wraps the commands to setup direct access
-    """
-    repoview.filtertable.update({'visible-directaccess-nowarn': _computehidden})
-    repoview.filtertable.update({'visible-directaccess-warn': _computehidden})
-    branchmap.subsettable['visible-directaccess-nowarn'] = 'visible'
-    branchmap.subsettable['visible-directaccess-warn'] = 'visible'
-
-    for warn, ext, cmd in directaccesslevel:
-        try:
-            cmdtable = extensions.find(ext).cmdtable if ext else commands.table
-            wrapper = wrapwitherror if warn == 'error' else wrapwithoutwarning
-            extensions.wrapcommand(cmdtable, cmd, wrapper)
-        except (error.UnknownCommand, KeyError):
-            pass
-
-def wrapwitherror(orig, ui, repo, *args, **kwargs):
-    if repo and repo.filtername == 'visible-directaccess-warn':
-        repo = repo.filtered('visible')
-    return orig(ui, repo, *args, **kwargs)
-
-def wrapwithoutwarning(orig, ui, repo, *args, **kwargs):
-    if repo and repo.filtername == 'visible-directaccess-warn':
-        repo = repo.filtered("visible-directaccess-nowarn")
-    return orig(ui, repo, *args, **kwargs)
-
-def uisetup(ui):
-    """ Change ordering of extensions to ensure that directaccess extsetup comes
-    after the one of the extensions in the loadsafter list """
-    loadsafter = ui.configlist('directaccess','loadsafter')
-    order = list(extensions._order)
-    directaccesidx = order.index('directaccess')
-
-    # The min idx for directaccess to load after all the extensions in loadafter
-    minidxdirectaccess = directaccesidx
-
-    for ext in loadsafter:
-        try:
-            minidxdirectaccess = max(minidxdirectaccess, order.index(ext))
-        except ValueError:
-            pass # extension not loaded
-
-    if minidxdirectaccess > directaccesidx:
-        order.insert(minidxdirectaccess + 1, 'directaccess')
-        order.remove('directaccess')
-        extensions._order = order
-
-def _repository(orig, *args, **kwargs):
-    """Make visible-directaccess-warn the default filter for new repos"""
-    repo = orig(*args, **kwargs)
-    return repo.filtered("visible-directaccess-warn")
-
-def extsetup(ui):
-    extensions.wrapfunction(revset, 'posttreebuilthook', _posttreebuilthook)
-    extensions.wrapfunction(hg, 'repository', _repository)
-    setupdirectaccess()
-
-hashre = util.re.compile('[0-9a-fA-F]{1,40}')
-
-_listtuple = ('symbol', '_list')
-
-def _ishashsymbol(symbol, maxrev):
-    # Returns true if symbol looks like a hash
-    try:
-        n = int(symbol)
-        if n <= maxrev:
-            # It's a rev number
-            return False
-    except ValueError:
-        pass
-    return hashre.match(symbol)
-
-def gethashsymbols(tree, maxrev):
-    # Returns the list of symbols of the tree that look like hashes
-    # for example for the revset 3::abe3ff it will return ('abe3ff')
-    if not tree:
-        return []
-
-    results = []
-    if len(tree) == 2 and tree[0] == "symbol":
-        results.append(tree[1])
-    elif tree[0] == "func" and tree[1] == _listtuple:
-        # the optimiser will group sequence of hash request
-        results += tree[2][1].split('\0')
-    elif len(tree) >= 3:
-        for subtree in tree[1:]:
-            results += gethashsymbols(subtree, maxrev)
-        # return directly, we don't need to filter symbols again
-        return results
-    return [s for s in results if _ishashsymbol(s, maxrev)]
-
-def _posttreebuilthook(orig, tree, repo):
-    # This is use to enabled direct hash access
-    # We extract the symbols that look like hashes and add them to the
-    # explicitaccess set
-    orig(tree, repo)
-    filternm = ""
-    if repo is not None:
-        filternm = repo.filtername
-    if filternm is not None and filternm.startswith('visible-directaccess'):
-        prelength = len(repo._explicitaccess)
-        accessbefore = set(repo._explicitaccess)
-        cl = repo.unfiltered().changelog
-        repo.symbols = gethashsymbols(tree, len(cl))
-        for node in repo.symbols:
-            try:
-                node = cl._partialmatch(node)
-            except error.LookupError:
-                node = None
-            if node is not None:
-                rev = cl.rev(node)
-                if rev not in repo.changelog:
-                    repo._explicitaccess.add(rev)
-        if prelength != len(repo._explicitaccess):
-            if repo.filtername != 'visible-directaccess-nowarn':
-                unhiddencommits = repo._explicitaccess - accessbefore
-                repo.ui.warn(_("Warning: accessing hidden changesets %s "
-                                "for write operation\n") %
-                                (",".join([str(repo.unfiltered()[l])
-                                    for l in unhiddencommits])))
-            repo.invalidatevolatilesets()
--- a/hgext/inhibit.py	Tue Feb 28 15:09:03 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,310 +0,0 @@
-"""reduce the changesets evolution feature scope for early and noob friendly ui
-
-the full scale changeset evolution have some massive bleeding edge and it is
-very easy for people not very intimate with the concept to end up in intricate
-situation. in order to get some of the benefit sooner, this extension is
-disabling some of the less polished aspect of evolution. it should gradually
-get thinner and thinner as changeset evolution will get more polished. this
-extension is only recommended for large scale organisations. individual user
-should probably stick on using evolution in its current state, understand its
-concept and provide feedback
-
-This extension provides the ability to "inhibit" obsolescence markers. obsolete
-revision can be cheaply brought back to life that way.
-However as the inhibitor are not fitting in an append only model, this is
-incompatible with sharing mutable history.
-"""
-from mercurial import localrepo
-from mercurial import obsolete
-from mercurial import extensions
-from mercurial import cmdutil
-from mercurial import error
-from mercurial import scmutil
-from mercurial import commands
-from mercurial import lock as lockmod
-from mercurial import bookmarks
-from mercurial import util
-from mercurial.i18n import _
-
-cmdtable = {}
-command = cmdutil.command(cmdtable)
-
-def _inhibitenabled(repo):
-    return util.safehasattr(repo, '_obsinhibit')
-
-def reposetup(ui, repo):
-
-    class obsinhibitedrepo(repo.__class__):
-
-        @localrepo.storecache('obsinhibit')
-        def _obsinhibit(self):
-            # XXX we should make sure it is invalidated by transaction failure
-            obsinhibit = set()
-            raw = self.svfs.tryread('obsinhibit')
-            for i in xrange(0, len(raw), 20):
-                obsinhibit.add(raw[i:i + 20])
-            return obsinhibit
-
-        def commit(self, *args, **kwargs):
-            newnode = super(obsinhibitedrepo, self).commit(*args, **kwargs)
-            if newnode is not None:
-                _inhibitmarkers(repo, [newnode])
-            return newnode
-
-    repo.__class__ = obsinhibitedrepo
-
-def _update(orig, ui, repo, *args, **kwargs):
-    """
-    When moving to a commit we want to inhibit any obsolete commit affecting
-    the changeset we are updating to. In other words we don't want any visible
-    commit to be obsolete.
-    """
-    wlock = None
-    try:
-        # Evolve is running a hook on lock release to display a warning message
-        # if the workind dir's parent is obsolete.
-        # We take the lock here to make sure that we inhibit the parent before
-        # that hook get a chance to run.
-        wlock = repo.wlock()
-        res = orig(ui, repo, *args, **kwargs)
-        newhead = repo['.'].node()
-        _inhibitmarkers(repo, [newhead])
-        return res
-    finally:
-        lockmod.release(wlock)
-
-def _bookmarkchanged(orig, bkmstoreinst, *args, **kwargs):
-    """ Add inhibition markers to every obsolete bookmarks """
-    repo = bkmstoreinst._repo
-    bkmstorenodes = [repo[v].node() for v in bkmstoreinst.values()]
-    _inhibitmarkers(repo, bkmstorenodes)
-    return orig(bkmstoreinst, *args, **kwargs)
-
-def _bookmark(orig, ui, repo, *bookmarks, **opts):
-    """ Add a -D option to the bookmark command, map it to prune -B """
-    haspruneopt = opts.get('prune', False)
-    if not haspruneopt:
-        return orig(ui, repo, *bookmarks, **opts)
-    elif opts.get('rename'):
-        raise error.Abort('Cannot use both -m and -D')
-    elif len(bookmarks) == 0:
-        hint = _('make sure to put a space between -D and your bookmark name')
-        raise error.Abort(_('Error, please check your command'), hint=hint)
-
-    # Call prune -B
-    evolve = extensions.find('evolve')
-    optsdict = {
-        'new': [],
-        'succ': [],
-        'rev': [],
-        'bookmark': bookmarks,
-        'keep': None,
-        'biject': False,
-    }
-    evolve.cmdprune(ui, repo, **optsdict)
-
-# obsolescence inhibitor
-########################
-
-def _schedulewrite(tr, obsinhibit):
-    """Make sure on disk content will be updated on transaction commit"""
-    def writer(fp):
-        """Serialize the inhibited list to disk.
-        """
-        raw = ''.join(obsinhibit)
-        fp.write(raw)
-    tr.addfilegenerator('obsinhibit', ('obsinhibit',), writer)
-    tr.hookargs['obs_inbihited'] = '1'
-
-def _filterpublic(repo, nodes):
-    """filter out inhibitor on public changeset
-
-    Public changesets are already immune to obsolescence"""
-    getrev = repo.changelog.nodemap.get
-    getphase = repo._phasecache.phase
-    return (n for n in nodes
-            if getrev(n) is not None and getphase(repo, getrev(n)))
-
-def _inhibitmarkers(repo, nodes):
-    """add marker inhibitor for all obsolete revision under <nodes>
-
-    Content of <nodes> and all mutable ancestors are considered. Marker for
-    obsolete revision only are created.
-    """
-    if not _inhibitenabled(repo):
-        return
-
-    # we add (non public()) as a lower boundary to
-    # - use the C code in 3.6 (no ancestors in C as this is written)
-    # - restrict the search space. Otherwise, the ancestors can spend a lot of
-    #   time iterating if you have a check very low in the repo. We do not need
-    #   to iterate over tens of thousand of public revisions with higher
-    #   revision number
-    #
-    # In addition, the revset logic could be made significantly smarter here.
-    newinhibit = repo.revs('(not public())::%ln and obsolete()', nodes)
-    if newinhibit:
-        node = repo.changelog.node
-        lock = tr = None
-        try:
-            lock = repo.lock()
-            tr = repo.transaction('obsinhibit')
-            repo._obsinhibit.update(node(r) for r in newinhibit)
-            _schedulewrite(tr, _filterpublic(repo, repo._obsinhibit))
-            repo.invalidatevolatilesets()
-            tr.close()
-        finally:
-            lockmod.release(tr, lock)
-
-def _deinhibitmarkers(repo, nodes):
-    """lift obsolescence inhibition on a set of nodes
-
-    This will be triggered when inhibited nodes received new obsolescence
-    markers. Otherwise the new obsolescence markers would also be inhibited.
-    """
-    if not _inhibitenabled(repo):
-        return
-
-    deinhibited = repo._obsinhibit & set(nodes)
-    if deinhibited:
-        tr = repo.transaction('obsinhibit')
-        try:
-            repo._obsinhibit -= deinhibited
-            _schedulewrite(tr, _filterpublic(repo, repo._obsinhibit))
-            repo.invalidatevolatilesets()
-            tr.close()
-        finally:
-            tr.release()
-
-def _createmarkers(orig, repo, relations, flag=0, date=None, metadata=None):
-    """wrap markers create to make sure we de-inhibit target nodes"""
-    # wrapping transactio to unify the one in each function
-    lock = tr = None
-    try:
-        lock = repo.lock()
-        tr = repo.transaction('add-obsolescence-marker')
-        orig(repo, relations, flag, date, metadata)
-        precs = (r[0].node() for r in relations)
-        _deinhibitmarkers(repo, precs)
-        tr.close()
-    finally:
-        lockmod.release(tr, lock)
-
-def _filterobsoleterevswrap(orig, repo, rebasesetrevs, *args, **kwargs):
-    repo._notinhibited = rebasesetrevs
-    try:
-        repo.invalidatevolatilesets()
-        r = orig(repo, rebasesetrevs, *args, **kwargs)
-    finally:
-        del repo._notinhibited
-        repo.invalidatevolatilesets()
-    return r
-
-def transactioncallback(orig, repo, desc, *args, **kwargs):
-    """ Wrap localrepo.transaction to inhibit new obsolete changes """
-    def inhibitposttransaction(transaction):
-        # At the end of the transaction we catch all the new visible and
-        # obsolete commit to inhibit them
-        visibleobsolete = repo.revs('obsolete() - hidden()')
-        ignoreset = set(getattr(repo, '_rebaseset', []))
-        ignoreset |= set(getattr(repo, '_obsoletenotrebased', []))
-        visibleobsolete = list(r for r in visibleobsolete if r not in ignoreset)
-        if visibleobsolete:
-            _inhibitmarkers(repo, [repo[r].node() for r in visibleobsolete])
-    transaction = orig(repo, desc, *args, **kwargs)
-    if desc != 'strip' and _inhibitenabled(repo):
-        transaction.addpostclose('inhibitposttransaction',
-                                 inhibitposttransaction)
-    return transaction
-
-
-# We wrap these two functions to address the following scenario:
-# - Assuming that we have markers between commits in the rebase set and
-#   destination and that these markers are inhibited
-# - At the end of the rebase the nodes are still visible because rebase operate
-#   without inhibition and skip these nodes
-# We keep track in repo._obsoletenotrebased of the obsolete commits skipped by
-# the rebase and lift the inhibition in the end of the rebase.
-
-def _computeobsoletenotrebased(orig, repo, *args, **kwargs):
-    r = orig(repo, *args, **kwargs)
-    repo._obsoletenotrebased = r.keys()
-    return r
-
-def _clearrebased(orig, ui, repo, *args, **kwargs):
-    r = orig(ui, repo, *args, **kwargs)
-    tonode = repo.changelog.node
-    if util.safehasattr(repo, '_obsoletenotrebased'):
-        _deinhibitmarkers(repo, [tonode(k) for k in repo._obsoletenotrebased])
-    return r
-
-
-def extsetup(ui):
-    # lets wrap the computation of the obsolete set
-    # We apply inhibition there
-    obsfunc = obsolete.cachefuncs['obsolete']
-    def _computeobsoleteset(repo):
-        """remove any inhibited nodes from the obsolete set
-
-        This will trickle down to other part of mercurial (hidden, log, etc)"""
-        obs = obsfunc(repo)
-        if _inhibitenabled(repo):
-            getrev = repo.changelog.nodemap.get
-            blacklist = getattr(repo, '_notinhibited', set())
-            for n in repo._obsinhibit:
-                if getrev(n) not in blacklist:
-                    obs.discard(getrev(n))
-        return obs
-    try:
-        extensions.find('directaccess')
-    except KeyError:
-        errormsg = _('cannot use inhibit without the direct access extension\n')
-        hint = _("(please enable it or inhibit won\'t work)\n")
-        ui.warn(errormsg)
-        ui.warn(hint)
-        return
-
-    # Wrapping this to inhibit obsolete revs resulting from a transaction
-    extensions.wrapfunction(localrepo.localrepository,
-                            'transaction', transactioncallback)
-
-    obsolete.cachefuncs['obsolete'] = _computeobsoleteset
-    # wrap create marker to make it able to lift the inhibition
-    extensions.wrapfunction(obsolete, 'createmarkers', _createmarkers)
-    # drop divergence computation since it is incompatible with "light revive"
-    obsolete.cachefuncs['divergent'] = lambda repo: set()
-    # drop bumped computation since it is incompatible with "light revive"
-    obsolete.cachefuncs['bumped'] = lambda repo: set()
-    # wrap update to make sure that no obsolete commit is visible after an
-    # update
-    extensions.wrapcommand(commands.table, 'update', _update)
-    try:
-        rebase = extensions.find('rebase')
-        if rebase:
-            if util.safehasattr(rebase, '_filterobsoleterevs'):
-                extensions.wrapfunction(rebase,
-                                        '_filterobsoleterevs',
-                                        _filterobsoleterevswrap)
-            extensions.wrapfunction(rebase, 'clearrebased', _clearrebased)
-            if util.safehasattr(rebase, '_computeobsoletenotrebased'):
-                extensions.wrapfunction(rebase,
-                                        '_computeobsoletenotrebased',
-                                        _computeobsoletenotrebased)
-
-    except KeyError:
-        pass
-    # There are two ways to save bookmark changes during a transation, we
-    # wrap both to add inhibition markers.
-    extensions.wrapfunction(bookmarks.bmstore, 'recordchange', _bookmarkchanged)
-    if getattr(bookmarks.bmstore, 'write', None) is not None:# mercurial < 3.9
-        extensions.wrapfunction(bookmarks.bmstore, 'write', _bookmarkchanged)
-    # Add bookmark -D option
-    entry = extensions.wrapcommand(commands.table, 'bookmark', _bookmark)
-    entry[1].append(('D','prune',None,
-                    _('delete the bookmark and prune the commits underneath')))
-
-@command('debugobsinhibit', [], '')
-def cmddebugobsinhibit(ui, repo, *revs):
-    """inhibit obsolescence markers effect on a set of revs"""
-    nodes = (repo[r].node() for r in scmutil.revrange(repo, revs))
-    _inhibitmarkers(repo, nodes)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/evolve/hack/directaccess.py	Tue Feb 28 14:36:18 2017 +0100
@@ -0,0 +1,194 @@
+""" This extension provides direct access
+It is the ability to refer and access hidden sha in commands provided that you
+know their value.
+For example hg log -r xxx where xxx is a commit has should work whether xxx is
+hidden or not as we assume that the user knows what he is doing when referring
+to xxx.
+"""
+from mercurial import extensions
+from mercurial import cmdutil
+from mercurial import repoview
+from mercurial import branchmap
+from mercurial import revset
+from mercurial import error
+from mercurial import commands
+from mercurial import hg
+from mercurial import util
+from mercurial.i18n import _
+
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+
+# By default, all the commands have directaccess with warnings
+# List of commands that have no directaccess and directaccess with no warning
+directaccesslevel = [
+    # Format:
+    # ('nowarning', 'evolve', 'prune'),
+    # means: no directaccess warning, for the command in evolve named prune
+    #
+    # ('error', None, 'serve'),
+    # means: no directaccess for the command in core named serve
+    #
+    # The list is ordered alphabetically by command names, starting with all
+    # the commands in core then all the commands in the extensions
+    #
+    # The general guideline is:
+    # - remove directaccess warnings for read only commands
+    # - no direct access for commands with consequences outside of the repo
+    # - leave directaccess warnings for all the other commands
+    #
+    ('nowarning', None, 'annotate'),
+    ('nowarning', None, 'archive'),
+    ('nowarning', None, 'bisect'),
+    ('nowarning', None, 'bookmarks'),
+    ('nowarning', None, 'bundle'),
+    ('nowarning', None, 'cat'),
+    ('nowarning', None, 'diff'),
+    ('nowarning', None, 'export'),
+    ('nowarning', None, 'identify'),
+    ('nowarning', None, 'incoming'),
+    ('nowarning', None, 'log'),
+    ('nowarning', None, 'manifest'),
+    ('error', None, 'outgoing'), # confusing if push errors and not outgoing
+    ('error', None, 'push'), # destructive
+    ('nowarning', None, 'revert'),
+    ('error', None, 'serve'),
+    ('nowarning', None, 'tags'),
+    ('nowarning', None, 'unbundle'),
+    ('nowarning', None, 'update'),
+]
+
+def reposetup(ui, repo):
+    repo._explicitaccess = set()
+
+def _computehidden(repo):
+    hidden = repoview.filterrevs(repo, 'visible')
+    cl = repo.changelog
+    dynamic = hidden & repo._explicitaccess
+    if dynamic:
+        blocked = cl.ancestors(dynamic, inclusive=True)
+        hidden = frozenset(r for r in hidden if r not in blocked)
+    return hidden
+
+def setupdirectaccess():
+    """ Add two new filtername that behave like visible to provide direct access
+    and direct access with warning. Wraps the commands to setup direct access
+    """
+    repoview.filtertable.update({'visible-directaccess-nowarn': _computehidden})
+    repoview.filtertable.update({'visible-directaccess-warn': _computehidden})
+    branchmap.subsettable['visible-directaccess-nowarn'] = 'visible'
+    branchmap.subsettable['visible-directaccess-warn'] = 'visible'
+
+    for warn, ext, cmd in directaccesslevel:
+        try:
+            cmdtable = extensions.find(ext).cmdtable if ext else commands.table
+            wrapper = wrapwitherror if warn == 'error' else wrapwithoutwarning
+            extensions.wrapcommand(cmdtable, cmd, wrapper)
+        except (error.UnknownCommand, KeyError):
+            pass
+
+def wrapwitherror(orig, ui, repo, *args, **kwargs):
+    if repo and repo.filtername == 'visible-directaccess-warn':
+        repo = repo.filtered('visible')
+    return orig(ui, repo, *args, **kwargs)
+
+def wrapwithoutwarning(orig, ui, repo, *args, **kwargs):
+    if repo and repo.filtername == 'visible-directaccess-warn':
+        repo = repo.filtered("visible-directaccess-nowarn")
+    return orig(ui, repo, *args, **kwargs)
+
+def uisetup(ui):
+    """ Change ordering of extensions to ensure that directaccess extsetup comes
+    after the one of the extensions in the loadsafter list """
+    loadsafter = ui.configlist('directaccess','loadsafter')
+    order = list(extensions._order)
+    directaccesidx = order.index('directaccess')
+
+    # The min idx for directaccess to load after all the extensions in loadafter
+    minidxdirectaccess = directaccesidx
+
+    for ext in loadsafter:
+        try:
+            minidxdirectaccess = max(minidxdirectaccess, order.index(ext))
+        except ValueError:
+            pass # extension not loaded
+
+    if minidxdirectaccess > directaccesidx:
+        order.insert(minidxdirectaccess + 1, 'directaccess')
+        order.remove('directaccess')
+        extensions._order = order
+
+def _repository(orig, *args, **kwargs):
+    """Make visible-directaccess-warn the default filter for new repos"""
+    repo = orig(*args, **kwargs)
+    return repo.filtered("visible-directaccess-warn")
+
+def extsetup(ui):
+    extensions.wrapfunction(revset, 'posttreebuilthook', _posttreebuilthook)
+    extensions.wrapfunction(hg, 'repository', _repository)
+    setupdirectaccess()
+
+hashre = util.re.compile('[0-9a-fA-F]{1,40}')
+
+_listtuple = ('symbol', '_list')
+
+def _ishashsymbol(symbol, maxrev):
+    # Returns true if symbol looks like a hash
+    try:
+        n = int(symbol)
+        if n <= maxrev:
+            # It's a rev number
+            return False
+    except ValueError:
+        pass
+    return hashre.match(symbol)
+
+def gethashsymbols(tree, maxrev):
+    # Returns the list of symbols of the tree that look like hashes
+    # for example for the revset 3::abe3ff it will return ('abe3ff')
+    if not tree:
+        return []
+
+    results = []
+    if len(tree) == 2 and tree[0] == "symbol":
+        results.append(tree[1])
+    elif tree[0] == "func" and tree[1] == _listtuple:
+        # the optimiser will group sequence of hash request
+        results += tree[2][1].split('\0')
+    elif len(tree) >= 3:
+        for subtree in tree[1:]:
+            results += gethashsymbols(subtree, maxrev)
+        # return directly, we don't need to filter symbols again
+        return results
+    return [s for s in results if _ishashsymbol(s, maxrev)]
+
+def _posttreebuilthook(orig, tree, repo):
+    # This is use to enabled direct hash access
+    # We extract the symbols that look like hashes and add them to the
+    # explicitaccess set
+    orig(tree, repo)
+    filternm = ""
+    if repo is not None:
+        filternm = repo.filtername
+    if filternm is not None and filternm.startswith('visible-directaccess'):
+        prelength = len(repo._explicitaccess)
+        accessbefore = set(repo._explicitaccess)
+        cl = repo.unfiltered().changelog
+        repo.symbols = gethashsymbols(tree, len(cl))
+        for node in repo.symbols:
+            try:
+                node = cl._partialmatch(node)
+            except error.LookupError:
+                node = None
+            if node is not None:
+                rev = cl.rev(node)
+                if rev not in repo.changelog:
+                    repo._explicitaccess.add(rev)
+        if prelength != len(repo._explicitaccess):
+            if repo.filtername != 'visible-directaccess-nowarn':
+                unhiddencommits = repo._explicitaccess - accessbefore
+                repo.ui.warn(_("Warning: accessing hidden changesets %s "
+                                "for write operation\n") %
+                                (",".join([str(repo.unfiltered()[l])
+                                    for l in unhiddencommits])))
+            repo.invalidatevolatilesets()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/evolve/hack/inhibit.py	Tue Feb 28 14:36:18 2017 +0100
@@ -0,0 +1,310 @@
+"""reduce the changesets evolution feature scope for early and noob friendly ui
+
+the full scale changeset evolution have some massive bleeding edge and it is
+very easy for people not very intimate with the concept to end up in intricate
+situation. in order to get some of the benefit sooner, this extension is
+disabling some of the less polished aspect of evolution. it should gradually
+get thinner and thinner as changeset evolution will get more polished. this
+extension is only recommended for large scale organisations. individual user
+should probably stick on using evolution in its current state, understand its
+concept and provide feedback
+
+This extension provides the ability to "inhibit" obsolescence markers. obsolete
+revision can be cheaply brought back to life that way.
+However as the inhibitor are not fitting in an append only model, this is
+incompatible with sharing mutable history.
+"""
+from mercurial import localrepo
+from mercurial import obsolete
+from mercurial import extensions
+from mercurial import cmdutil
+from mercurial import error
+from mercurial import scmutil
+from mercurial import commands
+from mercurial import lock as lockmod
+from mercurial import bookmarks
+from mercurial import util
+from mercurial.i18n import _
+
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+
+def _inhibitenabled(repo):
+    return util.safehasattr(repo, '_obsinhibit')
+
+def reposetup(ui, repo):
+
+    class obsinhibitedrepo(repo.__class__):
+
+        @localrepo.storecache('obsinhibit')
+        def _obsinhibit(self):
+            # XXX we should make sure it is invalidated by transaction failure
+            obsinhibit = set()
+            raw = self.svfs.tryread('obsinhibit')
+            for i in xrange(0, len(raw), 20):
+                obsinhibit.add(raw[i:i + 20])
+            return obsinhibit
+
+        def commit(self, *args, **kwargs):
+            newnode = super(obsinhibitedrepo, self).commit(*args, **kwargs)
+            if newnode is not None:
+                _inhibitmarkers(repo, [newnode])
+            return newnode
+
+    repo.__class__ = obsinhibitedrepo
+
+def _update(orig, ui, repo, *args, **kwargs):
+    """
+    When moving to a commit we want to inhibit any obsolete commit affecting
+    the changeset we are updating to. In other words we don't want any visible
+    commit to be obsolete.
+    """
+    wlock = None
+    try:
+        # Evolve is running a hook on lock release to display a warning message
+        # if the workind dir's parent is obsolete.
+        # We take the lock here to make sure that we inhibit the parent before
+        # that hook get a chance to run.
+        wlock = repo.wlock()
+        res = orig(ui, repo, *args, **kwargs)
+        newhead = repo['.'].node()
+        _inhibitmarkers(repo, [newhead])
+        return res
+    finally:
+        lockmod.release(wlock)
+
+def _bookmarkchanged(orig, bkmstoreinst, *args, **kwargs):
+    """ Add inhibition markers to every obsolete bookmarks """
+    repo = bkmstoreinst._repo
+    bkmstorenodes = [repo[v].node() for v in bkmstoreinst.values()]
+    _inhibitmarkers(repo, bkmstorenodes)
+    return orig(bkmstoreinst, *args, **kwargs)
+
+def _bookmark(orig, ui, repo, *bookmarks, **opts):
+    """ Add a -D option to the bookmark command, map it to prune -B """
+    haspruneopt = opts.get('prune', False)
+    if not haspruneopt:
+        return orig(ui, repo, *bookmarks, **opts)
+    elif opts.get('rename'):
+        raise error.Abort('Cannot use both -m and -D')
+    elif len(bookmarks) == 0:
+        hint = _('make sure to put a space between -D and your bookmark name')
+        raise error.Abort(_('Error, please check your command'), hint=hint)
+
+    # Call prune -B
+    evolve = extensions.find('evolve')
+    optsdict = {
+        'new': [],
+        'succ': [],
+        'rev': [],
+        'bookmark': bookmarks,
+        'keep': None,
+        'biject': False,
+    }
+    evolve.cmdprune(ui, repo, **optsdict)
+
+# obsolescence inhibitor
+########################
+
+def _schedulewrite(tr, obsinhibit):
+    """Make sure on disk content will be updated on transaction commit"""
+    def writer(fp):
+        """Serialize the inhibited list to disk.
+        """
+        raw = ''.join(obsinhibit)
+        fp.write(raw)
+    tr.addfilegenerator('obsinhibit', ('obsinhibit',), writer)
+    tr.hookargs['obs_inbihited'] = '1'
+
+def _filterpublic(repo, nodes):
+    """filter out inhibitor on public changeset
+
+    Public changesets are already immune to obsolescence"""
+    getrev = repo.changelog.nodemap.get
+    getphase = repo._phasecache.phase
+    return (n for n in nodes
+            if getrev(n) is not None and getphase(repo, getrev(n)))
+
+def _inhibitmarkers(repo, nodes):
+    """add marker inhibitor for all obsolete revision under <nodes>
+
+    Content of <nodes> and all mutable ancestors are considered. Marker for
+    obsolete revision only are created.
+    """
+    if not _inhibitenabled(repo):
+        return
+
+    # we add (non public()) as a lower boundary to
+    # - use the C code in 3.6 (no ancestors in C as this is written)
+    # - restrict the search space. Otherwise, the ancestors can spend a lot of
+    #   time iterating if you have a check very low in the repo. We do not need
+    #   to iterate over tens of thousand of public revisions with higher
+    #   revision number
+    #
+    # In addition, the revset logic could be made significantly smarter here.
+    newinhibit = repo.revs('(not public())::%ln and obsolete()', nodes)
+    if newinhibit:
+        node = repo.changelog.node
+        lock = tr = None
+        try:
+            lock = repo.lock()
+            tr = repo.transaction('obsinhibit')
+            repo._obsinhibit.update(node(r) for r in newinhibit)
+            _schedulewrite(tr, _filterpublic(repo, repo._obsinhibit))
+            repo.invalidatevolatilesets()
+            tr.close()
+        finally:
+            lockmod.release(tr, lock)
+
+def _deinhibitmarkers(repo, nodes):
+    """lift obsolescence inhibition on a set of nodes
+
+    This will be triggered when inhibited nodes received new obsolescence
+    markers. Otherwise the new obsolescence markers would also be inhibited.
+    """
+    if not _inhibitenabled(repo):
+        return
+
+    deinhibited = repo._obsinhibit & set(nodes)
+    if deinhibited:
+        tr = repo.transaction('obsinhibit')
+        try:
+            repo._obsinhibit -= deinhibited
+            _schedulewrite(tr, _filterpublic(repo, repo._obsinhibit))
+            repo.invalidatevolatilesets()
+            tr.close()
+        finally:
+            tr.release()
+
+def _createmarkers(orig, repo, relations, flag=0, date=None, metadata=None):
+    """wrap markers create to make sure we de-inhibit target nodes"""
+    # wrapping transactio to unify the one in each function
+    lock = tr = None
+    try:
+        lock = repo.lock()
+        tr = repo.transaction('add-obsolescence-marker')
+        orig(repo, relations, flag, date, metadata)
+        precs = (r[0].node() for r in relations)
+        _deinhibitmarkers(repo, precs)
+        tr.close()
+    finally:
+        lockmod.release(tr, lock)
+
+def _filterobsoleterevswrap(orig, repo, rebasesetrevs, *args, **kwargs):
+    repo._notinhibited = rebasesetrevs
+    try:
+        repo.invalidatevolatilesets()
+        r = orig(repo, rebasesetrevs, *args, **kwargs)
+    finally:
+        del repo._notinhibited
+        repo.invalidatevolatilesets()
+    return r
+
+def transactioncallback(orig, repo, desc, *args, **kwargs):
+    """ Wrap localrepo.transaction to inhibit new obsolete changes """
+    def inhibitposttransaction(transaction):
+        # At the end of the transaction we catch all the new visible and
+        # obsolete commit to inhibit them
+        visibleobsolete = repo.revs('obsolete() - hidden()')
+        ignoreset = set(getattr(repo, '_rebaseset', []))
+        ignoreset |= set(getattr(repo, '_obsoletenotrebased', []))
+        visibleobsolete = list(r for r in visibleobsolete if r not in ignoreset)
+        if visibleobsolete:
+            _inhibitmarkers(repo, [repo[r].node() for r in visibleobsolete])
+    transaction = orig(repo, desc, *args, **kwargs)
+    if desc != 'strip' and _inhibitenabled(repo):
+        transaction.addpostclose('inhibitposttransaction',
+                                 inhibitposttransaction)
+    return transaction
+
+
+# We wrap these two functions to address the following scenario:
+# - Assuming that we have markers between commits in the rebase set and
+#   destination and that these markers are inhibited
+# - At the end of the rebase the nodes are still visible because rebase operate
+#   without inhibition and skip these nodes
+# We keep track in repo._obsoletenotrebased of the obsolete commits skipped by
+# the rebase and lift the inhibition in the end of the rebase.
+
+def _computeobsoletenotrebased(orig, repo, *args, **kwargs):
+    r = orig(repo, *args, **kwargs)
+    repo._obsoletenotrebased = r.keys()
+    return r
+
+def _clearrebased(orig, ui, repo, *args, **kwargs):
+    r = orig(ui, repo, *args, **kwargs)
+    tonode = repo.changelog.node
+    if util.safehasattr(repo, '_obsoletenotrebased'):
+        _deinhibitmarkers(repo, [tonode(k) for k in repo._obsoletenotrebased])
+    return r
+
+
+def extsetup(ui):
+    # lets wrap the computation of the obsolete set
+    # We apply inhibition there
+    obsfunc = obsolete.cachefuncs['obsolete']
+    def _computeobsoleteset(repo):
+        """remove any inhibited nodes from the obsolete set
+
+        This will trickle down to other part of mercurial (hidden, log, etc)"""
+        obs = obsfunc(repo)
+        if _inhibitenabled(repo):
+            getrev = repo.changelog.nodemap.get
+            blacklist = getattr(repo, '_notinhibited', set())
+            for n in repo._obsinhibit:
+                if getrev(n) not in blacklist:
+                    obs.discard(getrev(n))
+        return obs
+    try:
+        extensions.find('directaccess')
+    except KeyError:
+        errormsg = _('cannot use inhibit without the direct access extension\n')
+        hint = _("(please enable it or inhibit won\'t work)\n")
+        ui.warn(errormsg)
+        ui.warn(hint)
+        return
+
+    # Wrapping this to inhibit obsolete revs resulting from a transaction
+    extensions.wrapfunction(localrepo.localrepository,
+                            'transaction', transactioncallback)
+
+    obsolete.cachefuncs['obsolete'] = _computeobsoleteset
+    # wrap create marker to make it able to lift the inhibition
+    extensions.wrapfunction(obsolete, 'createmarkers', _createmarkers)
+    # drop divergence computation since it is incompatible with "light revive"
+    obsolete.cachefuncs['divergent'] = lambda repo: set()
+    # drop bumped computation since it is incompatible with "light revive"
+    obsolete.cachefuncs['bumped'] = lambda repo: set()
+    # wrap update to make sure that no obsolete commit is visible after an
+    # update
+    extensions.wrapcommand(commands.table, 'update', _update)
+    try:
+        rebase = extensions.find('rebase')
+        if rebase:
+            if util.safehasattr(rebase, '_filterobsoleterevs'):
+                extensions.wrapfunction(rebase,
+                                        '_filterobsoleterevs',
+                                        _filterobsoleterevswrap)
+            extensions.wrapfunction(rebase, 'clearrebased', _clearrebased)
+            if util.safehasattr(rebase, '_computeobsoletenotrebased'):
+                extensions.wrapfunction(rebase,
+                                        '_computeobsoletenotrebased',
+                                        _computeobsoletenotrebased)
+
+    except KeyError:
+        pass
+    # There are two ways to save bookmark changes during a transation, we
+    # wrap both to add inhibition markers.
+    extensions.wrapfunction(bookmarks.bmstore, 'recordchange', _bookmarkchanged)
+    if getattr(bookmarks.bmstore, 'write', None) is not None:# mercurial < 3.9
+        extensions.wrapfunction(bookmarks.bmstore, 'write', _bookmarkchanged)
+    # Add bookmark -D option
+    entry = extensions.wrapcommand(commands.table, 'bookmark', _bookmark)
+    entry[1].append(('D','prune',None,
+                    _('delete the bookmark and prune the commits underneath')))
+
+@command('debugobsinhibit', [], '')
+def cmddebugobsinhibit(ui, repo, *revs):
+    """inhibit obsolescence markers effect on a set of revs"""
+    nodes = (repo[r].node() for r in scmutil.revrange(repo, revs))
+    _inhibitmarkers(repo, nodes)
--- a/setup.py	Tue Feb 28 15:09:03 2017 +0100
+++ b/setup.py	Tue Feb 28 14:36:18 2017 +0100
@@ -22,8 +22,8 @@
 ]
 
 if os.environ.get('INCLUDE_INHIBIT'):
-    py_modules.append('hgext.inhibit')
-    py_modules.append('hgext.directaccess')
+    py_modules.append('hgext3rd.evolve.hack.inhibit')
+    py_modules.append('hgext3rd.evolve.hack.directaccess')
 
 setup(
     name='hg-evolve',
--- a/tests/test-inhibit.t	Tue Feb 28 15:09:03 2017 +0100
+++ b/tests/test-inhibit.t	Tue Feb 28 14:36:18 2017 +0100
@@ -9,8 +9,8 @@
   > strip=
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
-  $ echo "directaccess=$(echo $(dirname $TESTDIR))/hgext/directaccess.py" >> $HGRCPATH
-  $ echo "inhibit=$(echo $(dirname $TESTDIR))/hgext/inhibit.py" >> $HGRCPATH
+  $ echo "directaccess=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/hack/directaccess.py" >> $HGRCPATH
+  $ echo "inhibit=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/hack/inhibit.py" >> $HGRCPATH
   $ mkcommit() {
   >    echo "$1" > "$1"
   >    hg add "$1"
@@ -728,7 +728,7 @@
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
   > EOF
-  $ echo "inhibit=$(echo $(dirname $TESTDIR))/hgext/inhibit.py" >> $HGRCPATH
+  $ echo "inhibit=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/hack/inhibit.py" >> $HGRCPATH
 
 Empty commit
   $ hg amend
@@ -781,7 +781,7 @@
   cannot use inhibit without the direct access extension
   (please enable it or inhibit won't work)
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ echo "directaccess=$(echo $(dirname $TESTDIR))/hgext/directaccess.py" >> $HGRCPATH
+  $ echo "directaccess=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/hack/directaccess.py" >> $HGRCPATH
   $ cd ..
 
 hg push should not allow directaccess unless forced with --hidden