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
--- 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()