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