hgext/obsolete.py
changeset 447 fa85e7205e0b
parent 446 9d47474d2582
child 448 96c896f0180b
--- a/hgext/obsolete.py	Tue Aug 07 18:53:26 2012 +0200
+++ b/hgext/obsolete.py	Tue Aug 07 21:35:39 2012 +0200
@@ -57,6 +57,7 @@
 from mercurial import discovery
 from mercurial import error
 from mercurial import extensions
+from mercurial import localrepo
 from mercurial import phases
 from mercurial import revset
 from mercurial import scmutil
@@ -304,12 +305,112 @@
 ### Obsolescence Caching Logic                                    ###
 #####################################################################
 
+### compute cache functions
+
+computecache = {}
+def cachefor(name):
+    """Decorator to register a function as computing the cache for a set"""
+    def decorator(func):
+        assert name not in computecache
+        computecache[name] = func
+        return func
+    return decorator
+
+@cachefor('obsolete')
+def _computeobsoleteset(repo):
+    """the set of obsolete revision"""
+    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 revision with obsolete parent"""
+    return set(repo.revs('(obsolete()::) - obsolete()'))
+
+@cachefor('suspended')
+def _computesuspendedset(repo):
+    """the set of obsolete parent with non obsolete descendant"""
+    return set(repo.revs('obsolete() and obsolete()::unstable()'))
+
+@cachefor('extinct')
+def _computeextinctset(repo):
+    """the set of obsolete parent without non obsolete descendant"""
+    return set(repo.revs('obsolete() - obsolete()::unstable()'))
+
+@cachefor('latecomer')
+def _computelatecomerset(repo):
+    """the set of rev trying to obsolete public revision"""
+    query = 'allsuccessors(public()) - obsolete() - public()'
+    return set(repo.revs(query))
+
+@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
+
+@eh.wrapfunction(obsolete.obsstore, '__init__')
+def _initobsstorecache(orig, obsstore, *args, **kwargs):
+    """add a caches attributes to obsstore"""
+    obsstore.caches = {}
+    return orig(obsstore, *args, **kwargs)
+
+def getobscache(repo, name):
+    if not repo.obsstore:
+        return ()
+    if name not in repo.obsstore.caches:
+        repo.obsstore.caches[name] = computecache[name](repo)
+    return repo.obsstore.caches[name]
+
+
+### cache clean up
+def clearobscaches(repo):
+    #if 'obsstore' in vars(repo):
+    # should work great but cache invalidation act strange
+    repo.obsstore.caches.clear()
+
+@eh.wrapfunction(localrepo.localrepository, 'updatebranchcache')
+@eh.wrapfunction(phases, 'advanceboundary')
+def wrapclearcache(orig, repo, *args, **kwargs):
+    try:
+        return orig(repo, *args, **kwargs)
+    finally:
+        clearobscaches(repo)
+
+@eh.wrapfunction(obsolete.obsstore, 'add')
+def clearonadd(orig, obsstore, *args, **kwargs):
+    try:
+        return orig(obsstore, *args, **kwargs)
+    finally:
+        obsstore.caches.clear()
+
+### cache user
+
 @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 ctx._repo._unstableset
+    return ctx.rev() in getobscache(ctx._repo, 'unstable')
 
 
 @eh.addattr(context.changectx, 'extinct')
@@ -317,7 +418,7 @@
     """is the changeset extinct by other"""
     if ctx.node() is None:
         return False
-    return ctx.rev() in ctx._repo._extinctset
+    return ctx.rev() in getobscache(ctx._repo, 'extinct')
 
 @eh.revset('obsolete')
 def revsetobsolete(repo, subset, x):
@@ -325,7 +426,8 @@
     Changeset is obsolete.
     """
     args = revset.getargs(x, 0, 0, 'obsolete takes no argument')
-    return [r for r in subset if r in repo._obsoleteset and repo._phasecache.phase(repo, r) > 0]
+    obsoletes = getobscache(repo, 'obsolete')
+    return [r for r in subset if r in obsoletes]
 
 @eh.revset('unstable')
 def revsetunstable(repo, subset, x):
@@ -333,7 +435,8 @@
     Unstable changesets are non-obsolete with obsolete ancestors.
     """
     args = revset.getargs(x, 0, 0, 'unstable takes no arguments')
-    return [r for r in subset if r in repo._unstableset]
+    unstables = getobscache(repo, 'unstable')
+    return [r for r in subset if r in unstables]
 
 @eh.revset('extinct')
 def revsetextinct(repo, subset, x):
@@ -341,104 +444,8 @@
     Obsolete changesets with obsolete descendants only.
     """
     args = revset.getargs(x, 0, 0, 'extinct takes no arguments')
-    return [r for r in subset if r in repo._extinctset]
-
-
-@eh.wrapfunction(phases, 'advanceboundary')
-def wrapclearcache(orig, repo, *args, **kwargs):
-    try:
-        return orig(repo, *args, **kwargs)
-    finally:
-        repo._clearobsoletecache()
-
-@eh.reposetup
-def _repocachesetup(ui, repo):
-    if not repo.local():
-        return
-
-    o_updatebranchcache = repo.updatebranchcache
-    class cachedobsolescencegrepo(repo.__class__):
-
-        # XXX move me on obssotre
-        @util.propertycache
-        def _obsoleteset(self):
-            """the set of obsolete revision"""
-            obs = set()
-            nm = self.changelog.nodemap
-            for prec in self.obsstore.precursors:
-                rev = nm.get(prec)
-                if rev is not None:
-                    obs.add(rev)
-            return obs
-
-        # XXX move me on obssotre
-        @util.propertycache
-        def _unstableset(self):
-            """the set of non obsolete revision with obsolete parent"""
-            return set(self.revs('(obsolete()::) - obsolete()'))
-
-        # XXX move me on obssotre
-        @util.propertycache
-        def _suspendedset(self):
-            """the set of obsolete parent with non obsolete descendant"""
-            return set(self.revs('obsolete() and obsolete()::unstable()'))
-
-        # XXX move me on obssotre
-        @util.propertycache
-        def _extinctset(self):
-            """the set of obsolete parent without non obsolete descendant"""
-            return set(self.revs('obsolete() - obsolete()::unstable()'))
-
-        # XXX move me on obssotre
-        @util.propertycache
-        def _latecomerset(self):
-            """the set of rev trying to obsolete public revision"""
-            query = 'allsuccessors(public()) - obsolete() - public()'
-            return set(self.revs(query))
-
-        # XXX move me on obssotre
-        @util.propertycache
-        def _conflictingset(self):
-            """the set of rev trying to obsolete public revision"""
-            conflicting = set()
-            obsstore = self.obsstore
-            newermap = {}
-            for ctx in self.set('(not public()) - obsolete()'):
-                prec = obsstore.successors.get(ctx.node(), ())
-                toprocess = set(prec)
-                while toprocess:
-                    prec = toprocess.pop()[0]
-                    if prec not in newermap:
-                        newermap[prec] = newerversion(self, prec)
-                    newer = [n for n in newermap[prec] if n] # filter kill
-                    if len(newer) > 1:
-                        conflicting.add(ctx.rev())
-                        break
-                toprocess.update(obsstore.successors.get(prec, ()))
-            return conflicting
-
-        def _clearobsoletecache(self):
-            if '_obsoleteset' in vars(self):
-                del self._obsoleteset
-            self._clearunstablecache()
-
-        def updatebranchcache(self):
-            o_updatebranchcache()
-            self._clearunstablecache()
-
-        def _clearunstablecache(self):
-            if '_unstableset' in vars(self):
-                del self._unstableset
-            if '_suspendedset' in vars(self):
-                del self._suspendedset
-            if '_extinctset' in vars(self):
-                del self._extinctset
-            if '_latecomerset' in vars(self):
-                del self._latecomerset
-            if '_conflictingset' in vars(self):
-                del self._conflictingset
-
-    repo.__class__ = cachedobsolescencegrepo
+    extincts = getobscache(repo, 'extinct')
+    return [r for r in subset if r in extincts]
 
 #####################################################################
 ### Complete troubles computation logic                           ###
@@ -449,14 +456,14 @@
     """is the changeset latecomer (Try to succeed to public change)"""
     if ctx.node() is None:
         return False
-    return ctx.rev() in ctx._repo._latecomerset
+    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 ctx._repo._conflictingset
+    return ctx.rev() in getobscache(ctx._repo, 'conflicting')
 
 
 #####################################################################
@@ -593,7 +600,7 @@
             if nprec in nsucs:
                 raise util.Abort("Changeset %s cannot obsolete himself" % prec)
             repo.obsstore.create(tr, nprec, nsucs, flag, metadata)
-            repo._clearobsoletecache()
+            clearobscaches(repo)
         tr.close()
     finally:
         tr.release()
@@ -618,7 +625,8 @@
     Obsolete changesets with non-obsolete descendants.
     """
     args = revset.getargs(x, 0, 0, 'suspended takes no arguments')
-    return [r for r in subset if r in repo._suspendedset]
+    suspended = getobscache(repo, 'suspended')
+    return [r for r in subset if r in suspended]
 
 @eh.revset('latecomer')
 def revsetlatecomer(repo, subset, x):
@@ -626,7 +634,8 @@
     Changesets marked as successors of public changesets.
     """
     args = revset.getargs(x, 0, 0, 'latecomer takes no arguments')
-    return [r for r in subset if r in repo._latecomerset]
+    lates = getobscache(repo, 'latecomer')
+    return [r for r in subset if r in lates]
 
 @eh.revset('conflicting')
 def revsetconflicting(repo, subset, x):
@@ -634,7 +643,8 @@
     Changesets marked as successors of a same changeset.
     """
     args = revset.getargs(x, 0, 0, 'conflicting takes no arguments')
-    return [r for r in subset if r in repo._conflictingset]
+    conf = getobscache(repo, 'conflicting')
+    return [r for r in subset if r in conf]
 
 
 @eh.revset('precursors')
@@ -683,11 +693,12 @@
     ``stable``, ``unstable``, ``suspended`` or ``extinct``.
     """
     rev = ctx.rev()
-    if rev in repo._extinctset:
-        return 'extinct'
-    if rev in repo._suspendedset:
-        return 'suspended'
-    if rev in repo._unstableset:
+    if ctx.obsolete():
+        if ctx.extinct():
+            return 'extinct'
+        else:
+            return 'suspended'
+    elif ctx.unstable():
         return 'unstable'
     return 'stable'
 
@@ -798,7 +809,7 @@
                     }
                 repo.obsstore.create(tr, oldnode, [new], 0, meta)
                 tr.close()
-                repo._clearobsoletecache()
+                clearobscaches(repo)
             finally:
                 tr.release()
         finally: