##// END OF EJS Templates
branchcache: dispatch the code into the dedicated subclass...
marmoute -
r52348:7a063dd9 default
parent child Browse files
Show More
This diff has been collapsed as it changes many lines, (547 lines changed) Show them Hide them
@@ -211,73 +211,18 b' class _BaseBranchCache:'
211 entries: Union[
211 entries: Union[
212 Dict[bytes, List[bytes]], Iterable[Tuple[bytes, List[bytes]]]
212 Dict[bytes, List[bytes]], Iterable[Tuple[bytes, List[bytes]]]
213 ] = (),
213 ] = (),
214 tipnode: Optional[bytes] = None,
214 closed_nodes: Optional[Set[bytes]] = None,
215 tiprev: Optional[int] = nullrev,
216 filteredhash: Optional[bytes] = None,
217 closednodes: Optional[Set[bytes]] = None,
218 hasnode: Optional[Callable[[bytes], bool]] = None,
219 verify_node: bool = False,
220 ) -> None:
215 ) -> None:
221 """hasnode is a function which can be used to verify whether changelog
216 """hasnode is a function which can be used to verify whether changelog
222 has a given node or not. If it's not provided, we assume that every node
217 has a given node or not. If it's not provided, we assume that every node
223 we have exists in changelog"""
218 we have exists in changelog"""
224 self._filtername = repo.filtername
225 self._delayed = False
226 if tipnode is None:
227 self.tipnode = repo.nullid
228 else:
229 self.tipnode = tipnode
230 self.tiprev = tiprev
231 self.filteredhash = filteredhash
232 # closednodes is a set of nodes that close their branch. If the branch
219 # closednodes is a set of nodes that close their branch. If the branch
233 # cache has been updated, it may contain nodes that are no longer
220 # cache has been updated, it may contain nodes that are no longer
234 # heads.
221 # heads.
235 if closednodes is None:
222 if closed_nodes is None:
236 self._closednodes = set()
223 closed_nodes = set()
237 else:
224 self._closednodes = set(closed_nodes)
238 self._closednodes = closednodes
239 self._entries = dict(entries)
225 self._entries = dict(entries)
240 # Do we need to verify branch at all ?
241 self._verify_node = verify_node
242 # whether closed nodes are verified or not
243 self._closedverified = False
244 # branches for which nodes are verified
245 self._verifiedbranches = set()
246 self._hasnode = None
247 if self._verify_node:
248 self._hasnode = repo.changelog.hasnode
249
250 def _verifyclosed(self):
251 """verify the closed nodes we have"""
252 if not self._verify_node:
253 return
254 if self._closedverified:
255 return
256 assert self._hasnode is not None
257 for node in self._closednodes:
258 if not self._hasnode(node):
259 _unknownnode(node)
260
261 self._closedverified = True
262
263 def _verifybranch(self, branch):
264 """verify head nodes for the given branch."""
265 if not self._verify_node:
266 return
267 if branch not in self._entries or branch in self._verifiedbranches:
268 return
269 assert self._hasnode is not None
270 for n in self._entries[branch]:
271 if not self._hasnode(n):
272 _unknownnode(n)
273
274 self._verifiedbranches.add(branch)
275
276 def _verifyall(self):
277 """verifies nodes of all the branches"""
278 needverification = set(self._entries.keys()) - self._verifiedbranches
279 for b in needverification:
280 self._verifybranch(b)
281
226
282 def __iter__(self):
227 def __iter__(self):
283 return iter(self._entries)
228 return iter(self._entries)
@@ -286,114 +231,20 b' class _BaseBranchCache:'
286 self._entries[key] = value
231 self._entries[key] = value
287
232
288 def __getitem__(self, key):
233 def __getitem__(self, key):
289 self._verifybranch(key)
290 return self._entries[key]
234 return self._entries[key]
291
235
292 def __contains__(self, key):
236 def __contains__(self, key):
293 self._verifybranch(key)
294 return key in self._entries
237 return key in self._entries
295
238
296 def iteritems(self):
239 def iteritems(self):
297 for k, v in self._entries.items():
240 return self._entries.items()
298 self._verifybranch(k)
299 yield k, v
300
241
301 items = iteritems
242 items = iteritems
302
243
303 def hasbranch(self, label):
244 def hasbranch(self, label):
304 """checks whether a branch of this name exists or not"""
245 """checks whether a branch of this name exists or not"""
305 self._verifybranch(label)
306 return label in self._entries
246 return label in self._entries
307
247
308 @classmethod
309 def fromfile(cls, repo):
310 f = None
311 try:
312 f = repo.cachevfs(cls._filename(repo))
313 lineiter = iter(f)
314 cachekey = next(lineiter).rstrip(b'\n').split(b" ", 2)
315 last, lrev = cachekey[:2]
316 last, lrev = bin(last), int(lrev)
317 filteredhash = None
318 if len(cachekey) > 2:
319 filteredhash = bin(cachekey[2])
320 bcache = cls(
321 repo,
322 tipnode=last,
323 tiprev=lrev,
324 filteredhash=filteredhash,
325 verify_node=True,
326 )
327 if not bcache.validfor(repo):
328 # invalidate the cache
329 raise ValueError('tip differs')
330 bcache.load(repo, lineiter)
331 except (IOError, OSError):
332 return None
333
334 except Exception as inst:
335 if repo.ui.debugflag:
336 msg = b'invalid %s: %s\n'
337 repo.ui.debug(
338 msg
339 % (
340 _branchcachedesc(repo),
341 stringutil.forcebytestr(inst),
342 )
343 )
344 bcache = None
345
346 finally:
347 if f:
348 f.close()
349
350 return bcache
351
352 def load(self, repo, lineiter):
353 """fully loads the branchcache by reading from the file using the line
354 iterator passed"""
355 for line in lineiter:
356 line = line.rstrip(b'\n')
357 if not line:
358 continue
359 node, state, label = line.split(b" ", 2)
360 if state not in b'oc':
361 raise ValueError('invalid branch state')
362 label = encoding.tolocal(label.strip())
363 node = bin(node)
364 self._entries.setdefault(label, []).append(node)
365 if state == b'c':
366 self._closednodes.add(node)
367
368 @staticmethod
369 def _filename(repo):
370 """name of a branchcache file for a given repo or repoview"""
371 filename = b"branch2"
372 if repo.filtername:
373 filename = b'%s-%s' % (filename, repo.filtername)
374 return filename
375
376 def validfor(self, repo):
377 """check that cache contents are valid for (a subset of) this repo
378
379 - False when the order of changesets changed or if we detect a strip.
380 - True when cache is up-to-date for the current repo or its subset."""
381 try:
382 node = repo.changelog.node(self.tiprev)
383 except IndexError:
384 # changesets were stripped and now we don't even have enough to
385 # find tiprev
386 return False
387 if self.tipnode != node:
388 # tiprev doesn't correspond to tipnode: repo was stripped, or this
389 # repo has a different order of changesets
390 return False
391 tiphash = scmutil.filteredhash(repo, self.tiprev, needobsolete=True)
392 # hashes don't match if this repo view has a different set of filtered
393 # revisions (e.g. due to phase changes) or obsolete revisions (e.g.
394 # history was rewritten)
395 return self.filteredhash == tiphash
396
397 def _branchtip(self, heads):
248 def _branchtip(self, heads):
398 """Return tuple with last open head in heads and false,
249 """Return tuple with last open head in heads and false,
399 otherwise return last closed head and true."""
250 otherwise return last closed head and true."""
@@ -416,7 +267,6 b' class _BaseBranchCache:'
416 return (n for n in nodes if n not in self._closednodes)
267 return (n for n in nodes if n not in self._closednodes)
417
268
418 def branchheads(self, branch, closed=False):
269 def branchheads(self, branch, closed=False):
419 self._verifybranch(branch)
420 heads = self._entries[branch]
270 heads = self._entries[branch]
421 if not closed:
271 if not closed:
422 heads = list(self.iteropen(heads))
272 heads = list(self.iteropen(heads))
@@ -428,98 +278,27 b' class _BaseBranchCache:'
428
278
429 def iterheads(self):
279 def iterheads(self):
430 """returns all the heads"""
280 """returns all the heads"""
431 self._verifyall()
432 return self._entries.values()
281 return self._entries.values()
433
282
434 def copy(self, repo):
435 """return a deep copy of the branchcache object"""
436 other = type(self)(
437 repo=repo,
438 # we always do a shally copy of self._entries, and the values is
439 # always replaced, so no need to deepcopy until the above remains
440 # true.
441 entries=self._entries,
442 tipnode=self.tipnode,
443 tiprev=self.tiprev,
444 filteredhash=self.filteredhash,
445 closednodes=set(self._closednodes),
446 verify_node=self._verify_node,
447 )
448 # we copy will likely schedule a write anyway, but that does not seems
449 # to hurt to overschedule
450 other._delayed = self._delayed
451 # also copy information about the current verification state
452 other._closedverified = self._closedverified
453 other._verifiedbranches = set(self._verifiedbranches)
454 return other
455
456 def write(self, repo):
457 assert self._filtername == repo.filtername, (
458 self._filtername,
459 repo.filtername,
460 )
461 tr = repo.currenttransaction()
462 if not getattr(tr, 'finalized', True):
463 # Avoid premature writing.
464 #
465 # (The cache warming setup by localrepo will update the file later.)
466 self._delayed = True
467 return
468 try:
469 filename = self._filename(repo)
470 with repo.cachevfs(filename, b"w", atomictemp=True) as f:
471 cachekey = [hex(self.tipnode), b'%d' % self.tiprev]
472 if self.filteredhash is not None:
473 cachekey.append(hex(self.filteredhash))
474 f.write(b" ".join(cachekey) + b'\n')
475 nodecount = 0
476 for label, nodes in sorted(self._entries.items()):
477 label = encoding.fromlocal(label)
478 for node in nodes:
479 nodecount += 1
480 if node in self._closednodes:
481 state = b'c'
482 else:
483 state = b'o'
484 f.write(b"%s %s %s\n" % (hex(node), state, label))
485 repo.ui.log(
486 b'branchcache',
487 b'wrote %s with %d labels and %d nodes\n',
488 _branchcachedesc(repo),
489 len(self._entries),
490 nodecount,
491 )
492 self._delayed = False
493 except (IOError, OSError, error.Abort) as inst:
494 # Abort may be raised by read only opener, so log and continue
495 repo.ui.debug(
496 b"couldn't write branch cache: %s\n"
497 % stringutil.forcebytestr(inst)
498 )
499
500 def update(self, repo, revgen):
283 def update(self, repo, revgen):
501 """Given a branchhead cache, self, that may have extra nodes or be
284 """Given a branchhead cache, self, that may have extra nodes or be
502 missing heads, and a generator of nodes that are strictly a superset of
285 missing heads, and a generator of nodes that are strictly a superset of
503 heads missing, this function updates self to be correct.
286 heads missing, this function updates self to be correct.
504 """
287 """
505 assert self._filtername == repo.filtername, (
506 self._filtername,
507 repo.filtername,
508 )
509 starttime = util.timer()
288 starttime = util.timer()
510 cl = repo.changelog
289 cl = repo.changelog
511 # collect new branch entries
290 # collect new branch entries
512 newbranches = {}
291 newbranches = {}
513 getbranchinfo = repo.revbranchcache().branchinfo
292 getbranchinfo = repo.revbranchcache().branchinfo
293 max_rev = -1
514 for r in revgen:
294 for r in revgen:
515 branch, closesbranch = getbranchinfo(r)
295 branch, closesbranch = getbranchinfo(r)
516 newbranches.setdefault(branch, []).append(r)
296 newbranches.setdefault(branch, []).append(r)
517 if closesbranch:
297 if closesbranch:
518 self._closednodes.add(cl.node(r))
298 self._closednodes.add(cl.node(r))
519
299 max_rev = max(max_rev, r)
520 # new tip revision which we found after iterating items from new
300 if max_rev < 0:
521 # branches
301 max_rev = None
522 ntiprev = self.tiprev
523
302
524 # Delay fetching the topological heads until they are needed.
303 # Delay fetching the topological heads until they are needed.
525 # A repository without non-continous branches can skip this part.
304 # A repository without non-continous branches can skip this part.
@@ -613,13 +392,287 b' class _BaseBranchCache:'
613 bheadset -= ancestors
392 bheadset -= ancestors
614 if bheadset:
393 if bheadset:
615 self[branch] = [cl.node(rev) for rev in sorted(bheadset)]
394 self[branch] = [cl.node(rev) for rev in sorted(bheadset)]
616 tiprev = max(newheadrevs)
395
617 if tiprev > ntiprev:
396 duration = util.timer() - starttime
618 ntiprev = tiprev
397 repo.ui.log(
398 b'branchcache',
399 b'updated %s in %.4f seconds\n',
400 _branchcachedesc(repo),
401 duration,
402 )
403 return max_rev
404
405
406 class branchcache(_BaseBranchCache):
407 """Branchmap info for a local repo or repoview"""
408
409 def __init__(
410 self,
411 repo: "localrepo.localrepository",
412 entries: Union[
413 Dict[bytes, List[bytes]], Iterable[Tuple[bytes, List[bytes]]]
414 ] = (),
415 tipnode: Optional[bytes] = None,
416 tiprev: Optional[int] = nullrev,
417 filteredhash: Optional[bytes] = None,
418 closednodes: Optional[Set[bytes]] = None,
419 hasnode: Optional[Callable[[bytes], bool]] = None,
420 verify_node: bool = False,
421 ) -> None:
422 """hasnode is a function which can be used to verify whether changelog
423 has a given node or not. If it's not provided, we assume that every node
424 we have exists in changelog"""
425 self._filtername = repo.filtername
426 self._delayed = False
427 if tipnode is None:
428 self.tipnode = repo.nullid
429 else:
430 self.tipnode = tipnode
431 self.tiprev = tiprev
432 self.filteredhash = filteredhash
433
434 super().__init__(repo=repo, entries=entries, closed_nodes=closednodes)
435 # closednodes is a set of nodes that close their branch. If the branch
436 # cache has been updated, it may contain nodes that are no longer
437 # heads.
438
439 # Do we need to verify branch at all ?
440 self._verify_node = verify_node
441 # whether closed nodes are verified or not
442 self._closedverified = False
443 # branches for which nodes are verified
444 self._verifiedbranches = set()
445 self._hasnode = None
446 if self._verify_node:
447 self._hasnode = repo.changelog.hasnode
448
449 def validfor(self, repo):
450 """check that cache contents are valid for (a subset of) this repo
451
452 - False when the order of changesets changed or if we detect a strip.
453 - True when cache is up-to-date for the current repo or its subset."""
454 try:
455 node = repo.changelog.node(self.tiprev)
456 except IndexError:
457 # changesets were stripped and now we don't even have enough to
458 # find tiprev
459 return False
460 if self.tipnode != node:
461 # tiprev doesn't correspond to tipnode: repo was stripped, or this
462 # repo has a different order of changesets
463 return False
464 tiphash = scmutil.filteredhash(repo, self.tiprev, needobsolete=True)
465 # hashes don't match if this repo view has a different set of filtered
466 # revisions (e.g. due to phase changes) or obsolete revisions (e.g.
467 # history was rewritten)
468 return self.filteredhash == tiphash
469
470 @classmethod
471 def fromfile(cls, repo):
472 f = None
473 try:
474 f = repo.cachevfs(cls._filename(repo))
475 lineiter = iter(f)
476 cachekey = next(lineiter).rstrip(b'\n').split(b" ", 2)
477 last, lrev = cachekey[:2]
478 last, lrev = bin(last), int(lrev)
479 filteredhash = None
480 if len(cachekey) > 2:
481 filteredhash = bin(cachekey[2])
482 bcache = cls(
483 repo,
484 tipnode=last,
485 tiprev=lrev,
486 filteredhash=filteredhash,
487 verify_node=True,
488 )
489 if not bcache.validfor(repo):
490 # invalidate the cache
491 raise ValueError('tip differs')
492 bcache.load(repo, lineiter)
493 except (IOError, OSError):
494 return None
495
496 except Exception as inst:
497 if repo.ui.debugflag:
498 msg = b'invalid %s: %s\n'
499 repo.ui.debug(
500 msg
501 % (
502 _branchcachedesc(repo),
503 stringutil.forcebytestr(inst),
504 )
505 )
506 bcache = None
507
508 finally:
509 if f:
510 f.close()
511
512 return bcache
513
514 def load(self, repo, lineiter):
515 """fully loads the branchcache by reading from the file using the line
516 iterator passed"""
517 for line in lineiter:
518 line = line.rstrip(b'\n')
519 if not line:
520 continue
521 node, state, label = line.split(b" ", 2)
522 if state not in b'oc':
523 raise ValueError('invalid branch state')
524 label = encoding.tolocal(label.strip())
525 node = bin(node)
526 self._entries.setdefault(label, []).append(node)
527 if state == b'c':
528 self._closednodes.add(node)
619
529
620 if ntiprev > self.tiprev:
530 @staticmethod
621 self.tiprev = ntiprev
531 def _filename(repo):
622 self.tipnode = cl.node(ntiprev)
532 """name of a branchcache file for a given repo or repoview"""
533 filename = b"branch2"
534 if repo.filtername:
535 filename = b'%s-%s' % (filename, repo.filtername)
536 return filename
537
538 def copy(self, repo):
539 """return a deep copy of the branchcache object"""
540 other = type(self)(
541 repo=repo,
542 # we always do a shally copy of self._entries, and the values is
543 # always replaced, so no need to deepcopy until the above remains
544 # true.
545 entries=self._entries,
546 tipnode=self.tipnode,
547 tiprev=self.tiprev,
548 filteredhash=self.filteredhash,
549 closednodes=set(self._closednodes),
550 verify_node=self._verify_node,
551 )
552 # we copy will likely schedule a write anyway, but that does not seems
553 # to hurt to overschedule
554 other._delayed = self._delayed
555 # also copy information about the current verification state
556 other._closedverified = self._closedverified
557 other._verifiedbranches = set(self._verifiedbranches)
558 return other
559
560 def write(self, repo):
561 assert self._filtername == repo.filtername, (
562 self._filtername,
563 repo.filtername,
564 )
565 tr = repo.currenttransaction()
566 if not getattr(tr, 'finalized', True):
567 # Avoid premature writing.
568 #
569 # (The cache warming setup by localrepo will update the file later.)
570 self._delayed = True
571 return
572 try:
573 filename = self._filename(repo)
574 with repo.cachevfs(filename, b"w", atomictemp=True) as f:
575 cachekey = [hex(self.tipnode), b'%d' % self.tiprev]
576 if self.filteredhash is not None:
577 cachekey.append(hex(self.filteredhash))
578 f.write(b" ".join(cachekey) + b'\n')
579 nodecount = 0
580 for label, nodes in sorted(self._entries.items()):
581 label = encoding.fromlocal(label)
582 for node in nodes:
583 nodecount += 1
584 if node in self._closednodes:
585 state = b'c'
586 else:
587 state = b'o'
588 f.write(b"%s %s %s\n" % (hex(node), state, label))
589 repo.ui.log(
590 b'branchcache',
591 b'wrote %s with %d labels and %d nodes\n',
592 _branchcachedesc(repo),
593 len(self._entries),
594 nodecount,
595 )
596 self._delayed = False
597 except (IOError, OSError, error.Abort) as inst:
598 # Abort may be raised by read only opener, so log and continue
599 repo.ui.debug(
600 b"couldn't write branch cache: %s\n"
601 % stringutil.forcebytestr(inst)
602 )
603
604 def _verifyclosed(self):
605 """verify the closed nodes we have"""
606 if not self._verify_node:
607 return
608 if self._closedverified:
609 return
610 assert self._hasnode is not None
611 for node in self._closednodes:
612 if not self._hasnode(node):
613 _unknownnode(node)
614
615 self._closedverified = True
616
617 def _verifybranch(self, branch):
618 """verify head nodes for the given branch."""
619 if not self._verify_node:
620 return
621 if branch not in self._entries or branch in self._verifiedbranches:
622 return
623 assert self._hasnode is not None
624 for n in self._entries[branch]:
625 if not self._hasnode(n):
626 _unknownnode(n)
627
628 self._verifiedbranches.add(branch)
629
630 def _verifyall(self):
631 """verifies nodes of all the branches"""
632 for b in self._entries.keys():
633 if b not in self._verifiedbranches:
634 self._verifybranch(b)
635
636 def __getitem__(self, key):
637 self._verifybranch(key)
638 return super().__getitem__(key)
639
640 def __contains__(self, key):
641 self._verifybranch(key)
642 return super().__contains__(key)
643
644 def iteritems(self):
645 self._verifyall()
646 return super().iteritems()
647
648 items = iteritems
649
650 def iterheads(self):
651 """returns all the heads"""
652 self._verifyall()
653 return super().iterheads()
654
655 def hasbranch(self, label):
656 """checks whether a branch of this name exists or not"""
657 self._verifybranch(label)
658 return super().hasbranch(label)
659
660 def branchheads(self, branch, closed=False):
661 self._verifybranch(branch)
662 return super().branchheads(branch, closed=closed)
663
664 def update(self, repo, revgen):
665 assert self._filtername == repo.filtername, (
666 self._filtername,
667 repo.filtername,
668 )
669 cl = repo.changelog
670 max_rev = super().update(repo, revgen)
671 # new tip revision which we found after iterating items from new
672 # branches
673 if max_rev is not None and max_rev > self.tiprev:
674 self.tiprev = max_rev
675 self.tipnode = cl.node(max_rev)
623
676
624 if not self.validfor(repo):
677 if not self.validfor(repo):
625 # old cache key is now invalid for the repo, but we've just updated
678 # old cache key is now invalid for the repo, but we've just updated
@@ -641,24 +694,22 b' class _BaseBranchCache:'
641 repo, self.tiprev, needobsolete=True
694 repo, self.tiprev, needobsolete=True
642 )
695 )
643
696
644 duration = util.timer() - starttime
645 repo.ui.log(
646 b'branchcache',
647 b'updated %s in %.4f seconds\n',
648 _branchcachedesc(repo),
649 duration,
650 )
651
652 self.write(repo)
697 self.write(repo)
653
698
654
699
655 class branchcache(_BaseBranchCache):
656 """Branchmap info for a local repo or repoview"""
657
658
659 class remotebranchcache(_BaseBranchCache):
700 class remotebranchcache(_BaseBranchCache):
660 """Branchmap info for a remote connection, should not write locally"""
701 """Branchmap info for a remote connection, should not write locally"""
661
702
703 def __init__(
704 self,
705 repo: "localrepo.localrepository",
706 entries: Union[
707 Dict[bytes, List[bytes]], Iterable[Tuple[bytes, List[bytes]]]
708 ] = (),
709 closednodes: Optional[Set[bytes]] = None,
710 ) -> None:
711 super().__init__(repo=repo, entries=entries, closed_nodes=closednodes)
712
662
713
663 # Revision branch info cache
714 # Revision branch info cache
664
715
General Comments 0
You need to be logged in to leave comments. Login now