branchcache: store filtered hash and obsolete hash independently for V3
authorPierre-Yves David <pierre-yves.david@octobus.net>
Wed, 06 Mar 2024 11:39:44 +0100
changeset 51529 4141d12de073
parent 51528 88b0e07dd2cd
child 51530 fc710c993ec9
branchcache: store filtered hash and obsolete hash independently for V3 This will avoid the bug covered in tests/test-branches-obsolete.t when we stop storing all heads explicitly in V3.
mercurial/branchmap.py
mercurial/scmutil.py
tests/test-branches-obsolete.t
--- a/mercurial/branchmap.py	Wed Mar 06 12:07:31 2024 +0100
+++ b/mercurial/branchmap.py	Wed Mar 06 11:39:44 2024 +0100
@@ -813,14 +813,16 @@
 
     - tip-rev: the rev-num of the tip-most revision seen by this cache
     - tip-node: the node-id of the tip-most revision sen by this cache
-    - filtered-hash: the hash of all filtered and obsolete revisions (before
+    - filtered-hash: the hash of all filtered revisions (before tip-rev)
+                     ignored by this cache.
+    - obsolete-hash: the hash of all non-filtered obsolete revisions (before
                      tip-rev) ignored by this cache.
 
     The tip-rev is used to know how far behind the value in the file are
     compared to the current repository state.
 
-    The tip-node and filtered-hash are used to detect if this cache can be used
-    for this repository state at all.
+    The tip-node, filtered-hash and obsolete-hash are used to detect if this
+    cache can be used for this repository state at all.
 
     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
@@ -828,6 +830,7 @@
     """
 
     _base_filename = b"branch3"
+    _default_key_hashes = (None, None)
 
     def _write_header(self, fp) -> None:
         cache_keys = {
@@ -835,7 +838,10 @@
             b"tip-rev": b'%d' % self.tiprev,
         }
         if self.key_hashes:
-            cache_keys[b"filtered-hash"] = hex(self.key_hashes[0])
+            if self.key_hashes[0] is not None:
+                cache_keys[b"filtered-hash"] = hex(self.key_hashes[0])
+            if self.key_hashes[1] is not None:
+                cache_keys[b"obsolete-hash"] = hex(self.key_hashes[1])
         pieces = (b"%s=%s" % i for i in sorted(cache_keys.items()))
         fp.write(b" ".join(pieces) + b'\n')
 
@@ -846,29 +852,29 @@
         cache_keys = dict(p.split(b'=', 1) for p in pieces)
 
         args = {}
+        filtered_hash = None
+        obsolete_hash = None
         for k, v in cache_keys.items():
             if k == b"tip-rev":
                 args["tiprev"] = int(v)
             elif k == b"tip-node":
                 args["tipnode"] = bin(v)
             elif k == b"filtered-hash":
-                args["key_hashes"] = (bin(v),)
+                filtered_hash = bin(v)
+            elif k == b"obsolete-hash":
+                obsolete_hash = bin(v)
             else:
                 msg = b"unknown cache key: %r" % k
                 raise ValueError(msg)
+        args["key_hashes"] = (filtered_hash, obsolete_hash)
         return args
 
     def _compute_key_hashes(self, repo) -> Tuple[bytes]:
         """return the cache key hashes that match this repoview state"""
-        filtered_hash = scmutil.combined_filtered_and_obsolete_hash(
+        return scmutil.filtered_and_obsolete_hash(
             repo,
             self.tiprev,
-            needobsolete=True,
         )
-        if filtered_hash is None:
-            return cast(Tuple[bytes], ())
-        else:
-            return (filtered_hash,)
 
 
 class remotebranchcache(_BaseBranchCache):
--- a/mercurial/scmutil.py	Wed Mar 06 12:07:31 2024 +0100
+++ b/mercurial/scmutil.py	Wed Mar 06 11:39:44 2024 +0100
@@ -385,6 +385,37 @@
     return result
 
 
+def filtered_and_obsolete_hash(repo, maxrev):
+    """build hashs of filtered and obsolete revisions in the current repoview.
+
+    Multiple caches perform up-to-date validation by checking that the
+    tiprev and tipnode stored in the cache file match the current repository.
+    However, this is not sufficient for validating repoviews because the set
+    of revisions in the view may change without the repository tiprev and
+    tipnode changing.
+
+    This function hashes all the revs filtered from the view up to maxrev and
+    returns that SHA-1 digest. The obsolete revisions hashed are only the
+    non-filtered one.
+    """
+    cl = repo.changelog
+    obs_set = obsolete.getrevs(repo, b'obsolete')
+    key = (maxrev, hash(cl.filteredrevs), hash(obs_set))
+
+    result = cl._filteredrevs_hashcache.get(key)
+    if result is None:
+        filtered_hash = None
+        obs_hash = None
+        filtered_revs, obs_revs = _filtered_and_obs_revs(repo, maxrev)
+        if filtered_revs:
+            filtered_hash = _hash_revs(filtered_revs)
+        if obs_revs:
+            obs_hash = _hash_revs(obs_revs)
+        result = (filtered_hash, obs_hash)
+        cl._filteredrevs_hashcache[key] = result
+    return result
+
+
 def _filtered_and_obs_revs(repo, max_rev):
     """return the set of filtered and non-filtered obsolete revision"""
     cl = repo.changelog
--- a/tests/test-branches-obsolete.t	Wed Mar 06 12:07:31 2024 +0100
+++ b/tests/test-branches-obsolete.t	Wed Mar 06 11:39:44 2024 +0100
@@ -210,11 +210,11 @@
 #else
   $ show_cache
   ##### .hg/cache/branch3
-  filtered-hash=a943c3355ad9e93654d58b1c934c7c4329a5d1d4 tip-node=3d808bbc94408ea19da905596d4079357a1f28be tip-rev=8
+  obsolete-hash=b6d2b1f5b70f09c25c835edcae69be35f681605c tip-node=3d808bbc94408ea19da905596d4079357a1f28be tip-rev=8
   550bb31f072912453ccbb503de1d554616911e88 o default
   7c29ff2453bf38c75ee8982935739103c38a9284 o default
   ##### .hg/cache/branch3-served
-  filtered-hash=a943c3355ad9e93654d58b1c934c7c4329a5d1d4 tip-node=3d808bbc94408ea19da905596d4079357a1f28be tip-rev=8
+  filtered-hash=f8006d64a10d35c011a5c5fa88be1e25c5929514 obsolete-hash=ac5282439f301518f362f37547fcd52bcc670373 tip-node=3d808bbc94408ea19da905596d4079357a1f28be tip-rev=8
   550bb31f072912453ccbb503de1d554616911e88 o default
   7c29ff2453bf38c75ee8982935739103c38a9284 o default
 #endif
@@ -235,7 +235,7 @@
 #else
   $ show_cache
   ##### .hg/cache/branch3-served
-  filtered-hash=a943c3355ad9e93654d58b1c934c7c4329a5d1d4 tip-node=3d808bbc94408ea19da905596d4079357a1f28be tip-rev=8
+  filtered-hash=f8006d64a10d35c011a5c5fa88be1e25c5929514 obsolete-hash=ac5282439f301518f362f37547fcd52bcc670373 tip-node=3d808bbc94408ea19da905596d4079357a1f28be tip-rev=8
   550bb31f072912453ccbb503de1d554616911e88 o default
   7c29ff2453bf38c75ee8982935739103c38a9284 o default
 #endif
@@ -304,11 +304,11 @@
 #else
   $ show_cache
   ##### .hg/cache/branch3
-  filtered-hash=a943c3355ad9e93654d58b1c934c7c4329a5d1d4 tip-node=3d808bbc94408ea19da905596d4079357a1f28be tip-rev=8
+  obsolete-hash=b6d2b1f5b70f09c25c835edcae69be35f681605c tip-node=3d808bbc94408ea19da905596d4079357a1f28be tip-rev=8
   550bb31f072912453ccbb503de1d554616911e88 o default
   7c29ff2453bf38c75ee8982935739103c38a9284 o default
   ##### .hg/cache/branch3-served
-  filtered-hash=f8006d64a10d35c011a5c5fa88be1e25c5929514 tip-node=7c29ff2453bf38c75ee8982935739103c38a9284 tip-rev=7
+  filtered-hash=f1456c0d675980582dda9b8edc7f13f503ce544f obsolete-hash=3e74f5349008671629e39d13d7e00d9ba94c74f7 tip-node=7c29ff2453bf38c75ee8982935739103c38a9284 tip-rev=7
   550bb31f072912453ccbb503de1d554616911e88 o default
   7c29ff2453bf38c75ee8982935739103c38a9284 o default
 #endif
@@ -329,7 +329,7 @@
 #else
   $ show_cache
   ##### .hg/cache/branch3-served
-  filtered-hash=f8006d64a10d35c011a5c5fa88be1e25c5929514 tip-node=7c29ff2453bf38c75ee8982935739103c38a9284 tip-rev=7
+  filtered-hash=f1456c0d675980582dda9b8edc7f13f503ce544f obsolete-hash=3e74f5349008671629e39d13d7e00d9ba94c74f7 tip-node=7c29ff2453bf38c75ee8982935739103c38a9284 tip-rev=7
   550bb31f072912453ccbb503de1d554616911e88 o default
   7c29ff2453bf38c75ee8982935739103c38a9284 o default
 #endif
@@ -405,7 +405,7 @@
 #else
   $ show_cache
   ##### .hg/cache/branch3-served
-  filtered-hash=ac5282439f301518f362f37547fcd52bcc670373 tip-node=3d808bbc94408ea19da905596d4079357a1f28be tip-rev=8
+  obsolete-hash=ac5282439f301518f362f37547fcd52bcc670373 tip-node=3d808bbc94408ea19da905596d4079357a1f28be tip-rev=8
   63ba7cd843d1e95aac1a24435befeb1909c53619 o default
   7c29ff2453bf38c75ee8982935739103c38a9284 o default
 #endif
@@ -426,7 +426,7 @@
 #else
   $ show_cache
   ##### .hg/cache/branch3-served
-  filtered-hash=ac5282439f301518f362f37547fcd52bcc670373 tip-node=3d808bbc94408ea19da905596d4079357a1f28be tip-rev=8
+  obsolete-hash=ac5282439f301518f362f37547fcd52bcc670373 tip-node=3d808bbc94408ea19da905596d4079357a1f28be tip-rev=8
   63ba7cd843d1e95aac1a24435befeb1909c53619 o default
   7c29ff2453bf38c75ee8982935739103c38a9284 o default
 #endif
@@ -516,7 +516,7 @@
 #else
   $ show_cache
   ##### .hg/cache/branch3-served
-  filtered-hash=f8006d64a10d35c011a5c5fa88be1e25c5929514 tip-node=3d808bbc94408ea19da905596d4079357a1f28be tip-rev=8
+  filtered-hash=f1456c0d675980582dda9b8edc7f13f503ce544f obsolete-hash=3e74f5349008671629e39d13d7e00d9ba94c74f7 tip-node=3d808bbc94408ea19da905596d4079357a1f28be tip-rev=8
   550bb31f072912453ccbb503de1d554616911e88 o default
   3d808bbc94408ea19da905596d4079357a1f28be o default
 #endif
@@ -537,7 +537,7 @@
 #else
   $ show_cache
   ##### .hg/cache/branch3-served
-  filtered-hash=f8006d64a10d35c011a5c5fa88be1e25c5929514 tip-node=3d808bbc94408ea19da905596d4079357a1f28be tip-rev=8
+  filtered-hash=f1456c0d675980582dda9b8edc7f13f503ce544f obsolete-hash=3e74f5349008671629e39d13d7e00d9ba94c74f7 tip-node=3d808bbc94408ea19da905596d4079357a1f28be tip-rev=8
   550bb31f072912453ccbb503de1d554616911e88 o default
   3d808bbc94408ea19da905596d4079357a1f28be o default
 #endif