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