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