407 |
408 |
408 class _LocalBranchCache(_BaseBranchCache): |
409 class _LocalBranchCache(_BaseBranchCache): |
409 """base class of branch-map info for a local repo or repoview""" |
410 """base class of branch-map info for a local repo or repoview""" |
410 |
411 |
411 _base_filename = None |
412 _base_filename = None |
|
413 _default_key_hashes: Tuple[bytes] = cast(Tuple[bytes], ()) |
412 |
414 |
413 def __init__( |
415 def __init__( |
414 self, |
416 self, |
415 repo: "localrepo.localrepository", |
417 repo: "localrepo.localrepository", |
416 entries: Union[ |
418 entries: Union[ |
417 Dict[bytes, List[bytes]], Iterable[Tuple[bytes, List[bytes]]] |
419 Dict[bytes, List[bytes]], Iterable[Tuple[bytes, List[bytes]]] |
418 ] = (), |
420 ] = (), |
419 tipnode: Optional[bytes] = None, |
421 tipnode: Optional[bytes] = None, |
420 tiprev: Optional[int] = nullrev, |
422 tiprev: Optional[int] = nullrev, |
421 filteredhash: Optional[bytes] = None, |
423 key_hashes: Optional[Tuple[bytes]] = None, |
422 closednodes: Optional[Set[bytes]] = None, |
424 closednodes: Optional[Set[bytes]] = None, |
423 hasnode: Optional[Callable[[bytes], bool]] = None, |
425 hasnode: Optional[Callable[[bytes], bool]] = None, |
424 verify_node: bool = False, |
426 verify_node: bool = False, |
425 inherited: bool = False, |
427 inherited: bool = False, |
426 ) -> None: |
428 ) -> None: |
431 if tipnode is None: |
433 if tipnode is None: |
432 self.tipnode = repo.nullid |
434 self.tipnode = repo.nullid |
433 else: |
435 else: |
434 self.tipnode = tipnode |
436 self.tipnode = tipnode |
435 self.tiprev = tiprev |
437 self.tiprev = tiprev |
436 self.filteredhash = filteredhash |
438 if key_hashes is None: |
|
439 self.key_hashes = self._default_key_hashes |
|
440 else: |
|
441 self.key_hashes = key_hashes |
437 self._state = STATE_CLEAN |
442 self._state = STATE_CLEAN |
438 if inherited: |
443 if inherited: |
439 self._state = STATE_INHERITED |
444 self._state = STATE_INHERITED |
440 |
445 |
441 super().__init__(repo=repo, entries=entries, closed_nodes=closednodes) |
446 super().__init__(repo=repo, entries=entries, closed_nodes=closednodes) |
448 # branches for which nodes are verified |
453 # branches for which nodes are verified |
449 self._verifiedbranches = set() |
454 self._verifiedbranches = set() |
450 self._hasnode = None |
455 self._hasnode = None |
451 if self._verify_node: |
456 if self._verify_node: |
452 self._hasnode = repo.changelog.hasnode |
457 self._hasnode = repo.changelog.hasnode |
|
458 |
|
459 def _compute_key_hashes(self, repo) -> Tuple[bytes]: |
|
460 raise NotImplementedError |
453 |
461 |
454 def validfor(self, repo): |
462 def validfor(self, repo): |
455 """check that cache contents are valid for (a subset of) this repo |
463 """check that cache contents are valid for (a subset of) this repo |
456 |
464 |
457 - False when the order of changesets changed or if we detect a strip. |
465 - False when the order of changesets changed or if we detect a strip. |
464 return False |
472 return False |
465 if self.tipnode != node: |
473 if self.tipnode != node: |
466 # tiprev doesn't correspond to tipnode: repo was stripped, or this |
474 # tiprev doesn't correspond to tipnode: repo was stripped, or this |
467 # repo has a different order of changesets |
475 # repo has a different order of changesets |
468 return False |
476 return False |
469 tiphash = scmutil.combined_filtered_and_obsolete_hash( |
477 repo_key_hashes = self._compute_key_hashes(repo) |
470 repo, |
|
471 self.tiprev, |
|
472 needobsolete=True, |
|
473 ) |
|
474 # hashes don't match if this repo view has a different set of filtered |
478 # hashes don't match if this repo view has a different set of filtered |
475 # revisions (e.g. due to phase changes) or obsolete revisions (e.g. |
479 # revisions (e.g. due to phase changes) or obsolete revisions (e.g. |
476 # history was rewritten) |
480 # history was rewritten) |
477 return self.filteredhash == tiphash |
481 return self.key_hashes == repo_key_hashes |
478 |
482 |
479 @classmethod |
483 @classmethod |
480 def fromfile(cls, repo): |
484 def fromfile(cls, repo): |
481 f = None |
485 f = None |
482 try: |
486 try: |
511 |
515 |
512 return bcache |
516 return bcache |
513 |
517 |
514 @classmethod |
518 @classmethod |
515 def _load_header(cls, repo, lineiter) -> "dict[str, Any]": |
519 def _load_header(cls, repo, lineiter) -> "dict[str, Any]": |
516 """parse the head of a branchmap file |
520 raise NotImplementedError |
517 |
|
518 return parameters to pass to a newly created class instance. |
|
519 """ |
|
520 cachekey = next(lineiter).rstrip(b'\n').split(b" ", 2) |
|
521 last, lrev = cachekey[:2] |
|
522 last, lrev = bin(last), int(lrev) |
|
523 filteredhash = None |
|
524 if len(cachekey) > 2: |
|
525 filteredhash = bin(cachekey[2]) |
|
526 return { |
|
527 "tipnode": last, |
|
528 "tiprev": lrev, |
|
529 "filteredhash": filteredhash, |
|
530 } |
|
531 |
521 |
532 def _load_heads(self, repo, lineiter): |
522 def _load_heads(self, repo, lineiter): |
533 """fully loads the branchcache by reading from the file using the line |
523 """fully loads the branchcache by reading from the file using the line |
534 iterator passed""" |
524 iterator passed""" |
535 for line in lineiter: |
525 for line in lineiter: |
563 # always replaced, so no need to deepcopy until the above remains |
553 # always replaced, so no need to deepcopy until the above remains |
564 # true. |
554 # true. |
565 entries=self._entries, |
555 entries=self._entries, |
566 tipnode=self.tipnode, |
556 tipnode=self.tipnode, |
567 tiprev=self.tiprev, |
557 tiprev=self.tiprev, |
568 filteredhash=self.filteredhash, |
558 key_hashes=self.key_hashes, |
569 closednodes=set(self._closednodes), |
559 closednodes=set(self._closednodes), |
570 verify_node=self._verify_node, |
560 verify_node=self._verify_node, |
571 inherited=True, |
561 inherited=True, |
572 ) |
562 ) |
573 # also copy information about the current verification state |
563 # also copy information about the current verification state |
619 b"couldn't write branch cache: %s\n" |
609 b"couldn't write branch cache: %s\n" |
620 % stringutil.forcebytestr(inst) |
610 % stringutil.forcebytestr(inst) |
621 ) |
611 ) |
622 |
612 |
623 def _write_header(self, fp) -> None: |
613 def _write_header(self, fp) -> None: |
624 """write the branch cache header to a file""" |
614 raise NotImplementedError |
625 cachekey = [hex(self.tipnode), b'%d' % self.tiprev] |
|
626 if self.filteredhash is not None: |
|
627 cachekey.append(hex(self.filteredhash)) |
|
628 fp.write(b" ".join(cachekey) + b'\n') |
|
629 |
615 |
630 def _write_heads(self, fp) -> int: |
616 def _write_heads(self, fp) -> int: |
631 """write list of heads to a file |
617 """write list of heads to a file |
632 |
618 |
633 Return the number of heads written.""" |
619 Return the number of heads written.""" |
712 # invalid for the repo. |
698 # invalid for the repo. |
713 # |
699 # |
714 # However. we've just updated the cache and we assume it's valid, |
700 # However. we've just updated the cache and we assume it's valid, |
715 # so let's make the cache key valid as well by recomputing it from |
701 # so let's make the cache key valid as well by recomputing it from |
716 # the cached data |
702 # the cached data |
|
703 self.key_hashes = self._compute_key_hashes(repo) |
717 self.filteredhash = scmutil.combined_filtered_and_obsolete_hash( |
704 self.filteredhash = scmutil.combined_filtered_and_obsolete_hash( |
718 repo, |
705 repo, |
719 self.tiprev, |
706 self.tiprev, |
720 needobsolete=True, |
|
721 ) |
707 ) |
722 |
708 |
723 self._state = STATE_DIRTY |
709 self._state = STATE_DIRTY |
724 tr = repo.currenttransaction() |
710 tr = repo.currenttransaction() |
725 if getattr(tr, 'finalized', True): |
711 if getattr(tr, 'finalized', True): |
770 branch head closes a branch or not. |
756 branch head closes a branch or not. |
771 """ |
757 """ |
772 |
758 |
773 _base_filename = b"branch2" |
759 _base_filename = b"branch2" |
774 |
760 |
|
761 @classmethod |
|
762 def _load_header(cls, repo, lineiter) -> "dict[str, Any]": |
|
763 """parse the head of a branchmap file |
|
764 |
|
765 return parameters to pass to a newly created class instance. |
|
766 """ |
|
767 cachekey = next(lineiter).rstrip(b'\n').split(b" ", 2) |
|
768 last, lrev = cachekey[:2] |
|
769 last, lrev = bin(last), int(lrev) |
|
770 filteredhash = () |
|
771 if len(cachekey) > 2: |
|
772 filteredhash = (bin(cachekey[2]),) |
|
773 return { |
|
774 "tipnode": last, |
|
775 "tiprev": lrev, |
|
776 "key_hashes": filteredhash, |
|
777 } |
|
778 |
|
779 def _write_header(self, fp) -> None: |
|
780 """write the branch cache header to a file""" |
|
781 cachekey = [hex(self.tipnode), b'%d' % self.tiprev] |
|
782 if self.key_hashes: |
|
783 cachekey.append(hex(self.key_hashes[0])) |
|
784 fp.write(b" ".join(cachekey) + b'\n') |
|
785 |
|
786 def _compute_key_hashes(self, repo) -> Tuple[bytes]: |
|
787 """return the cache key hashes that match this repoview state""" |
|
788 filtered_hash = scmutil.combined_filtered_and_obsolete_hash( |
|
789 repo, |
|
790 self.tiprev, |
|
791 needobsolete=True, |
|
792 ) |
|
793 keys: Tuple[bytes] = cast(Tuple[bytes], ()) |
|
794 if filtered_hash is not None: |
|
795 keys: Tuple[bytes] = (filtered_hash,) |
|
796 return keys |
|
797 |
775 |
798 |
776 class BranchCacheV3(_LocalBranchCache): |
799 class BranchCacheV3(_LocalBranchCache): |
777 """a branch cache using version 3 of the format on disk |
800 """a branch cache using version 3 of the format on disk |
778 |
801 |
779 This version is still EXPERIMENTAL and the format is subject to changes. |
802 This version is still EXPERIMENTAL and the format is subject to changes. |
809 def _write_header(self, fp) -> None: |
832 def _write_header(self, fp) -> None: |
810 cache_keys = { |
833 cache_keys = { |
811 b"tip-node": hex(self.tipnode), |
834 b"tip-node": hex(self.tipnode), |
812 b"tip-rev": b'%d' % self.tiprev, |
835 b"tip-rev": b'%d' % self.tiprev, |
813 } |
836 } |
814 if self.filteredhash is not None: |
837 if self.key_hashes: |
815 cache_keys[b"filtered-hash"] = hex(self.filteredhash) |
838 cache_keys[b"filtered-hash"] = hex(self.key_hashes[0]) |
816 pieces = (b"%s=%s" % i for i in sorted(cache_keys.items())) |
839 pieces = (b"%s=%s" % i for i in sorted(cache_keys.items())) |
817 fp.write(b" ".join(pieces) + b'\n') |
840 fp.write(b" ".join(pieces) + b'\n') |
818 |
841 |
819 @classmethod |
842 @classmethod |
820 def _load_header(cls, repo, lineiter): |
843 def _load_header(cls, repo, lineiter): |
827 if k == b"tip-rev": |
850 if k == b"tip-rev": |
828 args["tiprev"] = int(v) |
851 args["tiprev"] = int(v) |
829 elif k == b"tip-node": |
852 elif k == b"tip-node": |
830 args["tipnode"] = bin(v) |
853 args["tipnode"] = bin(v) |
831 elif k == b"filtered-hash": |
854 elif k == b"filtered-hash": |
832 args["filteredhash"] = bin(v) |
855 args["key_hashes"] = (bin(v),) |
833 else: |
856 else: |
834 msg = b"unknown cache key: %r" % k |
857 msg = b"unknown cache key: %r" % k |
835 raise ValueError(msg) |
858 raise ValueError(msg) |
836 return args |
859 return args |
|
860 |
|
861 def _compute_key_hashes(self, repo) -> Tuple[bytes]: |
|
862 """return the cache key hashes that match this repoview state""" |
|
863 filtered_hash = scmutil.combined_filtered_and_obsolete_hash( |
|
864 repo, |
|
865 self.tiprev, |
|
866 needobsolete=True, |
|
867 ) |
|
868 if filtered_hash is None: |
|
869 return cast(Tuple[bytes], ()) |
|
870 else: |
|
871 return (filtered_hash,) |
837 |
872 |
838 |
873 |
839 class remotebranchcache(_BaseBranchCache): |
874 class remotebranchcache(_BaseBranchCache): |
840 """Branchmap info for a remote connection, should not write locally""" |
875 """Branchmap info for a remote connection, should not write locally""" |
841 |
876 |