# HG changeset patch # User Pierre-Yves David # Date 1457912644 0 # Node ID e846b8f402d0564361786fdc7b6e4c041c5e31a4 # Parent d9b929bcc3adb030dca9c0ea238d3db337ba4404 topicmap: write and read format from disc To prevent too awful performance we allow writing and reading topicmap cache. This is done with a lot of code duplication from core because core is not extensible enough. diff -r d9b929bcc3ad -r e846b8f402d0 src/topic/__init__.py --- a/src/topic/__init__.py Mon Mar 14 00:15:54 2016 +0000 +++ b/src/topic/__init__.py Sun Mar 13 23:44:04 2016 +0000 @@ -69,10 +69,12 @@ """use awful monkey patching to update the topic cache""" oldbranchcache = branchmap.branchcache oldfilename = branchmap._filename + oldread = branchmap.read oldcaches = getattr(repo, '_branchcaches', {}) try: branchmap.branchcache = topicmap.topiccache branchmap._filename = topicmap._filename + branchmap.read = topicmap.readtopicmap repo._branchcaches = getattr(repo, '_topiccaches', {}) yield repo._topiccaches = repo._branchcaches @@ -80,6 +82,7 @@ repo._branchcaches = oldcaches branchmap.branchcache = oldbranchcache branchmap._filename = oldfilename + branchmap.read = oldread def cgapply(orig, repo, *args, **kwargs): with usetopicmap(repo): diff -r d9b929bcc3ad -r e846b8f402d0 src/topic/topicmap.py --- a/src/topic/topicmap.py Mon Mar 14 00:15:54 2016 +0000 +++ b/src/topic/topicmap.py Sun Mar 13 23:44:04 2016 +0000 @@ -1,4 +1,9 @@ from mercurial import branchmap +from mercurial import encoding +from mercurial import error +from mercurial import scmutil +from mercurial import util +from mercurial.node import hex, bin, nullid def _filename(repo): """name of a branchcache file for a given repo or repoview""" @@ -9,6 +14,25 @@ oldbranchcache = branchmap.branchcache +def _phaseshash(repo, maxrev): + revs = set() + cl = repo.changelog + fr = cl.filteredrevs + nm = cl.nodemap + for roots in repo._phasecache.phaseroots[1:]: + for n in roots: + r = nm.get(n) + if r not in fr and r < maxrev: + revs.add(r) + key = nullid + revs = sorted(revs) + if revs: + s = util.sha1() + for rev in revs: + s.update('%s;' % rev) + key = s.digest() + return key + class topiccache(oldbranchcache): def __init__(self, *args, **kwargs): @@ -16,10 +40,22 @@ try: # super() call may fail otherwise branchmap.branchcache = oldbranchcache - return super(topiccache, self).__init__(*args, **kwargs) + super(topiccache, self).__init__(*args, **kwargs) + if self.filteredhash is None: + self.filteredhash = nullid + self.phaseshash = nullid finally: branchmap.branchcache = otherbranchcache + def copy(self): + """return an deep copy of the branchcache object""" + new = topiccache(self, self.tipnode, self.tiprev, self.filteredhash, + self._closednodes) + if self.filteredhash is None: + self.filteredhash = nullid + new.phaseshash = self.phaseshash + return new + def branchtip(self, branch, topic=''): '''Return the tipmost open head on branch head, otherwise return the tipmost closed head on branch. @@ -33,10 +69,60 @@ branch = '%s:%s' % (branch, topic) return super(topiccache, self).branchheads(branch, closed=closed) + def validfor(self, repo): + """Is the cache content valid regarding a repo + + - False when cached tipnode is unknown or if we detect a strip. + - True when cache is up to date or a subset of current repo.""" + # This is copy paste of mercurial.branchmap.branchcache.validfor in + # 69077c65919d With a small changes to the cache key handling to + # include phase information that impact the topic cache. + # + # All code changes should be flagged on site. + try: + if (self.tipnode == repo.changelog.node(self.tiprev)): + fh = scmutil.filteredhash(repo, self.tiprev) + if fh is None: + fh = nullid + if ((self.filteredhash == fh) + and (self.phaseshash == _phaseshash(repo, self.tiprev))): + return True + return False + except IndexError: + return False + def write(self, repo): - # The cache key needs to take phase root in account because change to - # what is public affect topics. We can't write on disk until we have this. - return + # This is copy paste of mercurial.branchmap.branchcache.write in + # 69077c65919d With a small changes to the cache key handling to + # include phase information that impact the topic cache. + # + # All code changes should be flagged on site. + try: + f = repo.vfs(_filename(repo), "w", atomictemp=True) + cachekey = [hex(self.tipnode), str(self.tiprev)] + # [CHANGE] we need a hash in all cases + assert self.filteredhash is not None + cachekey.append(hex(self.filteredhash)) + cachekey.append(hex(self.phaseshash)) + f.write(" ".join(cachekey) + '\n') + nodecount = 0 + for label, nodes in sorted(self.iteritems()): + for node in nodes: + nodecount += 1 + if node in self._closednodes: + state = 'c' + else: + state = 'o' + f.write("%s %s %s\n" % (hex(node), state, + encoding.fromlocal(label))) + f.close() + repo.ui.log('branchcache', + 'wrote %s branch cache with %d labels and %d nodes\n', + repo.filtername, len(self), nodecount) + except (IOError, OSError, error.Abort) as inst: + repo.ui.debug("couldn't write branch cache: %s\n" % inst) + # Abort may be raise by read only opener + pass def update(self, repo, revgen): """Given a branchhead cache, self, that may have extra nodes or be @@ -56,6 +142,59 @@ branch = '%s:%s' % (branch, topic) return (branch, info[1]) repo.revbranchcache().branchinfo = branchinfo - return super(topiccache, self).update(repo, revgen) + super(topiccache, self).update(repo, revgen) + if self.filteredhash is None: + self.filteredhash = nullid + self.phaseshash = _phaseshash(repo, self.tiprev) finally: repo.revbranchcache().branchinfo = oldgetbranchinfo + +def readtopicmap(repo): + # This is copy paste of mercurial.branchmap.read in 69077c65919d + # With a small changes to the cache key handling to include phase + # information that impact the topic cache. + # + # All code changes should be flagged on site. + try: + f = repo.vfs(_filename(repo)) + lines = f.read().split('\n') + f.close() + except (IOError, OSError): + return None + + try: + cachekey = lines.pop(0).split(" ", 2) + last, lrev = cachekey[:2] + last, lrev = bin(last), int(lrev) + filteredhash = bin(cachekey[2]) # [CHANGE] unconditional filteredhash + partial = branchcache(tipnode=last, tiprev=lrev, + filteredhash=filteredhash) + partial.phaseshash = bin(cachekey[3]) # [CHANGE] read phaseshash + if not partial.validfor(repo): + # invalidate the cache + raise ValueError('tip differs') + cl = repo.changelog + for l in lines: + if not l: + continue + node, state, label = l.split(" ", 2) + if state not in 'oc': + raise ValueError('invalid branch state') + label = encoding.tolocal(label.strip()) + node = bin(node) + if not cl.hasnode(node): + raise ValueError('node %s does not exist' % hex(node)) + partial.setdefault(label, []).append(node) + if state == 'c': + partial._closednodes.add(node) + except KeyboardInterrupt: + raise + 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 % inst) + partial = None + return partial