dirstate-v2: Add support when Rust is not enabled
authorSimon Sapin <simon.sapin@octobus.net>
Fri, 16 Jul 2021 18:42:20 +0200
changeset 48223 b4f83c9e7905
parent 48222 7e78c72ee3ea
child 48224 6b5773f89183
dirstate-v2: Add support when Rust is not enabled This wires into `dirstatemap` the parser and serializer added in previous changesets. The memory representation is still the same, with a flat `dict` for `DirstateItem`s and another one for copy sources. Serialization always creates a new dirstate-v2 data file and does not support (when Rust is not enabled) appending to an existing one, since we don’t keep track of which tree nodes are new or modified. Instead the tree is reconstructed during serialization. Differential Revision: https://phab.mercurial-scm.org/D11520
mercurial/dirstate.py
mercurial/dirstatemap.py
mercurial/localrepo.py
tests/test-dirstate-race.t
tests/test-dirstate-race2.t
tests/test-dirstate.t
tests/test-hgignore.t
tests/test-permissions.t
tests/test-purge.t
tests/test-status.t
tests/test-symlinks.t
--- a/mercurial/dirstate.py	Thu Jul 22 17:31:37 2021 +0200
+++ b/mercurial/dirstate.py	Fri Jul 16 18:42:20 2021 +0200
@@ -39,8 +39,6 @@
 parsers = policy.importmod('parsers')
 rustmod = policy.importrust('dirstate')
 
-SUPPORTS_DIRSTATE_V2 = rustmod is not None
-
 propertycache = util.propertycache
 filecache = scmutil.filecache
 _rangemask = dirstatemap.rangemask
--- a/mercurial/dirstatemap.py	Thu Jul 22 17:31:37 2021 +0200
+++ b/mercurial/dirstatemap.py	Fri Jul 16 18:42:20 2021 +0200
@@ -332,15 +332,6 @@
       denormalized form that they appear as in the dirstate.
     """
 
-    def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
-        super(dirstatemap, self).__init__(
-            ui, opener, root, nodeconstants, use_dirstate_v2
-        )
-        if self._use_dirstate_v2:
-            msg = "Dirstate V2 not supportedi"
-            msg += "(should have detected unsupported requirement)"
-            raise error.ProgrammingError(msg)
-
     ### Core data storage and access
 
     @propertycache
@@ -406,19 +397,17 @@
             self._opener.join(self._filename)
         )
 
-        try:
-            fp = self._opendirstatefile()
-            try:
-                st = fp.read()
-            finally:
-                fp.close()
-        except IOError as err:
-            if err.errno != errno.ENOENT:
-                raise
-            return
+        if self._use_dirstate_v2:
+            if not self.docket.uuid:
+                return
+            st = self._opener.read(self.docket.data_filename())
+        else:
+            st = self._readdirstatefile()
+
         if not st:
             return
 
+        # TODO: adjust this estimate for dirstate-v2
         if util.safehasattr(parsers, b'dict_new_presized'):
             # Make an estimate of the number of files in the dirstate based on
             # its size. This trades wasting some memory for avoiding costly
@@ -440,8 +429,14 @@
         # parsing the dirstate.
         #
         # (we cannot decorate the function directly since it is in a C module)
-        parse_dirstate = util.nogc(parsers.parse_dirstate)
-        p = parse_dirstate(self._map, self.copymap, st)
+        if self._use_dirstate_v2:
+            p = self.docket.parents
+            meta = self.docket.tree_metadata
+            parse_dirstate = util.nogc(v2.parse_dirstate)
+            parse_dirstate(self._map, self.copymap, st, meta)
+        else:
+            parse_dirstate = util.nogc(parsers.parse_dirstate)
+            p = parse_dirstate(self._map, self.copymap, st)
         if not self._dirtyparents:
             self.setparents(*p)
 
@@ -450,10 +445,16 @@
         self.__getitem__ = self._map.__getitem__
         self.get = self._map.get
 
-    def write(self, _tr, st, now):
-        d = parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
-        st.write(d)
-        st.close()
+    def write(self, tr, st, now):
+        if self._use_dirstate_v2:
+            packed, meta = v2.pack_dirstate(self._map, self.copymap, now)
+            self.write_v2_no_append(tr, st, meta, packed)
+        else:
+            packed = parsers.pack_dirstate(
+                self._map, self.copymap, self.parents(), now
+            )
+            st.write(packed)
+            st.close()
         self._dirtyparents = False
 
     @propertycache
--- a/mercurial/localrepo.py	Thu Jul 22 17:31:37 2021 +0200
+++ b/mercurial/localrepo.py	Fri Jul 16 18:42:20 2021 +0200
@@ -917,9 +917,6 @@
     # Start with all requirements supported by this file.
     supported = set(localrepository._basesupported)
 
-    if dirstate.SUPPORTS_DIRSTATE_V2:
-        supported.add(requirementsmod.DIRSTATE_V2_REQUIREMENT)
-
     # Execute ``featuresetupfuncs`` entries if they belong to an extension
     # relevant to this ui instance.
     modules = {m.__name__ for n, m in extensions.extensions(ui)}
@@ -1266,6 +1263,7 @@
         requirementsmod.NODEMAP_REQUIREMENT,
         bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
         requirementsmod.SHARESAFE_REQUIREMENT,
+        requirementsmod.DIRSTATE_V2_REQUIREMENT,
     }
     _basesupported = supportedformats | {
         requirementsmod.STORE_REQUIREMENT,
@@ -3609,15 +3607,7 @@
     # experimental config: format.exp-dirstate-v2
     # Keep this logic in sync with `has_dirstate_v2()` in `tests/hghave.py`
     if ui.configbool(b'format', b'exp-dirstate-v2'):
-        if dirstate.SUPPORTS_DIRSTATE_V2:
-            requirements.add(requirementsmod.DIRSTATE_V2_REQUIREMENT)
-        else:
-            raise error.Abort(
-                _(
-                    b"dirstate v2 format requested by config "
-                    b"but not supported (requires Rust extensions)"
-                )
-            )
+        requirements.add(requirementsmod.DIRSTATE_V2_REQUIREMENT)
 
     # experimental config: format.exp-use-copies-side-data-changeset
     if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
--- a/tests/test-dirstate-race.t	Thu Jul 22 17:31:37 2021 +0200
+++ b/tests/test-dirstate-race.t	Fri Jul 16 18:42:20 2021 +0200
@@ -1,7 +1,6 @@
 #testcases dirstate-v1 dirstate-v2
 
 #if dirstate-v2
-#require rust
   $ echo '[format]' >> $HGRCPATH
   $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
 #endif
--- a/tests/test-dirstate-race2.t	Thu Jul 22 17:31:37 2021 +0200
+++ b/tests/test-dirstate-race2.t	Fri Jul 16 18:42:20 2021 +0200
@@ -1,7 +1,6 @@
 #testcases dirstate-v1 dirstate-v2
 
 #if dirstate-v2
-#require rust
   $ echo '[format]' >> $HGRCPATH
   $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
 #endif
--- a/tests/test-dirstate.t	Thu Jul 22 17:31:37 2021 +0200
+++ b/tests/test-dirstate.t	Fri Jul 16 18:42:20 2021 +0200
@@ -1,7 +1,6 @@
 #testcases dirstate-v1 dirstate-v2
 
 #if dirstate-v2
-#require rust
   $ echo '[format]' >> $HGRCPATH
   $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
 #endif
--- a/tests/test-hgignore.t	Thu Jul 22 17:31:37 2021 +0200
+++ b/tests/test-hgignore.t	Fri Jul 16 18:42:20 2021 +0200
@@ -1,7 +1,6 @@
 #testcases dirstate-v1 dirstate-v2
 
 #if dirstate-v2
-#require rust
   $ echo '[format]' >> $HGRCPATH
   $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
 #endif
@@ -397,9 +396,10 @@
 
 #endif
 
-#if dirstate-v2
+#if dirstate-v2 rust
 
 Check the hash of ignore patterns written in the dirstate
+This is an optimization that is only relevant when using the Rust extensions
 
   $ hg status > /dev/null
   $ cat .hg/testhgignore .hg/testhgignorerel .hgignore dir2/.hgignore dir1/.hgignore dir1/.hgignoretwo | $TESTDIR/f --sha1
--- a/tests/test-permissions.t	Thu Jul 22 17:31:37 2021 +0200
+++ b/tests/test-permissions.t	Fri Jul 16 18:42:20 2021 +0200
@@ -3,7 +3,6 @@
 #testcases dirstate-v1 dirstate-v2
 
 #if dirstate-v2
-#require rust
   $ echo '[format]' >> $HGRCPATH
   $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
 #endif
--- a/tests/test-purge.t	Thu Jul 22 17:31:37 2021 +0200
+++ b/tests/test-purge.t	Fri Jul 16 18:42:20 2021 +0200
@@ -1,7 +1,6 @@
 #testcases dirstate-v1 dirstate-v2
 
 #if dirstate-v2
-#require rust
   $ echo '[format]' >> $HGRCPATH
   $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
 #endif
--- a/tests/test-status.t	Thu Jul 22 17:31:37 2021 +0200
+++ b/tests/test-status.t	Fri Jul 16 18:42:20 2021 +0200
@@ -1,13 +1,6 @@
 #testcases dirstate-v1 dirstate-v2
 
-#if no-rust
-  $ hg init repo0 --config format.exp-dirstate-v2=1
-  abort: dirstate v2 format requested by config but not supported (requires Rust extensions)
-  [255]
-#endif
-
 #if dirstate-v2
-#require rust
   $ echo '[format]' >> $HGRCPATH
   $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
 #endif
@@ -743,7 +736,7 @@
 if also listing unknowns.
 The tree-based dirstate and status algorithm fix this:
 
-#if symlink no-dirstate-v1
+#if symlink no-dirstate-v1 rust
 
   $ cd ..
   $ hg init issue6335
@@ -759,11 +752,11 @@
   ? bar/a
   ? foo
 
-  $ hg status -c  # incorrect output with `dirstate-v1`
+  $ hg status -c  # incorrect output without the Rust implementation
   $ hg status -cu
   ? bar/a
   ? foo
-  $ hg status -d  # incorrect output with `dirstate-v1`
+  $ hg status -d  # incorrect output without the Rust implementation
   ! foo/a
   $ hg status -du
   ! foo/a
@@ -910,7 +903,7 @@
   I B.hs
   I ignored-folder/ctest.hs
 
-#if dirstate-v2
+#if rust dirstate-v2
 
 Check read_dir caching
 
--- a/tests/test-symlinks.t	Thu Jul 22 17:31:37 2021 +0200
+++ b/tests/test-symlinks.t	Fri Jul 16 18:42:20 2021 +0200
@@ -3,7 +3,6 @@
 #testcases dirstate-v1 dirstate-v2
 
 #if dirstate-v2
-#require rust
   $ echo '[format]' >> $HGRCPATH
   $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
 #endif