branchmap: make branchcache responsible for reading
Encapsulate reading in a classmethod, to make it clear what kind of object is
being handled.
This is part of a stack of refactoring changes to help performance improvements
down the line.
Differential Revision: https://phab.mercurial-scm.org/D5635
--- a/contrib/perf.py Mon Feb 04 09:10:07 2019 -0800
+++ b/contrib/perf.py Mon Jan 21 16:04:48 2019 +0000
@@ -2409,10 +2409,15 @@
# add unfiltered
allfilters.append(None)
- branchcacheread = safeattrsetter(branchmap, b'read')
+ if util.safehasattr(branchmap.branchcache, 'fromfile'):
+ branchcacheread = safeattrsetter(branchmap.branchcache, b'fromfile')
+ branchcacheread.set(classmethod(lambda *args: None))
+ else:
+ # older versions
+ branchcacheread = safeattrsetter(branchmap, b'read')
+ branchcacheread.set(lambda *args: None)
branchcachewrite = safeattrsetter(branchmap.branchcache, b'write')
- branchcacheread.set(lambda repo: None)
- branchcachewrite.set(lambda bc, repo: None)
+ branchcachewrite.set(lambda *args: None)
try:
for name in allfilters:
printname = name
@@ -2556,9 +2561,15 @@
repo.branchmap() # make sure we have a relevant, up to date branchmap
+ try:
+ fromfile = branchmap.branchcache.fromfile
+ except AttributeError:
+ # older versions
+ fromfile = branchmap.read
+
currentfilter = filter
# try once without timer, the filter may not be cached
- while branchmap.read(repo) is None:
+ while fromfile(repo) is None:
currentfilter = subsettable.get(currentfilter)
if currentfilter is None:
raise error.Abort(b'No branchmap cached for %s repo'
@@ -2569,7 +2580,7 @@
if clearrevlogs:
clearchangelog(repo)
def bench():
- branchmap.read(repo)
+ fromfile(repo)
timer(bench, setup=setup)
fm.end()
--- a/mercurial/branchmap.py Mon Feb 04 09:10:07 2019 -0800
+++ b/mercurial/branchmap.py Mon Jan 21 16:04:48 2019 +0000
@@ -30,63 +30,6 @@
pack_into = struct.pack_into
unpack_from = struct.unpack_from
-def _filename(repo):
- """name of a branchcache file for a given repo or repoview"""
- filename = "branch2"
- if repo.filtername:
- filename = '%s-%s' % (filename, repo.filtername)
- return filename
-
-def read(repo):
- f = None
- try:
- f = repo.cachevfs(_filename(repo))
- lineiter = iter(f)
- cachekey = next(lineiter).rstrip('\n').split(" ", 2)
- last, lrev = cachekey[:2]
- last, lrev = bin(last), int(lrev)
- filteredhash = None
- if len(cachekey) > 2:
- filteredhash = bin(cachekey[2])
- bcache = branchcache(tipnode=last, tiprev=lrev,
- filteredhash=filteredhash)
- if not bcache.validfor(repo):
- # invalidate the cache
- raise ValueError(r'tip differs')
- cl = repo.changelog
- for l in lineiter:
- l = l.rstrip('\n')
- if not l:
- continue
- node, state, label = l.split(" ", 2)
- if state not in 'oc':
- raise ValueError(r'invalid branch state')
- label = encoding.tolocal(label.strip())
- node = bin(node)
- if not cl.hasnode(node):
- raise ValueError(
- r'node %s does not exist' % pycompat.sysstr(hex(node)))
- bcache.setdefault(label, []).append(node)
- if state == 'c':
- bcache._closednodes.add(node)
-
- except (IOError, OSError):
- return None
-
- except Exception as inst:
- if repo.ui.debugflag:
- msg = 'invalid branchheads cache'
- if repo.filtername is not None:
- msg += ' (%s)' % repo.filtername
- msg += ': %s\n'
- repo.ui.debug(msg % pycompat.bytestr(inst))
- bcache = None
-
- finally:
- if f:
- f.close()
-
- return bcache
### Nearest subset relation
# Nearest subset of filter X is a filter Y so that:
@@ -107,7 +50,7 @@
revs = []
if bcache is None or not bcache.validfor(repo):
- bcache = read(repo)
+ bcache = branchcache.fromfile(repo)
if bcache is None:
subsetname = subsettable.get(filtername)
if subsetname is None:
@@ -181,6 +124,64 @@
This field can be used to avoid changelog reads when determining if a
branch head closes a branch or not.
"""
+ @classmethod
+ def fromfile(cls, repo):
+ f = None
+ try:
+ f = repo.cachevfs(cls._filename(repo))
+ lineiter = iter(f)
+ cachekey = next(lineiter).rstrip('\n').split(" ", 2)
+ last, lrev = cachekey[:2]
+ last, lrev = bin(last), int(lrev)
+ filteredhash = None
+ if len(cachekey) > 2:
+ filteredhash = bin(cachekey[2])
+ bcache = cls(tipnode=last, tiprev=lrev, filteredhash=filteredhash)
+ if not bcache.validfor(repo):
+ # invalidate the cache
+ raise ValueError(r'tip differs')
+ cl = repo.changelog
+ for line in lineiter:
+ line = line.rstrip('\n')
+ if not line:
+ continue
+ node, state, label = line.split(" ", 2)
+ if state not in 'oc':
+ raise ValueError(r'invalid branch state')
+ label = encoding.tolocal(label.strip())
+ node = bin(node)
+ if not cl.hasnode(node):
+ raise ValueError(
+ r'node %s does not exist' % pycompat.sysstr(hex(node)))
+ bcache.setdefault(label, []).append(node)
+ if state == 'c':
+ bcache._closednodes.add(node)
+
+ except (IOError, OSError):
+ return None
+
+ except Exception as inst:
+ if repo.ui.debugflag:
+ msg = 'invalid branchheads cache'
+ if repo.filtername is not None:
+ msg += ' (%s)' % repo.filtername
+ msg += ': %s\n'
+ repo.ui.debug(msg % pycompat.bytestr(inst))
+ bcache = None
+
+ finally:
+ if f:
+ f.close()
+
+ return bcache
+
+ @staticmethod
+ def _filename(repo):
+ """name of a branchcache file for a given repo or repoview"""
+ filename = "branch2"
+ if repo.filtername:
+ filename = '%s-%s' % (filename, repo.filtername)
+ return filename
def __init__(self, entries=(), tipnode=nullid, tiprev=nullrev,
filteredhash=None, closednodes=None):
@@ -246,7 +247,7 @@
def write(self, repo):
try:
- f = repo.cachevfs(_filename(repo), "w", atomictemp=True)
+ f = repo.cachevfs(self._filename(repo), "w", atomictemp=True)
cachekey = [hex(self.tipnode), '%d' % self.tiprev]
if self.filteredhash is not None:
cachekey.append(hex(self.filteredhash))