Mercurial > hg
comparison mercurial/branchmap.py @ 51536:718f28ea3af4
branchcache: add a "pure topological head" fast path
In a narrow but actually quick common case, all topological heads are all on
the same branch and all open. In this case, computing the branch map is very
simple. We can quickly detect situation where this situation will not change.
So we update the V3 format to be able to express this situation and upgrade the
update code to detect we remains in that mode.
The branch cache is populated with the actual value when the branch map is
accessed, but the update_disk method can do the update without needing to
populate it.
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Thu, 07 Mar 2024 04:15:23 +0100 |
parents | 03247e37ccf7 |
children | 4a8bb136ee77 |
comparison
equal
deleted
inserted
replaced
51535:03247e37ccf7 | 51536:718f28ea3af4 |
---|---|
60 self._per_filter = {} | 60 self._per_filter = {} |
61 | 61 |
62 def __getitem__(self, repo): | 62 def __getitem__(self, repo): |
63 self.updatecache(repo) | 63 self.updatecache(repo) |
64 bcache = self._per_filter[repo.filtername] | 64 bcache = self._per_filter[repo.filtername] |
65 bcache._ensure_populated(repo) | |
65 assert bcache._filtername == repo.filtername, ( | 66 assert bcache._filtername == repo.filtername, ( |
66 bcache._filtername, | 67 bcache._filtername, |
67 repo.filtername, | 68 repo.filtername, |
68 ) | 69 ) |
69 return bcache | 70 return bcache |
482 self._hasnode = repo.changelog.hasnode | 483 self._hasnode = repo.changelog.hasnode |
483 | 484 |
484 def _compute_key_hashes(self, repo) -> Tuple[bytes]: | 485 def _compute_key_hashes(self, repo) -> Tuple[bytes]: |
485 raise NotImplementedError | 486 raise NotImplementedError |
486 | 487 |
488 def _ensure_populated(self, repo): | |
489 """make sure any lazily loaded values are fully populated""" | |
490 | |
487 def validfor(self, repo): | 491 def validfor(self, repo): |
488 """check that cache contents are valid for (a subset of) this repo | 492 """check that cache contents are valid for (a subset of) this repo |
489 | 493 |
490 - False when the order of changesets changed or if we detect a strip. | 494 - False when the order of changesets changed or if we detect a strip. |
491 - True when cache is up-to-date for the current repo or its subset.""" | 495 - True when cache is up-to-date for the current repo or its subset.""" |
859 """ | 863 """ |
860 | 864 |
861 _base_filename = b"branch3" | 865 _base_filename = b"branch3" |
862 _default_key_hashes = (None, None) | 866 _default_key_hashes = (None, None) |
863 | 867 |
864 def _get_topo_heads(self, repo) -> List[int]: | 868 def __init__(self, *args, pure_topo_branch=None, **kwargs): |
869 super().__init__(*args, **kwargs) | |
870 self._pure_topo_branch = pure_topo_branch | |
871 self._needs_populate = self._pure_topo_branch is not None | |
872 | |
873 def inherit_for(self, repo): | |
874 new = super().inherit_for(repo) | |
875 new._pure_topo_branch = self._pure_topo_branch | |
876 new._needs_populate = self._needs_populate | |
877 return new | |
878 | |
879 def _get_topo_heads(self, repo): | |
865 """returns the topological head of a repoview content up to self.tiprev""" | 880 """returns the topological head of a repoview content up to self.tiprev""" |
866 cl = repo.changelog | 881 cl = repo.changelog |
867 if self.tiprev == nullrev: | 882 if self.tiprev == nullrev: |
868 return [] | 883 return [] |
869 elif self.tiprev == cl.tiprev(): | 884 elif self.tiprev == cl.tiprev(): |
881 if self.key_hashes: | 896 if self.key_hashes: |
882 if self.key_hashes[0] is not None: | 897 if self.key_hashes[0] is not None: |
883 cache_keys[b"filtered-hash"] = hex(self.key_hashes[0]) | 898 cache_keys[b"filtered-hash"] = hex(self.key_hashes[0]) |
884 if self.key_hashes[1] is not None: | 899 if self.key_hashes[1] is not None: |
885 cache_keys[b"obsolete-hash"] = hex(self.key_hashes[1]) | 900 cache_keys[b"obsolete-hash"] = hex(self.key_hashes[1]) |
901 if self._pure_topo_branch is not None: | |
902 cache_keys[b"topo-mode"] = b"pure" | |
886 pieces = (b"%s=%s" % i for i in sorted(cache_keys.items())) | 903 pieces = (b"%s=%s" % i for i in sorted(cache_keys.items())) |
887 fp.write(b" ".join(pieces) + b'\n') | 904 fp.write(b" ".join(pieces) + b'\n') |
905 if self._pure_topo_branch is not None: | |
906 label = encoding.fromlocal(self._pure_topo_branch) | |
907 fp.write(label + b'\n') | |
888 | 908 |
889 def _write_heads(self, repo, fp) -> int: | 909 def _write_heads(self, repo, fp) -> int: |
890 """write list of heads to a file | 910 """write list of heads to a file |
891 | 911 |
892 Return the number of heads written.""" | 912 Return the number of heads written.""" |
893 nodecount = 0 | 913 nodecount = 0 |
894 topo_heads = set(self._get_topo_heads(repo)) | 914 topo_heads = None |
915 if self._pure_topo_branch is None: | |
916 topo_heads = set(self._get_topo_heads(repo)) | |
895 to_rev = repo.changelog.index.rev | 917 to_rev = repo.changelog.index.rev |
896 for label, nodes in sorted(self._entries.items()): | 918 for label, nodes in sorted(self._entries.items()): |
919 if label == self._pure_topo_branch: | |
920 # not need to write anything the header took care of that | |
921 continue | |
897 label = encoding.fromlocal(label) | 922 label = encoding.fromlocal(label) |
898 for node in nodes: | 923 for node in nodes: |
899 rev = to_rev(node) | 924 if topo_heads is not None: |
900 if rev in topo_heads: | 925 rev = to_rev(node) |
901 continue | 926 if rev in topo_heads: |
927 continue | |
902 if node in self._closednodes: | 928 if node in self._closednodes: |
903 state = b'c' | 929 state = b'c' |
904 else: | 930 else: |
905 state = b'o' | 931 state = b'o' |
906 nodecount += 1 | 932 nodecount += 1 |
914 cache_keys = dict(p.split(b'=', 1) for p in pieces) | 940 cache_keys = dict(p.split(b'=', 1) for p in pieces) |
915 | 941 |
916 args = {} | 942 args = {} |
917 filtered_hash = None | 943 filtered_hash = None |
918 obsolete_hash = None | 944 obsolete_hash = None |
945 has_pure_topo_heads = False | |
919 for k, v in cache_keys.items(): | 946 for k, v in cache_keys.items(): |
920 if k == b"tip-rev": | 947 if k == b"tip-rev": |
921 args["tiprev"] = int(v) | 948 args["tiprev"] = int(v) |
922 elif k == b"tip-node": | 949 elif k == b"tip-node": |
923 args["tipnode"] = bin(v) | 950 args["tipnode"] = bin(v) |
924 elif k == b"filtered-hash": | 951 elif k == b"filtered-hash": |
925 filtered_hash = bin(v) | 952 filtered_hash = bin(v) |
926 elif k == b"obsolete-hash": | 953 elif k == b"obsolete-hash": |
927 obsolete_hash = bin(v) | 954 obsolete_hash = bin(v) |
955 elif k == b"topo-mode": | |
956 if v == b"pure": | |
957 has_pure_topo_heads = True | |
958 else: | |
959 msg = b"unknown topo-mode: %r" % v | |
960 raise ValueError(msg) | |
928 else: | 961 else: |
929 msg = b"unknown cache key: %r" % k | 962 msg = b"unknown cache key: %r" % k |
930 raise ValueError(msg) | 963 raise ValueError(msg) |
931 args["key_hashes"] = (filtered_hash, obsolete_hash) | 964 args["key_hashes"] = (filtered_hash, obsolete_hash) |
965 if has_pure_topo_heads: | |
966 pure_line = next(lineiter).rstrip(b'\n') | |
967 args["pure_topo_branch"] = encoding.tolocal(pure_line) | |
932 return args | 968 return args |
933 | 969 |
934 def _load_heads(self, repo, lineiter): | 970 def _load_heads(self, repo, lineiter): |
935 """fully loads the branchcache by reading from the file using the line | 971 """fully loads the branchcache by reading from the file using the line |
936 iterator passed""" | 972 iterator passed""" |
937 super()._load_heads(repo, lineiter) | 973 super()._load_heads(repo, lineiter) |
974 if self._pure_topo_branch is not None: | |
975 # no need to read the repository heads, we know their value already. | |
976 return | |
938 cl = repo.changelog | 977 cl = repo.changelog |
939 getbranchinfo = repo.revbranchcache().branchinfo | 978 getbranchinfo = repo.revbranchcache().branchinfo |
940 obsrevs = obsolete.getrevs(repo, b'obsolete') | 979 obsrevs = obsolete.getrevs(repo, b'obsolete') |
941 to_node = cl.node | 980 to_node = cl.node |
942 touched_branch = set() | 981 touched_branch = set() |
957 """return the cache key hashes that match this repoview state""" | 996 """return the cache key hashes that match this repoview state""" |
958 return scmutil.filtered_and_obsolete_hash( | 997 return scmutil.filtered_and_obsolete_hash( |
959 repo, | 998 repo, |
960 self.tiprev, | 999 self.tiprev, |
961 ) | 1000 ) |
1001 | |
1002 def _process_new( | |
1003 self, | |
1004 repo, | |
1005 newbranches, | |
1006 new_closed, | |
1007 obs_ignored, | |
1008 max_rev, | |
1009 ) -> None: | |
1010 if ( | |
1011 # note: the check about `obs_ignored` is too strict as the | |
1012 # obsolete revision could be non-topological, but lets keep | |
1013 # things simple for now | |
1014 # | |
1015 # The same apply to `new_closed` if the closed changeset are | |
1016 # not a head, we don't care that it is closed, but lets keep | |
1017 # things simple here too. | |
1018 not (obs_ignored or new_closed) | |
1019 and ( | |
1020 not newbranches | |
1021 or ( | |
1022 len(newbranches) == 1 | |
1023 and ( | |
1024 self.tiprev == nullrev | |
1025 or self._pure_topo_branch in newbranches | |
1026 ) | |
1027 ) | |
1028 ) | |
1029 ): | |
1030 if newbranches: | |
1031 assert len(newbranches) == 1 | |
1032 self._pure_topo_branch = list(newbranches.keys())[0] | |
1033 self._needs_populate = True | |
1034 self._entries.pop(self._pure_topo_branch, None) | |
1035 return | |
1036 | |
1037 self._ensure_populated(repo) | |
1038 self._pure_topo_branch = None | |
1039 super()._process_new( | |
1040 repo, | |
1041 newbranches, | |
1042 new_closed, | |
1043 obs_ignored, | |
1044 max_rev, | |
1045 ) | |
1046 | |
1047 def _ensure_populated(self, repo): | |
1048 """make sure any lazily loaded values are fully populated""" | |
1049 if self._needs_populate: | |
1050 assert self._pure_topo_branch is not None | |
1051 cl = repo.changelog | |
1052 to_node = cl.node | |
1053 topo_heads = self._get_topo_heads(repo) | |
1054 heads = [to_node(r) for r in topo_heads] | |
1055 self._entries[self._pure_topo_branch] = heads | |
1056 self._needs_populate = False | |
962 | 1057 |
963 | 1058 |
964 class remotebranchcache(_BaseBranchCache): | 1059 class remotebranchcache(_BaseBranchCache): |
965 """Branchmap info for a remote connection, should not write locally""" | 1060 """Branchmap info for a remote connection, should not write locally""" |
966 | 1061 |