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