diff -r fc710c993ec9 -r f85f23f1479b mercurial/branchmap.py --- a/mercurial/branchmap.py Thu Mar 07 01:35:43 2024 +0100 +++ b/mercurial/branchmap.py Thu Mar 07 10:55:22 2024 +0100 @@ -594,7 +594,7 @@ filename = self._filename(repo) with repo.cachevfs(filename, b"w", atomictemp=True) as f: self._write_header(f) - nodecount = self._write_heads(f) + nodecount = self._write_heads(repo, f) repo.ui.log( b'branchcache', b'wrote %s with %d labels and %d nodes\n', @@ -613,7 +613,7 @@ def _write_header(self, fp) -> None: raise NotImplementedError - def _write_heads(self, fp) -> int: + def _write_heads(self, repo, fp) -> int: """write list of heads to a file Return the number of heads written.""" @@ -827,11 +827,27 @@ The open/closed state is represented by a single letter 'o' or 'c'. This field can be used to avoid changelog reads when determining if a branch head closes a branch or not. + + Topological heads are not included in the listing and should be dispatched + on the right branch at read time. Obsolete topological heads should be + ignored. """ _base_filename = b"branch3" _default_key_hashes = (None, None) + def _get_topo_heads(self, repo) -> List[int]: + """returns the topological head of a repoview content up to self.tiprev""" + cl = repo.changelog + if self.tiprev == nullrev: + return [] + elif self.tiprev == cl.tiprev(): + return cl.headrevs() + else: + # XXX passing tiprev as ceiling of cl.headrevs could be faster + heads = cl.headrevs(cl.revs(stop=self.tiprev)) + return heads + def _write_header(self, fp) -> None: cache_keys = { b"tip-node": hex(self.tipnode), @@ -845,6 +861,27 @@ pieces = (b"%s=%s" % i for i in sorted(cache_keys.items())) fp.write(b" ".join(pieces) + b'\n') + def _write_heads(self, repo, fp) -> int: + """write list of heads to a file + + Return the number of heads written.""" + nodecount = 0 + topo_heads = set(self._get_topo_heads(repo)) + to_rev = repo.changelog.index.rev + for label, nodes in sorted(self._entries.items()): + label = encoding.fromlocal(label) + for node in nodes: + rev = to_rev(node) + if rev in topo_heads: + continue + if node in self._closednodes: + state = b'c' + else: + state = b'o' + nodecount += 1 + fp.write(b"%s %s %s\n" % (hex(node), state, label)) + return nodecount + @classmethod def _load_header(cls, repo, lineiter): header_line = next(lineiter) @@ -869,6 +906,28 @@ args["key_hashes"] = (filtered_hash, obsolete_hash) return args + def _load_heads(self, repo, lineiter): + """fully loads the branchcache by reading from the file using the line + iterator passed""" + super()._load_heads(repo, lineiter) + cl = repo.changelog + getbranchinfo = repo.revbranchcache().branchinfo + obsrevs = obsolete.getrevs(repo, b'obsolete') + to_node = cl.node + touched_branch = set() + for head in self._get_topo_heads(repo): + if head in obsrevs: + continue + node = to_node(head) + branch, closed = getbranchinfo(head) + self._entries.setdefault(branch, []).append(node) + if closed: + self._closednodes.add(node) + touched_branch.add(branch) + to_rev = cl.index.rev + for branch in touched_branch: + self._entries[branch].sort(key=to_rev) + def _compute_key_hashes(self, repo) -> Tuple[bytes]: """return the cache key hashes that match this repoview state""" return scmutil.filtered_and_obsolete_hash(