hgext/inhibit.py
changeset 1224 859a854cedc3
child 1225 577f5340be6f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/inhibit.py	Fri Mar 06 21:25:44 2015 -0800
@@ -0,0 +1,125 @@
+"""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
+
+The first feature provided by this extension is 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 scmutil
+
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+
+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.sopener.tryread('obsinhibit')
+            for i in xrange(0, len(raw), 20):
+                obsinhibit.add(raw[i:i+20])
+            return obsinhibit
+
+    repo.__class__ = obsinhibitedrepo
+
+# 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 _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.
+    """
+    newinhibit = repo.set('::%ln and obsolete()', nodes)
+    if newinhibit:
+        tr = repo.transaction('obsinhibit')
+        try:
+            repo._obsinhibit.update(c.node() for c in newinhibit)
+            _schedulewrite(tr, repo._obsinhibit)
+            repo.invalidatevolatilesets()
+            tr.close()
+        finally:
+            tr.release()
+
+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.
+    """
+    deinhibited = repo._obsinhibit & set(nodes)
+    if deinhibited:
+        tr = repo.transaction('obsinhibit')
+        try:
+            repo._obsinhibit -= deinhibited
+            _schedulewrite(tr, 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
+    tr = repo.transaction('add-obsolescence-marker')
+    try:
+        orig(repo, relations, flag, date, metadata)
+        precs = (r[0].node() for r in relations)
+        _deinhibitmarkers(repo, precs)
+        tr.close()
+    finally:
+        tr.release()
+
+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)
+        getrev = repo.changelog.nodemap.get
+        for n in repo._obsinhibit:
+            obs.discard(getrev(n))
+        return obs
+    obsolete.cachefuncs['obsolete'] = _computeobsoleteset
+    # 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 create marker to make it able to lift the inhibition
+    extensions.wrapfunction(obsolete, 'createmarkers', _createmarkers)
+
+@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)