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