Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/branchmap.py @ 51569:fa9e3976a5a0
branchcache: rework the `filteredhash` logic to be more generic
We now have a more flexible `key_hashes` tuple. We duplicated various logic in
the V2 and V3 version of the cache as the goal is to start changing the logic
for V3 in the next few changesets.
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Wed, 06 Mar 2024 02:20:53 +0100 |
parents | a03fa40afd01 |
children | 4141d12de073 |
comparison
equal
deleted
inserted
replaced
51568:a03fa40afd01 | 51569:fa9e3976a5a0 |
---|---|
23 Optional, | 23 Optional, |
24 Set, | 24 Set, |
25 TYPE_CHECKING, | 25 TYPE_CHECKING, |
26 Tuple, | 26 Tuple, |
27 Union, | 27 Union, |
28 cast, | |
28 ) | 29 ) |
29 | 30 |
30 from . import ( | 31 from . import ( |
31 encoding, | 32 encoding, |
32 error, | 33 error, |
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 |