##// END OF EJS Templates
branchcache: introduce revbranchcache for caching of revision branch names...
Mads Kiilerich -
r23785:cb99bacb default
parent child Browse files
Show More
@@ -9,6 +9,8 b' from node import bin, hex, nullid, nullr'
9 import encoding
9 import encoding
10 import util
10 import util
11 import time
11 import time
12 from array import array
13 from struct import calcsize, pack, unpack
12
14
13 def _filename(repo):
15 def _filename(repo):
14 """name of a branchcache file for a given repo or repoview"""
16 """name of a branchcache file for a given repo or repoview"""
@@ -285,3 +287,150 b' class branchcache(dict):'
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
General Comments 0
You need to be logged in to leave comments. Login now