283 self.filteredhash = self._hashfiltered(repo) |
285 self.filteredhash = self._hashfiltered(repo) |
284 |
286 |
285 duration = time.time() - starttime |
287 duration = time.time() - starttime |
286 repo.ui.log('branchcache', 'updated %s branch cache in %.4f seconds\n', |
288 repo.ui.log('branchcache', 'updated %s branch cache in %.4f seconds\n', |
287 repo.filtername, duration) |
289 repo.filtername, duration) |
|
290 |
|
291 # Revision branch info cache |
|
292 |
|
293 _rbcversion = '-v1' |
|
294 _rbcnames = 'cache/rbc-names' + _rbcversion |
|
295 _rbcrevs = 'cache/rbc-revs' + _rbcversion |
|
296 # [4 byte hash prefix][4 byte branch name number with sign bit indicating open] |
|
297 _rbcrecfmt = '>4sI' |
|
298 _rbcrecsize = calcsize(_rbcrecfmt) |
|
299 _rbcnodelen = 4 |
|
300 _rbcbranchidxmask = 0x7fffffff |
|
301 _rbccloseflag = 0x80000000 |
|
302 |
|
303 class revbranchcache(object): |
|
304 """Persistent cache, mapping from revision number to branch name and close. |
|
305 This is a low level cache, independent of filtering. |
|
306 |
|
307 Branch names are stored in rbc-names in internal encoding separated by 0. |
|
308 rbc-names is append-only, and each branch name is only stored once and will |
|
309 thus have a unique index. |
|
310 |
|
311 The branch info for each revision is stored in rbc-revs as constant size |
|
312 records. The whole file is read into memory, but it is only 'parsed' on |
|
313 demand. The file is usually append-only but will be truncated if repo |
|
314 modification is detected. |
|
315 The record for each revision contains the first 4 bytes of the |
|
316 corresponding node hash, and the record is only used if it still matches. |
|
317 Even a completely trashed rbc-revs fill thus still give the right result |
|
318 while converging towards full recovery ... assuming no incorrectly matching |
|
319 node hashes. |
|
320 The record also contains 4 bytes where 31 bits contains the index of the |
|
321 branch and the last bit indicate that it is a branch close commit. |
|
322 The usage pattern for rbc-revs is thus somewhat similar to 00changelog.i |
|
323 and will grow with it but be 1/8th of its size. |
|
324 """ |
|
325 |
|
326 def __init__(self, repo): |
|
327 assert repo.filtername is None |
|
328 self._names = [] # branch names in local encoding with static index |
|
329 self._rbcrevs = array('c') # structs of type _rbcrecfmt |
|
330 self._rbcsnameslen = 0 |
|
331 try: |
|
332 bndata = repo.vfs.read(_rbcnames) |
|
333 self._rbcsnameslen = len(bndata) # for verification before writing |
|
334 self._names = [encoding.tolocal(bn) for bn in bndata.split('\0')] |
|
335 except (IOError, OSError), inst: |
|
336 repo.ui.debug("couldn't read revision branch cache names: %s\n" % |
|
337 inst) |
|
338 if self._names: |
|
339 try: |
|
340 data = repo.vfs.read(_rbcrevs) |
|
341 self._rbcrevs.fromstring(data) |
|
342 except (IOError, OSError), inst: |
|
343 repo.ui.debug("couldn't read revision branch cache: %s\n" % |
|
344 inst) |
|
345 # remember number of good records on disk |
|
346 self._rbcrevslen = min(len(self._rbcrevs) // _rbcrecsize, |
|
347 len(repo.changelog)) |
|
348 if self._rbcrevslen == 0: |
|
349 self._names = [] |
|
350 self._rbcnamescount = len(self._names) # number of good names on disk |
|
351 self._namesreverse = dict((b, r) for r, b in enumerate(self._names)) |
|
352 |
|
353 def branchinfo(self, changelog, rev): |
|
354 """Return branch name and close flag for rev, using and updating |
|
355 persistent cache.""" |
|
356 rbcrevidx = rev * _rbcrecsize |
|
357 |
|
358 # if requested rev is missing, add and populate all missing revs |
|
359 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize: |
|
360 first = len(self._rbcrevs) // _rbcrecsize |
|
361 self._rbcrevs.extend('\0' * (len(changelog) * _rbcrecsize - |
|
362 len(self._rbcrevs))) |
|
363 for r in xrange(first, len(changelog)): |
|
364 self._branchinfo(r) |
|
365 |
|
366 # fast path: extract data from cache, use it if node is matching |
|
367 reponode = changelog.node(rev)[:_rbcnodelen] |
|
368 cachenode, branchidx = unpack( |
|
369 _rbcrecfmt, buffer(self._rbcrevs, rbcrevidx, _rbcrecsize)) |
|
370 close = bool(branchidx & _rbccloseflag) |
|
371 if close: |
|
372 branchidx &= _rbcbranchidxmask |
|
373 if cachenode == reponode: |
|
374 return self._names[branchidx], close |
|
375 # fall back to slow path and make sure it will be written to disk |
|
376 self._rbcrevslen = min(self._rbcrevslen, rev) |
|
377 return self._branchinfo(rev) |
|
378 |
|
379 def _branchinfo(self, changelog, rev): |
|
380 """Retrieve branch info from changelog and update _rbcrevs""" |
|
381 b, close = changelog.branchinfo(rev) |
|
382 if b in self._namesreverse: |
|
383 branchidx = self._namesreverse[b] |
|
384 else: |
|
385 branchidx = len(self._names) |
|
386 self._names.append(b) |
|
387 self._namesreverse[b] = branchidx |
|
388 reponode = changelog.node(rev) |
|
389 if close: |
|
390 branchidx |= _rbccloseflag |
|
391 rbcrevidx = rev * _rbcrecsize |
|
392 rec = array('c') |
|
393 rec.fromstring(pack(_rbcrecfmt, reponode, branchidx)) |
|
394 self._rbcrevs[rbcrevidx:rbcrevidx + _rbcrecsize] = rec |
|
395 return b, close |
|
396 |
|
397 def write(self, repo): |
|
398 """Save branch cache if it is dirty.""" |
|
399 if self._rbcnamescount < len(self._names): |
|
400 try: |
|
401 if self._rbcnamescount != 0: |
|
402 f = repo.vfs.open(_rbcnames, 'ab') |
|
403 if f.tell() == self._rbcsnameslen: |
|
404 f.write('\0') |
|
405 else: |
|
406 f.close() |
|
407 self._rbcnamescount = 0 |
|
408 self._rbcrevslen = 0 |
|
409 if self._rbcnamescount == 0: |
|
410 f = repo.vfs.open(_rbcnames, 'wb') |
|
411 f.write('\0'.join(encoding.fromlocal(b) |
|
412 for b in self._names[self._rbcnamescount:])) |
|
413 self._rbcsnameslen = f.tell() |
|
414 f.close() |
|
415 except (IOError, OSError, util.Abort), inst: |
|
416 repo.ui.debug("couldn't write revision branch cache names: " |
|
417 "%s\n" % inst) |
|
418 return |
|
419 self._rbcnamescount = len(self._names) |
|
420 |
|
421 start = self._rbcrevslen * _rbcrecsize |
|
422 if start != len(self._rbcrevs): |
|
423 self._rbcrevslen = min(len(repo.changelog), |
|
424 len(self._rbcrevs) // _rbcrecsize) |
|
425 try: |
|
426 f = repo.vfs.open(_rbcrevs, 'ab') |
|
427 if f.tell() != start: |
|
428 f.seek(start) |
|
429 f.truncate() |
|
430 end = self._rbcrevslen * _rbcrecsize |
|
431 f.write(self._rbcrevs[start:end]) |
|
432 f.close() |
|
433 except (IOError, OSError, util.Abort), inst: |
|
434 repo.ui.debug("couldn't write revision branch cache: %s\n" % |
|
435 inst) |
|
436 return |