##// END OF EJS Templates
branchcache: fix the docstring of _verifybranch()...
Pulkit Goyal -
r42301:d9dc0896 default
parent child Browse files
Show More
@@ -1,668 +1,667 b''
1 # branchmap.py - logic to computes, maintain and stores branchmap for local repo
1 # branchmap.py - logic to computes, maintain and stores branchmap for local repo
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import struct
10 import struct
11
11
12 from .node import (
12 from .node import (
13 bin,
13 bin,
14 hex,
14 hex,
15 nullid,
15 nullid,
16 nullrev,
16 nullrev,
17 )
17 )
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 error,
21 pycompat,
21 pycompat,
22 scmutil,
22 scmutil,
23 util,
23 util,
24 )
24 )
25 from .utils import (
25 from .utils import (
26 stringutil,
26 stringutil,
27 )
27 )
28
28
29 calcsize = struct.calcsize
29 calcsize = struct.calcsize
30 pack_into = struct.pack_into
30 pack_into = struct.pack_into
31 unpack_from = struct.unpack_from
31 unpack_from = struct.unpack_from
32
32
33
33
34 ### Nearest subset relation
34 ### Nearest subset relation
35 # Nearest subset of filter X is a filter Y so that:
35 # Nearest subset of filter X is a filter Y so that:
36 # * Y is included in X,
36 # * Y is included in X,
37 # * X - Y is as small as possible.
37 # * X - Y is as small as possible.
38 # This create and ordering used for branchmap purpose.
38 # This create and ordering used for branchmap purpose.
39 # the ordering may be partial
39 # the ordering may be partial
40 subsettable = {None: 'visible',
40 subsettable = {None: 'visible',
41 'visible-hidden': 'visible',
41 'visible-hidden': 'visible',
42 'visible': 'served',
42 'visible': 'served',
43 'served.hidden': 'served',
43 'served.hidden': 'served',
44 'served': 'immutable',
44 'served': 'immutable',
45 'immutable': 'base'}
45 'immutable': 'base'}
46
46
47
47
48 class BranchMapCache(object):
48 class BranchMapCache(object):
49 """mapping of filtered views of repo with their branchcache"""
49 """mapping of filtered views of repo with their branchcache"""
50 def __init__(self):
50 def __init__(self):
51 self._per_filter = {}
51 self._per_filter = {}
52
52
53 def __getitem__(self, repo):
53 def __getitem__(self, repo):
54 self.updatecache(repo)
54 self.updatecache(repo)
55 return self._per_filter[repo.filtername]
55 return self._per_filter[repo.filtername]
56
56
57 def updatecache(self, repo):
57 def updatecache(self, repo):
58 """Update the cache for the given filtered view on a repository"""
58 """Update the cache for the given filtered view on a repository"""
59 # This can trigger updates for the caches for subsets of the filtered
59 # This can trigger updates for the caches for subsets of the filtered
60 # view, e.g. when there is no cache for this filtered view or the cache
60 # view, e.g. when there is no cache for this filtered view or the cache
61 # is stale.
61 # is stale.
62
62
63 cl = repo.changelog
63 cl = repo.changelog
64 filtername = repo.filtername
64 filtername = repo.filtername
65 bcache = self._per_filter.get(filtername)
65 bcache = self._per_filter.get(filtername)
66 if bcache is None or not bcache.validfor(repo):
66 if bcache is None or not bcache.validfor(repo):
67 # cache object missing or cache object stale? Read from disk
67 # cache object missing or cache object stale? Read from disk
68 bcache = branchcache.fromfile(repo)
68 bcache = branchcache.fromfile(repo)
69
69
70 revs = []
70 revs = []
71 if bcache is None:
71 if bcache is None:
72 # no (fresh) cache available anymore, perhaps we can re-use
72 # no (fresh) cache available anymore, perhaps we can re-use
73 # the cache for a subset, then extend that to add info on missing
73 # the cache for a subset, then extend that to add info on missing
74 # revisions.
74 # revisions.
75 subsetname = subsettable.get(filtername)
75 subsetname = subsettable.get(filtername)
76 if subsetname is not None:
76 if subsetname is not None:
77 subset = repo.filtered(subsetname)
77 subset = repo.filtered(subsetname)
78 bcache = self[subset].copy()
78 bcache = self[subset].copy()
79 extrarevs = subset.changelog.filteredrevs - cl.filteredrevs
79 extrarevs = subset.changelog.filteredrevs - cl.filteredrevs
80 revs.extend(r for r in extrarevs if r <= bcache.tiprev)
80 revs.extend(r for r in extrarevs if r <= bcache.tiprev)
81 else:
81 else:
82 # nothing to fall back on, start empty.
82 # nothing to fall back on, start empty.
83 bcache = branchcache()
83 bcache = branchcache()
84
84
85 revs.extend(cl.revs(start=bcache.tiprev + 1))
85 revs.extend(cl.revs(start=bcache.tiprev + 1))
86 if revs:
86 if revs:
87 bcache.update(repo, revs)
87 bcache.update(repo, revs)
88
88
89 assert bcache.validfor(repo), filtername
89 assert bcache.validfor(repo), filtername
90 self._per_filter[repo.filtername] = bcache
90 self._per_filter[repo.filtername] = bcache
91
91
92 def replace(self, repo, remotebranchmap):
92 def replace(self, repo, remotebranchmap):
93 """Replace the branchmap cache for a repo with a branch mapping.
93 """Replace the branchmap cache for a repo with a branch mapping.
94
94
95 This is likely only called during clone with a branch map from a
95 This is likely only called during clone with a branch map from a
96 remote.
96 remote.
97
97
98 """
98 """
99 cl = repo.changelog
99 cl = repo.changelog
100 clrev = cl.rev
100 clrev = cl.rev
101 clbranchinfo = cl.branchinfo
101 clbranchinfo = cl.branchinfo
102 rbheads = []
102 rbheads = []
103 closed = []
103 closed = []
104 for bheads in remotebranchmap.itervalues():
104 for bheads in remotebranchmap.itervalues():
105 rbheads += bheads
105 rbheads += bheads
106 for h in bheads:
106 for h in bheads:
107 r = clrev(h)
107 r = clrev(h)
108 b, c = clbranchinfo(r)
108 b, c = clbranchinfo(r)
109 if c:
109 if c:
110 closed.append(h)
110 closed.append(h)
111
111
112 if rbheads:
112 if rbheads:
113 rtiprev = max((int(clrev(node)) for node in rbheads))
113 rtiprev = max((int(clrev(node)) for node in rbheads))
114 cache = branchcache(
114 cache = branchcache(
115 remotebranchmap, repo[rtiprev].node(), rtiprev,
115 remotebranchmap, repo[rtiprev].node(), rtiprev,
116 closednodes=closed)
116 closednodes=closed)
117
117
118 # Try to stick it as low as possible
118 # Try to stick it as low as possible
119 # filter above served are unlikely to be fetch from a clone
119 # filter above served are unlikely to be fetch from a clone
120 for candidate in ('base', 'immutable', 'served'):
120 for candidate in ('base', 'immutable', 'served'):
121 rview = repo.filtered(candidate)
121 rview = repo.filtered(candidate)
122 if cache.validfor(rview):
122 if cache.validfor(rview):
123 self._per_filter[candidate] = cache
123 self._per_filter[candidate] = cache
124 cache.write(rview)
124 cache.write(rview)
125 return
125 return
126
126
127 def clear(self):
127 def clear(self):
128 self._per_filter.clear()
128 self._per_filter.clear()
129
129
130 def _unknownnode(node):
130 def _unknownnode(node):
131 """ raises ValueError when branchcache found a node which does not exists
131 """ raises ValueError when branchcache found a node which does not exists
132 """
132 """
133 raise ValueError(r'node %s does not exist' % pycompat.sysstr(hex(node)))
133 raise ValueError(r'node %s does not exist' % pycompat.sysstr(hex(node)))
134
134
135 class branchcache(object):
135 class branchcache(object):
136 """A dict like object that hold branches heads cache.
136 """A dict like object that hold branches heads cache.
137
137
138 This cache is used to avoid costly computations to determine all the
138 This cache is used to avoid costly computations to determine all the
139 branch heads of a repo.
139 branch heads of a repo.
140
140
141 The cache is serialized on disk in the following format:
141 The cache is serialized on disk in the following format:
142
142
143 <tip hex node> <tip rev number> [optional filtered repo hex hash]
143 <tip hex node> <tip rev number> [optional filtered repo hex hash]
144 <branch head hex node> <open/closed state> <branch name>
144 <branch head hex node> <open/closed state> <branch name>
145 <branch head hex node> <open/closed state> <branch name>
145 <branch head hex node> <open/closed state> <branch name>
146 ...
146 ...
147
147
148 The first line is used to check if the cache is still valid. If the
148 The first line is used to check if the cache is still valid. If the
149 branch cache is for a filtered repo view, an optional third hash is
149 branch cache is for a filtered repo view, an optional third hash is
150 included that hashes the hashes of all filtered revisions.
150 included that hashes the hashes of all filtered revisions.
151
151
152 The open/closed state is represented by a single letter 'o' or 'c'.
152 The open/closed state is represented by a single letter 'o' or 'c'.
153 This field can be used to avoid changelog reads when determining if a
153 This field can be used to avoid changelog reads when determining if a
154 branch head closes a branch or not.
154 branch head closes a branch or not.
155 """
155 """
156
156
157 def __init__(self, entries=(), tipnode=nullid, tiprev=nullrev,
157 def __init__(self, entries=(), tipnode=nullid, tiprev=nullrev,
158 filteredhash=None, closednodes=None, hasnode=None):
158 filteredhash=None, closednodes=None, hasnode=None):
159 """ hasnode is a function which can be used to verify whether changelog
159 """ hasnode is a function which can be used to verify whether changelog
160 has a given node or not. If it's not provided, we assume that every node
160 has a given node or not. If it's not provided, we assume that every node
161 we have exists in changelog """
161 we have exists in changelog """
162 self.tipnode = tipnode
162 self.tipnode = tipnode
163 self.tiprev = tiprev
163 self.tiprev = tiprev
164 self.filteredhash = filteredhash
164 self.filteredhash = filteredhash
165 # closednodes is a set of nodes that close their branch. If the branch
165 # closednodes is a set of nodes that close their branch. If the branch
166 # cache has been updated, it may contain nodes that are no longer
166 # cache has been updated, it may contain nodes that are no longer
167 # heads.
167 # heads.
168 if closednodes is None:
168 if closednodes is None:
169 self._closednodes = set()
169 self._closednodes = set()
170 else:
170 else:
171 self._closednodes = closednodes
171 self._closednodes = closednodes
172 self._entries = dict(entries)
172 self._entries = dict(entries)
173 # whether closed nodes are verified or not
173 # whether closed nodes are verified or not
174 self._closedverified = False
174 self._closedverified = False
175 # branches for which nodes are verified
175 # branches for which nodes are verified
176 self._verifiedbranches = set()
176 self._verifiedbranches = set()
177 self._hasnode = hasnode
177 self._hasnode = hasnode
178 if self._hasnode is None:
178 if self._hasnode is None:
179 self._hasnode = lambda x: True
179 self._hasnode = lambda x: True
180
180
181 def _verifyclosed(self):
181 def _verifyclosed(self):
182 """ verify the closed nodes we have """
182 """ verify the closed nodes we have """
183 if self._closedverified:
183 if self._closedverified:
184 return
184 return
185 for node in self._closednodes:
185 for node in self._closednodes:
186 if not self._hasnode(node):
186 if not self._hasnode(node):
187 _unknownnode(node)
187 _unknownnode(node)
188
188
189 self._closedverified = True
189 self._closedverified = True
190
190
191 def _verifybranch(self, branch):
191 def _verifybranch(self, branch):
192 """ verify head nodes for the given branch. If branch is None, verify
192 """ verify head nodes for the given branch. """
193 for all the branches """
194 if branch not in self._entries or branch in self._verifiedbranches:
193 if branch not in self._entries or branch in self._verifiedbranches:
195 return
194 return
196 for n in self._entries[branch]:
195 for n in self._entries[branch]:
197 if not self._hasnode(n):
196 if not self._hasnode(n):
198 _unknownnode(n)
197 _unknownnode(n)
199
198
200 self._verifiedbranches.add(branch)
199 self._verifiedbranches.add(branch)
201
200
202 def _verifyall(self):
201 def _verifyall(self):
203 """ verifies nodes of all the branches """
202 """ verifies nodes of all the branches """
204 for b in self._entries:
203 for b in self._entries:
205 self._verifybranch(b)
204 self._verifybranch(b)
206
205
207 def __iter__(self):
206 def __iter__(self):
208 return iter(self._entries)
207 return iter(self._entries)
209
208
210 def __setitem__(self, key, value):
209 def __setitem__(self, key, value):
211 self._entries[key] = value
210 self._entries[key] = value
212
211
213 def __getitem__(self, key):
212 def __getitem__(self, key):
214 self._verifybranch(key)
213 self._verifybranch(key)
215 return self._entries[key]
214 return self._entries[key]
216
215
217 def __contains__(self, key):
216 def __contains__(self, key):
218 self._verifybranch(key)
217 self._verifybranch(key)
219 return key in self._entries
218 return key in self._entries
220
219
221 def iteritems(self):
220 def iteritems(self):
222 self._verifyall()
221 self._verifyall()
223 return self._entries.iteritems()
222 return self._entries.iteritems()
224
223
225 def hasbranch(self, label):
224 def hasbranch(self, label):
226 """ checks whether a branch of this name exists or not """
225 """ checks whether a branch of this name exists or not """
227 self._verifybranch(label)
226 self._verifybranch(label)
228 return label in self._entries
227 return label in self._entries
229
228
230 @classmethod
229 @classmethod
231 def fromfile(cls, repo):
230 def fromfile(cls, repo):
232 f = None
231 f = None
233 try:
232 try:
234 f = repo.cachevfs(cls._filename(repo))
233 f = repo.cachevfs(cls._filename(repo))
235 lineiter = iter(f)
234 lineiter = iter(f)
236 cachekey = next(lineiter).rstrip('\n').split(" ", 2)
235 cachekey = next(lineiter).rstrip('\n').split(" ", 2)
237 last, lrev = cachekey[:2]
236 last, lrev = cachekey[:2]
238 last, lrev = bin(last), int(lrev)
237 last, lrev = bin(last), int(lrev)
239 filteredhash = None
238 filteredhash = None
240 hasnode = repo.changelog.hasnode
239 hasnode = repo.changelog.hasnode
241 if len(cachekey) > 2:
240 if len(cachekey) > 2:
242 filteredhash = bin(cachekey[2])
241 filteredhash = bin(cachekey[2])
243 bcache = cls(tipnode=last, tiprev=lrev, filteredhash=filteredhash,
242 bcache = cls(tipnode=last, tiprev=lrev, filteredhash=filteredhash,
244 hasnode=hasnode)
243 hasnode=hasnode)
245 if not bcache.validfor(repo):
244 if not bcache.validfor(repo):
246 # invalidate the cache
245 # invalidate the cache
247 raise ValueError(r'tip differs')
246 raise ValueError(r'tip differs')
248 bcache.load(repo, lineiter)
247 bcache.load(repo, lineiter)
249 except (IOError, OSError):
248 except (IOError, OSError):
250 return None
249 return None
251
250
252 except Exception as inst:
251 except Exception as inst:
253 if repo.ui.debugflag:
252 if repo.ui.debugflag:
254 msg = 'invalid branchheads cache'
253 msg = 'invalid branchheads cache'
255 if repo.filtername is not None:
254 if repo.filtername is not None:
256 msg += ' (%s)' % repo.filtername
255 msg += ' (%s)' % repo.filtername
257 msg += ': %s\n'
256 msg += ': %s\n'
258 repo.ui.debug(msg % pycompat.bytestr(inst))
257 repo.ui.debug(msg % pycompat.bytestr(inst))
259 bcache = None
258 bcache = None
260
259
261 finally:
260 finally:
262 if f:
261 if f:
263 f.close()
262 f.close()
264
263
265 return bcache
264 return bcache
266
265
267 def load(self, repo, lineiter):
266 def load(self, repo, lineiter):
268 """ fully loads the branchcache by reading from the file using the line
267 """ fully loads the branchcache by reading from the file using the line
269 iterator passed"""
268 iterator passed"""
270 for line in lineiter:
269 for line in lineiter:
271 line = line.rstrip('\n')
270 line = line.rstrip('\n')
272 if not line:
271 if not line:
273 continue
272 continue
274 node, state, label = line.split(" ", 2)
273 node, state, label = line.split(" ", 2)
275 if state not in 'oc':
274 if state not in 'oc':
276 raise ValueError(r'invalid branch state')
275 raise ValueError(r'invalid branch state')
277 label = encoding.tolocal(label.strip())
276 label = encoding.tolocal(label.strip())
278 node = bin(node)
277 node = bin(node)
279 self._entries.setdefault(label, []).append(node)
278 self._entries.setdefault(label, []).append(node)
280 if state == 'c':
279 if state == 'c':
281 self._closednodes.add(node)
280 self._closednodes.add(node)
282
281
283 @staticmethod
282 @staticmethod
284 def _filename(repo):
283 def _filename(repo):
285 """name of a branchcache file for a given repo or repoview"""
284 """name of a branchcache file for a given repo or repoview"""
286 filename = "branch2"
285 filename = "branch2"
287 if repo.filtername:
286 if repo.filtername:
288 filename = '%s-%s' % (filename, repo.filtername)
287 filename = '%s-%s' % (filename, repo.filtername)
289 return filename
288 return filename
290
289
291 def validfor(self, repo):
290 def validfor(self, repo):
292 """Is the cache content valid regarding a repo
291 """Is the cache content valid regarding a repo
293
292
294 - False when cached tipnode is unknown or if we detect a strip.
293 - False when cached tipnode is unknown or if we detect a strip.
295 - True when cache is up to date or a subset of current repo."""
294 - True when cache is up to date or a subset of current repo."""
296 try:
295 try:
297 return ((self.tipnode == repo.changelog.node(self.tiprev))
296 return ((self.tipnode == repo.changelog.node(self.tiprev))
298 and (self.filteredhash ==
297 and (self.filteredhash ==
299 scmutil.filteredhash(repo, self.tiprev)))
298 scmutil.filteredhash(repo, self.tiprev)))
300 except IndexError:
299 except IndexError:
301 return False
300 return False
302
301
303 def _branchtip(self, heads):
302 def _branchtip(self, heads):
304 '''Return tuple with last open head in heads and false,
303 '''Return tuple with last open head in heads and false,
305 otherwise return last closed head and true.'''
304 otherwise return last closed head and true.'''
306 tip = heads[-1]
305 tip = heads[-1]
307 closed = True
306 closed = True
308 for h in reversed(heads):
307 for h in reversed(heads):
309 if h not in self._closednodes:
308 if h not in self._closednodes:
310 tip = h
309 tip = h
311 closed = False
310 closed = False
312 break
311 break
313 return tip, closed
312 return tip, closed
314
313
315 def branchtip(self, branch):
314 def branchtip(self, branch):
316 '''Return the tipmost open head on branch head, otherwise return the
315 '''Return the tipmost open head on branch head, otherwise return the
317 tipmost closed head on branch.
316 tipmost closed head on branch.
318 Raise KeyError for unknown branch.'''
317 Raise KeyError for unknown branch.'''
319 return self._branchtip(self[branch])[0]
318 return self._branchtip(self[branch])[0]
320
319
321 def iteropen(self, nodes):
320 def iteropen(self, nodes):
322 return (n for n in nodes if n not in self._closednodes)
321 return (n for n in nodes if n not in self._closednodes)
323
322
324 def branchheads(self, branch, closed=False):
323 def branchheads(self, branch, closed=False):
325 self._verifybranch(branch)
324 self._verifybranch(branch)
326 heads = self._entries[branch]
325 heads = self._entries[branch]
327 if not closed:
326 if not closed:
328 heads = list(self.iteropen(heads))
327 heads = list(self.iteropen(heads))
329 return heads
328 return heads
330
329
331 def iterbranches(self):
330 def iterbranches(self):
332 for bn, heads in self.iteritems():
331 for bn, heads in self.iteritems():
333 yield (bn, heads) + self._branchtip(heads)
332 yield (bn, heads) + self._branchtip(heads)
334
333
335 def iterheads(self):
334 def iterheads(self):
336 """ returns all the heads """
335 """ returns all the heads """
337 self._verifyall()
336 self._verifyall()
338 return self._entries.itervalues()
337 return self._entries.itervalues()
339
338
340 def copy(self):
339 def copy(self):
341 """return an deep copy of the branchcache object"""
340 """return an deep copy of the branchcache object"""
342 return type(self)(
341 return type(self)(
343 self._entries, self.tipnode, self.tiprev, self.filteredhash,
342 self._entries, self.tipnode, self.tiprev, self.filteredhash,
344 self._closednodes)
343 self._closednodes)
345
344
346 def write(self, repo):
345 def write(self, repo):
347 try:
346 try:
348 f = repo.cachevfs(self._filename(repo), "w", atomictemp=True)
347 f = repo.cachevfs(self._filename(repo), "w", atomictemp=True)
349 cachekey = [hex(self.tipnode), '%d' % self.tiprev]
348 cachekey = [hex(self.tipnode), '%d' % self.tiprev]
350 if self.filteredhash is not None:
349 if self.filteredhash is not None:
351 cachekey.append(hex(self.filteredhash))
350 cachekey.append(hex(self.filteredhash))
352 f.write(" ".join(cachekey) + '\n')
351 f.write(" ".join(cachekey) + '\n')
353 nodecount = 0
352 nodecount = 0
354 for label, nodes in sorted(self.iteritems()):
353 for label, nodes in sorted(self.iteritems()):
355 label = encoding.fromlocal(label)
354 label = encoding.fromlocal(label)
356 for node in nodes:
355 for node in nodes:
357 nodecount += 1
356 nodecount += 1
358 if node in self._closednodes:
357 if node in self._closednodes:
359 state = 'c'
358 state = 'c'
360 else:
359 else:
361 state = 'o'
360 state = 'o'
362 f.write("%s %s %s\n" % (hex(node), state, label))
361 f.write("%s %s %s\n" % (hex(node), state, label))
363 f.close()
362 f.close()
364 repo.ui.log('branchcache',
363 repo.ui.log('branchcache',
365 'wrote %s branch cache with %d labels and %d nodes\n',
364 'wrote %s branch cache with %d labels and %d nodes\n',
366 repo.filtername, len(self._entries), nodecount)
365 repo.filtername, len(self._entries), nodecount)
367 except (IOError, OSError, error.Abort) as inst:
366 except (IOError, OSError, error.Abort) as inst:
368 # Abort may be raised by read only opener, so log and continue
367 # Abort may be raised by read only opener, so log and continue
369 repo.ui.debug("couldn't write branch cache: %s\n" %
368 repo.ui.debug("couldn't write branch cache: %s\n" %
370 stringutil.forcebytestr(inst))
369 stringutil.forcebytestr(inst))
371
370
372 def update(self, repo, revgen):
371 def update(self, repo, revgen):
373 """Given a branchhead cache, self, that may have extra nodes or be
372 """Given a branchhead cache, self, that may have extra nodes or be
374 missing heads, and a generator of nodes that are strictly a superset of
373 missing heads, and a generator of nodes that are strictly a superset of
375 heads missing, this function updates self to be correct.
374 heads missing, this function updates self to be correct.
376 """
375 """
377 starttime = util.timer()
376 starttime = util.timer()
378 cl = repo.changelog
377 cl = repo.changelog
379 # collect new branch entries
378 # collect new branch entries
380 newbranches = {}
379 newbranches = {}
381 getbranchinfo = repo.revbranchcache().branchinfo
380 getbranchinfo = repo.revbranchcache().branchinfo
382 for r in revgen:
381 for r in revgen:
383 branch, closesbranch = getbranchinfo(r)
382 branch, closesbranch = getbranchinfo(r)
384 newbranches.setdefault(branch, []).append(r)
383 newbranches.setdefault(branch, []).append(r)
385 if closesbranch:
384 if closesbranch:
386 self._closednodes.add(cl.node(r))
385 self._closednodes.add(cl.node(r))
387
386
388 # fetch current topological heads to speed up filtering
387 # fetch current topological heads to speed up filtering
389 topoheads = set(cl.headrevs())
388 topoheads = set(cl.headrevs())
390
389
391 # if older branchheads are reachable from new ones, they aren't
390 # if older branchheads are reachable from new ones, they aren't
392 # really branchheads. Note checking parents is insufficient:
391 # really branchheads. Note checking parents is insufficient:
393 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
392 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
394 for branch, newheadrevs in newbranches.iteritems():
393 for branch, newheadrevs in newbranches.iteritems():
395 bheads = self._entries.setdefault(branch, [])
394 bheads = self._entries.setdefault(branch, [])
396 bheadset = set(cl.rev(node) for node in bheads)
395 bheadset = set(cl.rev(node) for node in bheads)
397
396
398 # This have been tested True on all internal usage of this function.
397 # This have been tested True on all internal usage of this function.
399 # run it again in case of doubt
398 # run it again in case of doubt
400 # assert not (set(bheadrevs) & set(newheadrevs))
399 # assert not (set(bheadrevs) & set(newheadrevs))
401 bheadset.update(newheadrevs)
400 bheadset.update(newheadrevs)
402
401
403 # This prunes out two kinds of heads - heads that are superseded by
402 # This prunes out two kinds of heads - heads that are superseded by
404 # a head in newheadrevs, and newheadrevs that are not heads because
403 # a head in newheadrevs, and newheadrevs that are not heads because
405 # an existing head is their descendant.
404 # an existing head is their descendant.
406 uncertain = bheadset - topoheads
405 uncertain = bheadset - topoheads
407 if uncertain:
406 if uncertain:
408 floorrev = min(uncertain)
407 floorrev = min(uncertain)
409 ancestors = set(cl.ancestors(newheadrevs, floorrev))
408 ancestors = set(cl.ancestors(newheadrevs, floorrev))
410 bheadset -= ancestors
409 bheadset -= ancestors
411 bheadrevs = sorted(bheadset)
410 bheadrevs = sorted(bheadset)
412 self[branch] = [cl.node(rev) for rev in bheadrevs]
411 self[branch] = [cl.node(rev) for rev in bheadrevs]
413 tiprev = bheadrevs[-1]
412 tiprev = bheadrevs[-1]
414 if tiprev > self.tiprev:
413 if tiprev > self.tiprev:
415 self.tipnode = cl.node(tiprev)
414 self.tipnode = cl.node(tiprev)
416 self.tiprev = tiprev
415 self.tiprev = tiprev
417
416
418 if not self.validfor(repo):
417 if not self.validfor(repo):
419 # cache key are not valid anymore
418 # cache key are not valid anymore
420 self.tipnode = nullid
419 self.tipnode = nullid
421 self.tiprev = nullrev
420 self.tiprev = nullrev
422 for heads in self.iterheads():
421 for heads in self.iterheads():
423 tiprev = max(cl.rev(node) for node in heads)
422 tiprev = max(cl.rev(node) for node in heads)
424 if tiprev > self.tiprev:
423 if tiprev > self.tiprev:
425 self.tipnode = cl.node(tiprev)
424 self.tipnode = cl.node(tiprev)
426 self.tiprev = tiprev
425 self.tiprev = tiprev
427 self.filteredhash = scmutil.filteredhash(repo, self.tiprev)
426 self.filteredhash = scmutil.filteredhash(repo, self.tiprev)
428
427
429 duration = util.timer() - starttime
428 duration = util.timer() - starttime
430 repo.ui.log('branchcache', 'updated %s branch cache in %.4f seconds\n',
429 repo.ui.log('branchcache', 'updated %s branch cache in %.4f seconds\n',
431 repo.filtername or b'None', duration)
430 repo.filtername or b'None', duration)
432
431
433 self.write(repo)
432 self.write(repo)
434
433
435
434
436 class remotebranchcache(branchcache):
435 class remotebranchcache(branchcache):
437 """Branchmap info for a remote connection, should not write locally"""
436 """Branchmap info for a remote connection, should not write locally"""
438 def write(self, repo):
437 def write(self, repo):
439 pass
438 pass
440
439
441
440
442 # Revision branch info cache
441 # Revision branch info cache
443
442
444 _rbcversion = '-v1'
443 _rbcversion = '-v1'
445 _rbcnames = 'rbc-names' + _rbcversion
444 _rbcnames = 'rbc-names' + _rbcversion
446 _rbcrevs = 'rbc-revs' + _rbcversion
445 _rbcrevs = 'rbc-revs' + _rbcversion
447 # [4 byte hash prefix][4 byte branch name number with sign bit indicating open]
446 # [4 byte hash prefix][4 byte branch name number with sign bit indicating open]
448 _rbcrecfmt = '>4sI'
447 _rbcrecfmt = '>4sI'
449 _rbcrecsize = calcsize(_rbcrecfmt)
448 _rbcrecsize = calcsize(_rbcrecfmt)
450 _rbcnodelen = 4
449 _rbcnodelen = 4
451 _rbcbranchidxmask = 0x7fffffff
450 _rbcbranchidxmask = 0x7fffffff
452 _rbccloseflag = 0x80000000
451 _rbccloseflag = 0x80000000
453
452
454 class revbranchcache(object):
453 class revbranchcache(object):
455 """Persistent cache, mapping from revision number to branch name and close.
454 """Persistent cache, mapping from revision number to branch name and close.
456 This is a low level cache, independent of filtering.
455 This is a low level cache, independent of filtering.
457
456
458 Branch names are stored in rbc-names in internal encoding separated by 0.
457 Branch names are stored in rbc-names in internal encoding separated by 0.
459 rbc-names is append-only, and each branch name is only stored once and will
458 rbc-names is append-only, and each branch name is only stored once and will
460 thus have a unique index.
459 thus have a unique index.
461
460
462 The branch info for each revision is stored in rbc-revs as constant size
461 The branch info for each revision is stored in rbc-revs as constant size
463 records. The whole file is read into memory, but it is only 'parsed' on
462 records. The whole file is read into memory, but it is only 'parsed' on
464 demand. The file is usually append-only but will be truncated if repo
463 demand. The file is usually append-only but will be truncated if repo
465 modification is detected.
464 modification is detected.
466 The record for each revision contains the first 4 bytes of the
465 The record for each revision contains the first 4 bytes of the
467 corresponding node hash, and the record is only used if it still matches.
466 corresponding node hash, and the record is only used if it still matches.
468 Even a completely trashed rbc-revs fill thus still give the right result
467 Even a completely trashed rbc-revs fill thus still give the right result
469 while converging towards full recovery ... assuming no incorrectly matching
468 while converging towards full recovery ... assuming no incorrectly matching
470 node hashes.
469 node hashes.
471 The record also contains 4 bytes where 31 bits contains the index of the
470 The record also contains 4 bytes where 31 bits contains the index of the
472 branch and the last bit indicate that it is a branch close commit.
471 branch and the last bit indicate that it is a branch close commit.
473 The usage pattern for rbc-revs is thus somewhat similar to 00changelog.i
472 The usage pattern for rbc-revs is thus somewhat similar to 00changelog.i
474 and will grow with it but be 1/8th of its size.
473 and will grow with it but be 1/8th of its size.
475 """
474 """
476
475
477 def __init__(self, repo, readonly=True):
476 def __init__(self, repo, readonly=True):
478 assert repo.filtername is None
477 assert repo.filtername is None
479 self._repo = repo
478 self._repo = repo
480 self._names = [] # branch names in local encoding with static index
479 self._names = [] # branch names in local encoding with static index
481 self._rbcrevs = bytearray()
480 self._rbcrevs = bytearray()
482 self._rbcsnameslen = 0 # length of names read at _rbcsnameslen
481 self._rbcsnameslen = 0 # length of names read at _rbcsnameslen
483 try:
482 try:
484 bndata = repo.cachevfs.read(_rbcnames)
483 bndata = repo.cachevfs.read(_rbcnames)
485 self._rbcsnameslen = len(bndata) # for verification before writing
484 self._rbcsnameslen = len(bndata) # for verification before writing
486 if bndata:
485 if bndata:
487 self._names = [encoding.tolocal(bn)
486 self._names = [encoding.tolocal(bn)
488 for bn in bndata.split('\0')]
487 for bn in bndata.split('\0')]
489 except (IOError, OSError):
488 except (IOError, OSError):
490 if readonly:
489 if readonly:
491 # don't try to use cache - fall back to the slow path
490 # don't try to use cache - fall back to the slow path
492 self.branchinfo = self._branchinfo
491 self.branchinfo = self._branchinfo
493
492
494 if self._names:
493 if self._names:
495 try:
494 try:
496 data = repo.cachevfs.read(_rbcrevs)
495 data = repo.cachevfs.read(_rbcrevs)
497 self._rbcrevs[:] = data
496 self._rbcrevs[:] = data
498 except (IOError, OSError) as inst:
497 except (IOError, OSError) as inst:
499 repo.ui.debug("couldn't read revision branch cache: %s\n" %
498 repo.ui.debug("couldn't read revision branch cache: %s\n" %
500 stringutil.forcebytestr(inst))
499 stringutil.forcebytestr(inst))
501 # remember number of good records on disk
500 # remember number of good records on disk
502 self._rbcrevslen = min(len(self._rbcrevs) // _rbcrecsize,
501 self._rbcrevslen = min(len(self._rbcrevs) // _rbcrecsize,
503 len(repo.changelog))
502 len(repo.changelog))
504 if self._rbcrevslen == 0:
503 if self._rbcrevslen == 0:
505 self._names = []
504 self._names = []
506 self._rbcnamescount = len(self._names) # number of names read at
505 self._rbcnamescount = len(self._names) # number of names read at
507 # _rbcsnameslen
506 # _rbcsnameslen
508
507
509 def _clear(self):
508 def _clear(self):
510 self._rbcsnameslen = 0
509 self._rbcsnameslen = 0
511 del self._names[:]
510 del self._names[:]
512 self._rbcnamescount = 0
511 self._rbcnamescount = 0
513 self._rbcrevslen = len(self._repo.changelog)
512 self._rbcrevslen = len(self._repo.changelog)
514 self._rbcrevs = bytearray(self._rbcrevslen * _rbcrecsize)
513 self._rbcrevs = bytearray(self._rbcrevslen * _rbcrecsize)
515 util.clearcachedproperty(self, '_namesreverse')
514 util.clearcachedproperty(self, '_namesreverse')
516
515
517 @util.propertycache
516 @util.propertycache
518 def _namesreverse(self):
517 def _namesreverse(self):
519 return dict((b, r) for r, b in enumerate(self._names))
518 return dict((b, r) for r, b in enumerate(self._names))
520
519
521 def branchinfo(self, rev):
520 def branchinfo(self, rev):
522 """Return branch name and close flag for rev, using and updating
521 """Return branch name and close flag for rev, using and updating
523 persistent cache."""
522 persistent cache."""
524 changelog = self._repo.changelog
523 changelog = self._repo.changelog
525 rbcrevidx = rev * _rbcrecsize
524 rbcrevidx = rev * _rbcrecsize
526
525
527 # avoid negative index, changelog.read(nullrev) is fast without cache
526 # avoid negative index, changelog.read(nullrev) is fast without cache
528 if rev == nullrev:
527 if rev == nullrev:
529 return changelog.branchinfo(rev)
528 return changelog.branchinfo(rev)
530
529
531 # if requested rev isn't allocated, grow and cache the rev info
530 # if requested rev isn't allocated, grow and cache the rev info
532 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
531 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
533 return self._branchinfo(rev)
532 return self._branchinfo(rev)
534
533
535 # fast path: extract data from cache, use it if node is matching
534 # fast path: extract data from cache, use it if node is matching
536 reponode = changelog.node(rev)[:_rbcnodelen]
535 reponode = changelog.node(rev)[:_rbcnodelen]
537 cachenode, branchidx = unpack_from(
536 cachenode, branchidx = unpack_from(
538 _rbcrecfmt, util.buffer(self._rbcrevs), rbcrevidx)
537 _rbcrecfmt, util.buffer(self._rbcrevs), rbcrevidx)
539 close = bool(branchidx & _rbccloseflag)
538 close = bool(branchidx & _rbccloseflag)
540 if close:
539 if close:
541 branchidx &= _rbcbranchidxmask
540 branchidx &= _rbcbranchidxmask
542 if cachenode == '\0\0\0\0':
541 if cachenode == '\0\0\0\0':
543 pass
542 pass
544 elif cachenode == reponode:
543 elif cachenode == reponode:
545 try:
544 try:
546 return self._names[branchidx], close
545 return self._names[branchidx], close
547 except IndexError:
546 except IndexError:
548 # recover from invalid reference to unknown branch
547 # recover from invalid reference to unknown branch
549 self._repo.ui.debug("referenced branch names not found"
548 self._repo.ui.debug("referenced branch names not found"
550 " - rebuilding revision branch cache from scratch\n")
549 " - rebuilding revision branch cache from scratch\n")
551 self._clear()
550 self._clear()
552 else:
551 else:
553 # rev/node map has changed, invalidate the cache from here up
552 # rev/node map has changed, invalidate the cache from here up
554 self._repo.ui.debug("history modification detected - truncating "
553 self._repo.ui.debug("history modification detected - truncating "
555 "revision branch cache to revision %d\n" % rev)
554 "revision branch cache to revision %d\n" % rev)
556 truncate = rbcrevidx + _rbcrecsize
555 truncate = rbcrevidx + _rbcrecsize
557 del self._rbcrevs[truncate:]
556 del self._rbcrevs[truncate:]
558 self._rbcrevslen = min(self._rbcrevslen, truncate)
557 self._rbcrevslen = min(self._rbcrevslen, truncate)
559
558
560 # fall back to slow path and make sure it will be written to disk
559 # fall back to slow path and make sure it will be written to disk
561 return self._branchinfo(rev)
560 return self._branchinfo(rev)
562
561
563 def _branchinfo(self, rev):
562 def _branchinfo(self, rev):
564 """Retrieve branch info from changelog and update _rbcrevs"""
563 """Retrieve branch info from changelog and update _rbcrevs"""
565 changelog = self._repo.changelog
564 changelog = self._repo.changelog
566 b, close = changelog.branchinfo(rev)
565 b, close = changelog.branchinfo(rev)
567 if b in self._namesreverse:
566 if b in self._namesreverse:
568 branchidx = self._namesreverse[b]
567 branchidx = self._namesreverse[b]
569 else:
568 else:
570 branchidx = len(self._names)
569 branchidx = len(self._names)
571 self._names.append(b)
570 self._names.append(b)
572 self._namesreverse[b] = branchidx
571 self._namesreverse[b] = branchidx
573 reponode = changelog.node(rev)
572 reponode = changelog.node(rev)
574 if close:
573 if close:
575 branchidx |= _rbccloseflag
574 branchidx |= _rbccloseflag
576 self._setcachedata(rev, reponode, branchidx)
575 self._setcachedata(rev, reponode, branchidx)
577 return b, close
576 return b, close
578
577
579 def setdata(self, branch, rev, node, close):
578 def setdata(self, branch, rev, node, close):
580 """add new data information to the cache"""
579 """add new data information to the cache"""
581 if branch in self._namesreverse:
580 if branch in self._namesreverse:
582 branchidx = self._namesreverse[branch]
581 branchidx = self._namesreverse[branch]
583 else:
582 else:
584 branchidx = len(self._names)
583 branchidx = len(self._names)
585 self._names.append(branch)
584 self._names.append(branch)
586 self._namesreverse[branch] = branchidx
585 self._namesreverse[branch] = branchidx
587 if close:
586 if close:
588 branchidx |= _rbccloseflag
587 branchidx |= _rbccloseflag
589 self._setcachedata(rev, node, branchidx)
588 self._setcachedata(rev, node, branchidx)
590 # If no cache data were readable (non exists, bad permission, etc)
589 # If no cache data were readable (non exists, bad permission, etc)
591 # the cache was bypassing itself by setting:
590 # the cache was bypassing itself by setting:
592 #
591 #
593 # self.branchinfo = self._branchinfo
592 # self.branchinfo = self._branchinfo
594 #
593 #
595 # Since we now have data in the cache, we need to drop this bypassing.
594 # Since we now have data in the cache, we need to drop this bypassing.
596 if r'branchinfo' in vars(self):
595 if r'branchinfo' in vars(self):
597 del self.branchinfo
596 del self.branchinfo
598
597
599 def _setcachedata(self, rev, node, branchidx):
598 def _setcachedata(self, rev, node, branchidx):
600 """Writes the node's branch data to the in-memory cache data."""
599 """Writes the node's branch data to the in-memory cache data."""
601 if rev == nullrev:
600 if rev == nullrev:
602 return
601 return
603 rbcrevidx = rev * _rbcrecsize
602 rbcrevidx = rev * _rbcrecsize
604 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
603 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
605 self._rbcrevs.extend('\0' *
604 self._rbcrevs.extend('\0' *
606 (len(self._repo.changelog) * _rbcrecsize -
605 (len(self._repo.changelog) * _rbcrecsize -
607 len(self._rbcrevs)))
606 len(self._rbcrevs)))
608 pack_into(_rbcrecfmt, self._rbcrevs, rbcrevidx, node, branchidx)
607 pack_into(_rbcrecfmt, self._rbcrevs, rbcrevidx, node, branchidx)
609 self._rbcrevslen = min(self._rbcrevslen, rev)
608 self._rbcrevslen = min(self._rbcrevslen, rev)
610
609
611 tr = self._repo.currenttransaction()
610 tr = self._repo.currenttransaction()
612 if tr:
611 if tr:
613 tr.addfinalize('write-revbranchcache', self.write)
612 tr.addfinalize('write-revbranchcache', self.write)
614
613
615 def write(self, tr=None):
614 def write(self, tr=None):
616 """Save branch cache if it is dirty."""
615 """Save branch cache if it is dirty."""
617 repo = self._repo
616 repo = self._repo
618 wlock = None
617 wlock = None
619 step = ''
618 step = ''
620 try:
619 try:
621 if self._rbcnamescount < len(self._names):
620 if self._rbcnamescount < len(self._names):
622 step = ' names'
621 step = ' names'
623 wlock = repo.wlock(wait=False)
622 wlock = repo.wlock(wait=False)
624 if self._rbcnamescount != 0:
623 if self._rbcnamescount != 0:
625 f = repo.cachevfs.open(_rbcnames, 'ab')
624 f = repo.cachevfs.open(_rbcnames, 'ab')
626 if f.tell() == self._rbcsnameslen:
625 if f.tell() == self._rbcsnameslen:
627 f.write('\0')
626 f.write('\0')
628 else:
627 else:
629 f.close()
628 f.close()
630 repo.ui.debug("%s changed - rewriting it\n" % _rbcnames)
629 repo.ui.debug("%s changed - rewriting it\n" % _rbcnames)
631 self._rbcnamescount = 0
630 self._rbcnamescount = 0
632 self._rbcrevslen = 0
631 self._rbcrevslen = 0
633 if self._rbcnamescount == 0:
632 if self._rbcnamescount == 0:
634 # before rewriting names, make sure references are removed
633 # before rewriting names, make sure references are removed
635 repo.cachevfs.unlinkpath(_rbcrevs, ignoremissing=True)
634 repo.cachevfs.unlinkpath(_rbcrevs, ignoremissing=True)
636 f = repo.cachevfs.open(_rbcnames, 'wb')
635 f = repo.cachevfs.open(_rbcnames, 'wb')
637 f.write('\0'.join(encoding.fromlocal(b)
636 f.write('\0'.join(encoding.fromlocal(b)
638 for b in self._names[self._rbcnamescount:]))
637 for b in self._names[self._rbcnamescount:]))
639 self._rbcsnameslen = f.tell()
638 self._rbcsnameslen = f.tell()
640 f.close()
639 f.close()
641 self._rbcnamescount = len(self._names)
640 self._rbcnamescount = len(self._names)
642
641
643 start = self._rbcrevslen * _rbcrecsize
642 start = self._rbcrevslen * _rbcrecsize
644 if start != len(self._rbcrevs):
643 if start != len(self._rbcrevs):
645 step = ''
644 step = ''
646 if wlock is None:
645 if wlock is None:
647 wlock = repo.wlock(wait=False)
646 wlock = repo.wlock(wait=False)
648 revs = min(len(repo.changelog),
647 revs = min(len(repo.changelog),
649 len(self._rbcrevs) // _rbcrecsize)
648 len(self._rbcrevs) // _rbcrecsize)
650 f = repo.cachevfs.open(_rbcrevs, 'ab')
649 f = repo.cachevfs.open(_rbcrevs, 'ab')
651 if f.tell() != start:
650 if f.tell() != start:
652 repo.ui.debug("truncating cache/%s to %d\n"
651 repo.ui.debug("truncating cache/%s to %d\n"
653 % (_rbcrevs, start))
652 % (_rbcrevs, start))
654 f.seek(start)
653 f.seek(start)
655 if f.tell() != start:
654 if f.tell() != start:
656 start = 0
655 start = 0
657 f.seek(start)
656 f.seek(start)
658 f.truncate()
657 f.truncate()
659 end = revs * _rbcrecsize
658 end = revs * _rbcrecsize
660 f.write(self._rbcrevs[start:end])
659 f.write(self._rbcrevs[start:end])
661 f.close()
660 f.close()
662 self._rbcrevslen = revs
661 self._rbcrevslen = revs
663 except (IOError, OSError, error.Abort, error.LockError) as inst:
662 except (IOError, OSError, error.Abort, error.LockError) as inst:
664 repo.ui.debug("couldn't write revision branch cache%s: %s\n"
663 repo.ui.debug("couldn't write revision branch cache%s: %s\n"
665 % (step, stringutil.forcebytestr(inst)))
664 % (step, stringutil.forcebytestr(inst)))
666 finally:
665 finally:
667 if wlock is not None:
666 if wlock is not None:
668 wlock.release()
667 wlock.release()
General Comments 0
You need to be logged in to leave comments. Login now