comparison src/topic/topicmap.py @ 1890:e846b8f402d0

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.
author Pierre-Yves David <pierre-yves.david@fb.com>
date Sun, 13 Mar 2016 23:44:04 +0000
parents d49f75eab6a3
children
comparison
equal deleted inserted replaced
1889:d9b929bcc3ad 1890:e846b8f402d0
1 from mercurial import branchmap 1 from mercurial import branchmap
2 from mercurial import encoding
3 from mercurial import error
4 from mercurial import scmutil
5 from mercurial import util
6 from mercurial.node import hex, bin, nullid
2 7
3 def _filename(repo): 8 def _filename(repo):
4 """name of a branchcache file for a given repo or repoview""" 9 """name of a branchcache file for a given repo or repoview"""
5 filename = "cache/topicmap" 10 filename = "cache/topicmap"
6 if repo.filtername: 11 if repo.filtername:
7 filename = '%s-%s' % (filename, repo.filtername) 12 filename = '%s-%s' % (filename, repo.filtername)
8 return filename 13 return filename
9 14
10 oldbranchcache = branchmap.branchcache 15 oldbranchcache = branchmap.branchcache
11 16
17 def _phaseshash(repo, maxrev):
18 revs = set()
19 cl = repo.changelog
20 fr = cl.filteredrevs
21 nm = cl.nodemap
22 for roots in repo._phasecache.phaseroots[1:]:
23 for n in roots:
24 r = nm.get(n)
25 if r not in fr and r < maxrev:
26 revs.add(r)
27 key = nullid
28 revs = sorted(revs)
29 if revs:
30 s = util.sha1()
31 for rev in revs:
32 s.update('%s;' % rev)
33 key = s.digest()
34 return key
35
12 class topiccache(oldbranchcache): 36 class topiccache(oldbranchcache):
13 37
14 def __init__(self, *args, **kwargs): 38 def __init__(self, *args, **kwargs):
15 otherbranchcache = branchmap.branchcache 39 otherbranchcache = branchmap.branchcache
16 try: 40 try:
17 # super() call may fail otherwise 41 # super() call may fail otherwise
18 branchmap.branchcache = oldbranchcache 42 branchmap.branchcache = oldbranchcache
19 return super(topiccache, self).__init__(*args, **kwargs) 43 super(topiccache, self).__init__(*args, **kwargs)
44 if self.filteredhash is None:
45 self.filteredhash = nullid
46 self.phaseshash = nullid
20 finally: 47 finally:
21 branchmap.branchcache = otherbranchcache 48 branchmap.branchcache = otherbranchcache
49
50 def copy(self):
51 """return an deep copy of the branchcache object"""
52 new = topiccache(self, self.tipnode, self.tiprev, self.filteredhash,
53 self._closednodes)
54 if self.filteredhash is None:
55 self.filteredhash = nullid
56 new.phaseshash = self.phaseshash
57 return new
22 58
23 def branchtip(self, branch, topic=''): 59 def branchtip(self, branch, topic=''):
24 '''Return the tipmost open head on branch head, otherwise return the 60 '''Return the tipmost open head on branch head, otherwise return the
25 tipmost closed head on branch. 61 tipmost closed head on branch.
26 Raise KeyError for unknown branch.''' 62 Raise KeyError for unknown branch.'''
31 def branchheads(self, branch, closed=False, topic=''): 67 def branchheads(self, branch, closed=False, topic=''):
32 if topic: 68 if topic:
33 branch = '%s:%s' % (branch, topic) 69 branch = '%s:%s' % (branch, topic)
34 return super(topiccache, self).branchheads(branch, closed=closed) 70 return super(topiccache, self).branchheads(branch, closed=closed)
35 71
72 def validfor(self, repo):
73 """Is the cache content valid regarding a repo
74
75 - False when cached tipnode is unknown or if we detect a strip.
76 - True when cache is up to date or a subset of current repo."""
77 # This is copy paste of mercurial.branchmap.branchcache.validfor in
78 # 69077c65919d With a small changes to the cache key handling to
79 # include phase information that impact the topic cache.
80 #
81 # All code changes should be flagged on site.
82 try:
83 if (self.tipnode == repo.changelog.node(self.tiprev)):
84 fh = scmutil.filteredhash(repo, self.tiprev)
85 if fh is None:
86 fh = nullid
87 if ((self.filteredhash == fh)
88 and (self.phaseshash == _phaseshash(repo, self.tiprev))):
89 return True
90 return False
91 except IndexError:
92 return False
93
36 def write(self, repo): 94 def write(self, repo):
37 # The cache key needs to take phase root in account because change to 95 # This is copy paste of mercurial.branchmap.branchcache.write in
38 # what is public affect topics. We can't write on disk until we have this. 96 # 69077c65919d With a small changes to the cache key handling to
39 return 97 # include phase information that impact the topic cache.
98 #
99 # All code changes should be flagged on site.
100 try:
101 f = repo.vfs(_filename(repo), "w", atomictemp=True)
102 cachekey = [hex(self.tipnode), str(self.tiprev)]
103 # [CHANGE] we need a hash in all cases
104 assert self.filteredhash is not None
105 cachekey.append(hex(self.filteredhash))
106 cachekey.append(hex(self.phaseshash))
107 f.write(" ".join(cachekey) + '\n')
108 nodecount = 0
109 for label, nodes in sorted(self.iteritems()):
110 for node in nodes:
111 nodecount += 1
112 if node in self._closednodes:
113 state = 'c'
114 else:
115 state = 'o'
116 f.write("%s %s %s\n" % (hex(node), state,
117 encoding.fromlocal(label)))
118 f.close()
119 repo.ui.log('branchcache',
120 'wrote %s branch cache with %d labels and %d nodes\n',
121 repo.filtername, len(self), nodecount)
122 except (IOError, OSError, error.Abort) as inst:
123 repo.ui.debug("couldn't write branch cache: %s\n" % inst)
124 # Abort may be raise by read only opener
125 pass
40 126
41 def update(self, repo, revgen): 127 def update(self, repo, revgen):
42 """Given a branchhead cache, self, that may have extra nodes or be 128 """Given a branchhead cache, self, that may have extra nodes or be
43 missing heads, and a generator of nodes that are strictly a superset of 129 missing heads, and a generator of nodes that are strictly a superset of
44 heads missing, this function updates self to be correct. 130 heads missing, this function updates self to be correct.
54 branch = info[0] 140 branch = info[0]
55 if topic: 141 if topic:
56 branch = '%s:%s' % (branch, topic) 142 branch = '%s:%s' % (branch, topic)
57 return (branch, info[1]) 143 return (branch, info[1])
58 repo.revbranchcache().branchinfo = branchinfo 144 repo.revbranchcache().branchinfo = branchinfo
59 return super(topiccache, self).update(repo, revgen) 145 super(topiccache, self).update(repo, revgen)
146 if self.filteredhash is None:
147 self.filteredhash = nullid
148 self.phaseshash = _phaseshash(repo, self.tiprev)
60 finally: 149 finally:
61 repo.revbranchcache().branchinfo = oldgetbranchinfo 150 repo.revbranchcache().branchinfo = oldgetbranchinfo
151
152 def readtopicmap(repo):
153 # This is copy paste of mercurial.branchmap.read in 69077c65919d
154 # With a small changes to the cache key handling to include phase
155 # information that impact the topic cache.
156 #
157 # All code changes should be flagged on site.
158 try:
159 f = repo.vfs(_filename(repo))
160 lines = f.read().split('\n')
161 f.close()
162 except (IOError, OSError):
163 return None
164
165 try:
166 cachekey = lines.pop(0).split(" ", 2)
167 last, lrev = cachekey[:2]
168 last, lrev = bin(last), int(lrev)
169 filteredhash = bin(cachekey[2]) # [CHANGE] unconditional filteredhash
170 partial = branchcache(tipnode=last, tiprev=lrev,
171 filteredhash=filteredhash)
172 partial.phaseshash = bin(cachekey[3]) # [CHANGE] read phaseshash
173 if not partial.validfor(repo):
174 # invalidate the cache
175 raise ValueError('tip differs')
176 cl = repo.changelog
177 for l in lines:
178 if not l:
179 continue
180 node, state, label = l.split(" ", 2)
181 if state not in 'oc':
182 raise ValueError('invalid branch state')
183 label = encoding.tolocal(label.strip())
184 node = bin(node)
185 if not cl.hasnode(node):
186 raise ValueError('node %s does not exist' % hex(node))
187 partial.setdefault(label, []).append(node)
188 if state == 'c':
189 partial._closednodes.add(node)
190 except KeyboardInterrupt:
191 raise
192 except Exception as inst:
193 if repo.ui.debugflag:
194 msg = 'invalid branchheads cache'
195 if repo.filtername is not None:
196 msg += ' (%s)' % repo.filtername
197 msg += ': %s\n'
198 repo.ui.debug(msg % inst)
199 partial = None
200 return partial