diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -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
diff --git a/mercurial/dirstatemap.py b/mercurial/dirstatemap.py
--- a/mercurial/dirstatemap.py
+++ b/mercurial/dirstatemap.py
@@ -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
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -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'):
diff --git a/tests/test-dirstate-race.t b/tests/test-dirstate-race.t
--- a/tests/test-dirstate-race.t
+++ b/tests/test-dirstate-race.t
@@ -1,7 +1,6 @@
 #testcases dirstate-v1 dirstate-v2
 
 #if dirstate-v2
-#require rust
   $ echo '[format]' >> $HGRCPATH
   $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
 #endif
diff --git a/tests/test-dirstate-race2.t b/tests/test-dirstate-race2.t
--- a/tests/test-dirstate-race2.t
+++ b/tests/test-dirstate-race2.t
@@ -1,7 +1,6 @@
 #testcases dirstate-v1 dirstate-v2
 
 #if dirstate-v2
-#require rust
   $ echo '[format]' >> $HGRCPATH
   $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
 #endif
diff --git a/tests/test-dirstate.t b/tests/test-dirstate.t
--- a/tests/test-dirstate.t
+++ b/tests/test-dirstate.t
@@ -1,7 +1,6 @@
 #testcases dirstate-v1 dirstate-v2
 
 #if dirstate-v2
-#require rust
   $ echo '[format]' >> $HGRCPATH
   $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
 #endif
diff --git a/tests/test-hgignore.t b/tests/test-hgignore.t
--- a/tests/test-hgignore.t
+++ b/tests/test-hgignore.t
@@ -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
diff --git a/tests/test-permissions.t b/tests/test-permissions.t
--- a/tests/test-permissions.t
+++ b/tests/test-permissions.t
@@ -3,7 +3,6 @@
 #testcases dirstate-v1 dirstate-v2
 
 #if dirstate-v2
-#require rust
   $ echo '[format]' >> $HGRCPATH
   $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
 #endif
diff --git a/tests/test-purge.t b/tests/test-purge.t
--- a/tests/test-purge.t
+++ b/tests/test-purge.t
@@ -1,7 +1,6 @@
 #testcases dirstate-v1 dirstate-v2
 
 #if dirstate-v2
-#require rust
   $ echo '[format]' >> $HGRCPATH
   $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
 #endif
diff --git a/tests/test-status.t b/tests/test-status.t
--- a/tests/test-status.t
+++ b/tests/test-status.t
@@ -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
 
diff --git a/tests/test-symlinks.t b/tests/test-symlinks.t
--- a/tests/test-symlinks.t
+++ b/tests/test-symlinks.t
@@ -3,7 +3,6 @@
 #testcases dirstate-v1 dirstate-v2
 
 #if dirstate-v2
-#require rust
   $ echo '[format]' >> $HGRCPATH
   $ echo 'exp-dirstate-v2=1' >> $HGRCPATH
 #endif