branchmap: encapsulate cache updating in the map itself
authorMartijn Pieters <mj@octobus.net>
Mon, 21 Jan 2019 17:37:33 +0000
changeset 41626 328ca3b9e545
parent 41625 fbd4ce55bcbd
child 41627 2c13e91ede6e
branchmap: encapsulate cache updating in the map itself Rather than have a repository update the cache, move handling of cache updates into the branchmap module, in the form of a custom mapping class. This makes later performance improvements easier to handle too. Differential Revision: https://phab.mercurial-scm.org/D5638
contrib/perf.py
mercurial/branchmap.py
mercurial/localrepo.py
mercurial/statichttprepo.py
mercurial/streamclone.py
--- a/contrib/perf.py	Thu Feb 07 21:16:25 2019 -0800
+++ b/contrib/perf.py	Mon Jan 21 17:37:33 2019 +0000
@@ -2376,13 +2376,18 @@
             view = repo
         else:
             view = repo.filtered(filtername)
+        if util.safehasattr(view._branchcaches, '_per_filter'):
+            filtered = view._branchcaches._per_filter
+        else:
+            # older versions
+            filtered = view._branchcaches
         def d():
             if clear_revbranch:
                 repo.revbranchcache()._clear()
             if full:
                 view._branchcaches.clear()
             else:
-                view._branchcaches.pop(filtername, None)
+                filtered.pop(filtername, None)
             view.branchmap()
         return d
     # add filter in smaller subset to bigger subset
--- a/mercurial/branchmap.py	Thu Feb 07 21:16:25 2019 -0800
+++ b/mercurial/branchmap.py	Mon Jan 21 17:37:33 2019 +0000
@@ -43,75 +43,89 @@
                'served': 'immutable',
                'immutable': 'base'}
 
-def updatecache(repo):
-    """Update the cache for the given filtered view on a repository"""
-    # This can trigger updates for the caches for subsets of the filtered
-    # view, e.g. when there is no cache for this filtered view or the cache
-    # is stale.
+
+class BranchMapCache(object):
+    """Cache mapping"""
+    def __init__(self):
+        self._per_filter = {}
 
-    cl = repo.changelog
-    filtername = repo.filtername
-    bcache = repo._branchcaches.get(filtername)
-    if bcache is None or not bcache.validfor(repo):
-        # cache object missing or cache object stale? Read from disk
-        bcache = branchcache.fromfile(repo)
+    def __getitem__(self, repo):
+        self.updatecache(repo)
+        return self._per_filter[repo.filtername]
+
+    def updatecache(self, repo):
+        """Update the cache for the given filtered view on a repository"""
+        # This can trigger updates for the caches for subsets of the filtered
+        # view, e.g. when there is no cache for this filtered view or the cache
+        # is stale.
 
-    revs = []
-    if bcache is None:
-        # no (fresh) cache available anymore, perhaps we can re-use
-        # the cache for a subset, then extend that to add info on missing
-        # revisions.
-        subsetname = subsettable.get(filtername)
-        if subsetname is not None:
-            subset = repo.filtered(subsetname)
-            bcache = subset.branchmap().copy()
-            extrarevs = subset.changelog.filteredrevs - cl.filteredrevs
-            revs.extend(r for r in extrarevs if r <= bcache.tiprev)
-        else:
-            # nothing to fall back on, start empty.
-            bcache = branchcache()
+        cl = repo.changelog
+        filtername = repo.filtername
+        bcache = self._per_filter.get(filtername)
+        if bcache is None or not bcache.validfor(repo):
+            # cache object missing or cache object stale? Read from disk
+            bcache = branchcache.fromfile(repo)
 
-    revs.extend(cl.revs(start=bcache.tiprev + 1))
-    if revs:
-        bcache.update(repo, revs)
+        revs = []
+        if bcache is None:
+            # no (fresh) cache available anymore, perhaps we can re-use
+            # the cache for a subset, then extend that to add info on missing
+            # revisions.
+            subsetname = subsettable.get(filtername)
+            if subsetname is not None:
+                subset = repo.filtered(subsetname)
+                bcache = self[subset].copy()
+                extrarevs = subset.changelog.filteredrevs - cl.filteredrevs
+                revs.extend(r for r in extrarevs if r <= bcache.tiprev)
+            else:
+                # nothing to fall back on, start empty.
+                bcache = branchcache()
 
-    assert bcache.validfor(repo), filtername
-    repo._branchcaches[repo.filtername] = bcache
+        revs.extend(cl.revs(start=bcache.tiprev + 1))
+        if revs:
+            bcache.update(repo, revs)
 
-def replacecache(repo, bm):
-    """Replace the branchmap cache for a repo with a branch mapping.
+        assert bcache.validfor(repo), filtername
+        self._per_filter[repo.filtername] = bcache
+
+    def replace(self, repo, remotebranchmap):
+        """Replace the branchmap cache for a repo with a branch mapping.
+
+        This is likely only called during clone with a branch map from a
+        remote.
 
-    This is likely only called during clone with a branch map from a remote.
-    """
-    cl = repo.changelog
-    clrev = cl.rev
-    clbranchinfo = cl.branchinfo
-    rbheads = []
-    closed = []
-    for bheads in bm.itervalues():
-        rbheads.extend(bheads)
-        for h in bheads:
-            r = clrev(h)
-            b, c = clbranchinfo(r)
-            if c:
-                closed.append(h)
+        """
+        cl = repo.changelog
+        clrev = cl.rev
+        clbranchinfo = cl.branchinfo
+        rbheads = []
+        closed = []
+        for bheads in remotebranchmap.itervalues():
+            rbheads += bheads
+            for h in bheads:
+                r = clrev(h)
+                b, c = clbranchinfo(r)
+                if c:
+                    closed.append(h)
 
-    if rbheads:
-        rtiprev = max((int(clrev(node))
-                for node in rbheads))
-        cache = branchcache(bm,
-                            repo[rtiprev].node(),
-                            rtiprev,
-                            closednodes=closed)
+        if rbheads:
+            rtiprev = max((int(clrev(node)) for node in rbheads))
+            cache = branchcache(
+                remotebranchmap, repo[rtiprev].node(), rtiprev,
+                closednodes=closed)
 
-        # Try to stick it as low as possible
-        # filter above served are unlikely to be fetch from a clone
-        for candidate in ('base', 'immutable', 'served'):
-            rview = repo.filtered(candidate)
-            if cache.validfor(rview):
-                repo._branchcaches[candidate] = cache
-                cache.write(rview)
-                break
+            # Try to stick it as low as possible
+            # filter above served are unlikely to be fetch from a clone
+            for candidate in ('base', 'immutable', 'served'):
+                rview = repo.filtered(candidate)
+                if cache.validfor(rview):
+                    self._per_filter[candidate] = cache
+                    cache.write(rview)
+                    return
+
+    def clear(self):
+        self._per_filter.clear()
+
 
 class branchcache(dict):
     """A dict like object that hold branches heads cache.
--- a/mercurial/localrepo.py	Thu Feb 07 21:16:25 2019 -0800
+++ b/mercurial/localrepo.py	Mon Jan 21 17:37:33 2019 +0000
@@ -992,7 +992,7 @@
 
         self._dirstatevalidatewarned = False
 
-        self._branchcaches = {}
+        self._branchcaches = branchmap.BranchMapCache()
         self._revbranchcache = None
         self._filterpats = {}
         self._datafilters = {}
@@ -1520,8 +1520,7 @@
     def branchmap(self):
         '''returns a dictionary {branch: [branchheads]} with branchheads
         ordered by increasing revision number'''
-        branchmap.updatecache(self)
-        return self._branchcaches[self.filtername]
+        return self._branchcaches[self]
 
     @unfilteredmethod
     def revbranchcache(self):
@@ -2073,9 +2072,9 @@
             return
 
         if tr is None or tr.changes['origrepolen'] < len(self):
-            # updating the unfiltered branchmap should refresh all the others,
+            # accessing the 'ser ved' branchmap should refresh all the others,
             self.ui.debug('updating the branch cache\n')
-            branchmap.updatecache(self.filtered('served'))
+            self.filtered('served').branchmap()
 
         if full:
             rbc = self.revbranchcache()
@@ -2093,7 +2092,7 @@
             # can't use delattr on proxy
             del self.__dict__[r'_tagscache']
 
-        self.unfiltered()._branchcaches.clear()
+        self._branchcaches.clear()
         self.invalidatevolatilesets()
         self._sparsesignaturecache.clear()
 
--- a/mercurial/statichttprepo.py	Thu Feb 07 21:16:25 2019 -0800
+++ b/mercurial/statichttprepo.py	Mon Jan 21 17:37:33 2019 +0000
@@ -13,6 +13,7 @@
 
 from .i18n import _
 from . import (
+    branchmap,
     changelog,
     error,
     localrepo,
@@ -193,7 +194,7 @@
         self.changelog = changelog.changelog(self.svfs)
         self._tags = None
         self.nodetagscache = None
-        self._branchcaches = {}
+        self._branchcaches = branchmap.BranchMapCache()
         self._revbranchcache = None
         self.encodepats = None
         self.decodepats = None
--- a/mercurial/streamclone.py	Thu Feb 07 21:16:25 2019 -0800
+++ b/mercurial/streamclone.py	Mon Jan 21 17:37:33 2019 +0000
@@ -13,7 +13,6 @@
 
 from .i18n import _
 from . import (
-    branchmap,
     cacheutil,
     error,
     narrowspec,
@@ -174,7 +173,7 @@
         repo._writerequirements()
 
         if rbranchmap:
-            branchmap.replacecache(repo, rbranchmap)
+            repo._branchcaches.replace(repo, rbranchmap)
 
         repo.invalidate()