diff --git a/rust/hg-core/Cargo.toml b/rust/hg-core/Cargo.toml
index 8759e22f1649cd71a59d05e8f5ef8dce77ca4b44_cnVzdC9oZy1jb3JlL0NhcmdvLnRvbWw=..441024b279a635f3bf9bd0e857c8e346abb48d98_cnVzdC9oZy1jb3JlL0NhcmdvLnRvbWw= 100644
--- a/rust/hg-core/Cargo.toml
+++ b/rust/hg-core/Cargo.toml
@@ -41,9 +41,3 @@
 clap = "*"
 pretty_assertions = "0.6.1"
 tempfile = "3.1.0"
-
-[features]
-# Use a (still unoptimized) tree for the dirstate instead of the current flat
-# dirstate. This is not yet recommended for performance reasons. A future
-# version might make it the default, or make it a runtime option.
-dirstate-tree = []
diff --git a/rust/hg-core/src/dirstate.rs b/rust/hg-core/src/dirstate.rs
index 8759e22f1649cd71a59d05e8f5ef8dce77ca4b44_cnVzdC9oZy1jb3JlL3NyYy9kaXJzdGF0ZS5ycw==..441024b279a635f3bf9bd0e857c8e346abb48d98_cnVzdC9oZy1jb3JlL3NyYy9kaXJzdGF0ZS5ycw== 100644
--- a/rust/hg-core/src/dirstate.rs
+++ b/rust/hg-core/src/dirstate.rs
@@ -14,8 +14,6 @@
 
 pub mod dirs_multiset;
 pub mod dirstate_map;
-#[cfg(feature = "dirstate-tree")]
-pub mod dirstate_tree;
 pub mod parsers;
 pub mod status;
 
@@ -52,5 +50,4 @@
 /// merge.
 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
 
-#[cfg(not(feature = "dirstate-tree"))]
 pub type StateMap = FastHashMap<HgPathBuf, DirstateEntry>;
@@ -56,4 +53,3 @@
 pub type StateMap = FastHashMap<HgPathBuf, DirstateEntry>;
-#[cfg(not(feature = "dirstate-tree"))]
 pub type StateMapIter<'a> = hash_map::Iter<'a, HgPathBuf, DirstateEntry>;
 
@@ -58,9 +54,5 @@
 pub type StateMapIter<'a> = hash_map::Iter<'a, HgPathBuf, DirstateEntry>;
 
-#[cfg(feature = "dirstate-tree")]
-pub type StateMap = dirstate_tree::tree::Tree;
-#[cfg(feature = "dirstate-tree")]
-pub type StateMapIter<'a> = dirstate_tree::iter::Iter<'a>;
 pub type CopyMap = FastHashMap<HgPathBuf, HgPathBuf>;
 pub type CopyMapIter<'a> = hash_map::Iter<'a, HgPathBuf, HgPathBuf>;
 
diff --git a/rust/hg-core/src/dirstate/dirs_multiset.rs b/rust/hg-core/src/dirstate/dirs_multiset.rs
index 8759e22f1649cd71a59d05e8f5ef8dce77ca4b44_cnVzdC9oZy1jb3JlL3NyYy9kaXJzdGF0ZS9kaXJzX211bHRpc2V0LnJz..441024b279a635f3bf9bd0e857c8e346abb48d98_cnVzdC9oZy1jb3JlL3NyYy9kaXJzdGF0ZS9kaXJzX211bHRpc2V0LnJz 100644
--- a/rust/hg-core/src/dirstate/dirs_multiset.rs
+++ b/rust/hg-core/src/dirstate/dirs_multiset.rs
@@ -30,7 +30,6 @@
     /// Initializes the multiset from a dirstate.
     ///
     /// If `skip_state` is provided, skips dirstate entries with equal state.
-    #[cfg(not(feature = "dirstate-tree"))]
     pub fn from_dirstate(
         dirstate: &StateMap,
         skip_state: Option<EntryState>,
@@ -51,30 +50,6 @@
 
         Ok(multiset)
     }
-    /// Initializes the multiset from a dirstate.
-    ///
-    /// If `skip_state` is provided, skips dirstate entries with equal state.
-    #[cfg(feature = "dirstate-tree")]
-    pub fn from_dirstate(
-        dirstate: &StateMap,
-        skip_state: Option<EntryState>,
-    ) -> Result<Self, DirstateMapError> {
-        let mut multiset = DirsMultiset {
-            inner: FastHashMap::default(),
-        };
-        for (filename, DirstateEntry { state, .. }) in dirstate.iter() {
-            // This `if` is optimized out of the loop
-            if let Some(skip) = skip_state {
-                if skip != state {
-                    multiset.add_path(filename)?;
-                }
-            } else {
-                multiset.add_path(filename)?;
-            }
-        }
-
-        Ok(multiset)
-    }
 
     /// Initializes the multiset from a manifest.
     pub fn from_manifest(
diff --git a/rust/hg-core/src/dirstate/dirstate_map.rs b/rust/hg-core/src/dirstate/dirstate_map.rs
index 8759e22f1649cd71a59d05e8f5ef8dce77ca4b44_cnVzdC9oZy1jb3JlL3NyYy9kaXJzdGF0ZS9kaXJzdGF0ZV9tYXAucnM=..441024b279a635f3bf9bd0e857c8e346abb48d98_cnVzdC9oZy1jb3JlL3NyYy9kaXJzdGF0ZS9kaXJzdGF0ZV9tYXAucnM= 100644
--- a/rust/hg-core/src/dirstate/dirstate_map.rs
+++ b/rust/hg-core/src/dirstate/dirstate_map.rs
@@ -254,7 +254,6 @@
         )
     }
 
-    #[cfg(not(feature = "dirstate-tree"))]
     pub fn set_non_normal_other_parent_entries(&mut self, force: bool) {
         if !force
             && self.non_normal_set.is_some()
@@ -283,34 +282,6 @@
         self.non_normal_set = Some(non_normal);
         self.other_parent_set = Some(other_parent);
     }
-    #[cfg(feature = "dirstate-tree")]
-    pub fn set_non_normal_other_parent_entries(&mut self, force: bool) {
-        if !force
-            && self.non_normal_set.is_some()
-            && self.other_parent_set.is_some()
-        {
-            return;
-        }
-        let mut non_normal = HashSet::new();
-        let mut other_parent = HashSet::new();
-
-        for (
-            filename,
-            DirstateEntry {
-                state, size, mtime, ..
-            },
-        ) in self.state_map.iter()
-        {
-            if state != EntryState::Normal || mtime == MTIME_UNSET {
-                non_normal.insert(filename.to_owned());
-            }
-            if state == EntryState::Normal && size == SIZE_FROM_OTHER_PARENT {
-                other_parent.insert(filename.to_owned());
-            }
-        }
-        self.non_normal_set = Some(non_normal);
-        self.other_parent_set = Some(other_parent);
-    }
 
     /// Both of these setters and their uses appear to be the simplest way to
     /// emulate a Python lazy property, but it is ugly and unidiomatic.
@@ -426,7 +397,6 @@
         self.set_non_normal_other_parent_entries(true);
         Ok(packed)
     }
-    #[cfg(not(feature = "dirstate-tree"))]
     pub fn build_file_fold_map(&mut self) -> &FileFoldMap {
         if let Some(ref file_fold_map) = self.file_fold_map {
             return file_fold_map;
@@ -442,22 +412,6 @@
         self.file_fold_map = Some(new_file_fold_map);
         self.file_fold_map.as_ref().unwrap()
     }
-    #[cfg(feature = "dirstate-tree")]
-    pub fn build_file_fold_map(&mut self) -> &FileFoldMap {
-        if let Some(ref file_fold_map) = self.file_fold_map {
-            return file_fold_map;
-        }
-        let mut new_file_fold_map = FileFoldMap::default();
-
-        for (filename, DirstateEntry { state, .. }) in self.state_map.iter() {
-            if state != EntryState::Removed {
-                new_file_fold_map
-                    .insert(normalize_case(&filename), filename.to_owned());
-            }
-        }
-        self.file_fold_map = Some(new_file_fold_map);
-        self.file_fold_map.as_ref().unwrap()
-    }
 }
 
 #[cfg(test)]
diff --git a/rust/hg-core/src/dirstate/dirstate_tree.rs b/rust/hg-core/src/dirstate/dirstate_tree.rs
deleted file mode 100644
index 8759e22f1649cd71a59d05e8f5ef8dce77ca4b44_cnVzdC9oZy1jb3JlL3NyYy9kaXJzdGF0ZS9kaXJzdGF0ZV90cmVlLnJz..0000000000000000000000000000000000000000
--- a/rust/hg-core/src/dirstate/dirstate_tree.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-// dirstate_tree.rs
-//
-// Copyright 2020, Raphaël Gomès <rgomes@octobus.net>
-//
-// This software may be used and distributed according to the terms of the
-// GNU General Public License version 2 or any later version.
-
-//! Special-case radix tree that matches a filesystem hierarchy for use in the
-//! dirstate.
-//! It has not been optimized at all yet.
-
-pub mod iter;
-pub mod node;
-pub mod tree;
diff --git a/rust/hg-core/src/dirstate/dirstate_tree/iter.rs b/rust/hg-core/src/dirstate/dirstate_tree/iter.rs
deleted file mode 100644
index 8759e22f1649cd71a59d05e8f5ef8dce77ca4b44_cnVzdC9oZy1jb3JlL3NyYy9kaXJzdGF0ZS9kaXJzdGF0ZV90cmVlL2l0ZXIucnM=..0000000000000000000000000000000000000000
--- a/rust/hg-core/src/dirstate/dirstate_tree/iter.rs
+++ /dev/null
@@ -1,392 +0,0 @@
-// iter.rs
-//
-// Copyright 2020, Raphaël Gomès <rgomes@octobus.net>
-//
-// This software may be used and distributed according to the terms of the
-// GNU General Public License version 2 or any later version.
-
-use super::node::{Node, NodeKind};
-use super::tree::Tree;
-use crate::dirstate::dirstate_tree::node::Directory;
-use crate::dirstate::status::Dispatch;
-use crate::utils::hg_path::{hg_path_to_path_buf, HgPath, HgPathBuf};
-use crate::DirstateEntry;
-use std::borrow::Cow;
-use std::collections::VecDeque;
-use std::iter::{FromIterator, FusedIterator};
-use std::path::PathBuf;
-
-impl FromIterator<(HgPathBuf, DirstateEntry)> for Tree {
-    fn from_iter<T: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>(
-        iter: T,
-    ) -> Self {
-        let mut tree = Self::new();
-        for (path, entry) in iter {
-            tree.insert(path, entry);
-        }
-        tree
-    }
-}
-
-/// Iterator of all entries in the dirstate tree.
-///
-/// It has no particular ordering.
-pub struct Iter<'a> {
-    to_visit: VecDeque<(Cow<'a, [u8]>, &'a Node)>,
-}
-
-impl<'a> Iter<'a> {
-    pub fn new(node: &'a Node) -> Iter<'a> {
-        let mut to_visit = VecDeque::new();
-        to_visit.push_back((Cow::Borrowed(&b""[..]), node));
-        Self { to_visit }
-    }
-}
-
-impl<'a> Iterator for Iter<'a> {
-    type Item = (HgPathBuf, DirstateEntry);
-
-    fn next(&mut self) -> Option<Self::Item> {
-        while let Some((base_path, node)) = self.to_visit.pop_front() {
-            match &node.kind {
-                NodeKind::Directory(dir) => {
-                    add_children_to_visit(
-                        &mut self.to_visit,
-                        &base_path,
-                        &dir,
-                    );
-                    if let Some(file) = &dir.was_file {
-                        return Some((
-                            HgPathBuf::from_bytes(&base_path),
-                            file.entry,
-                        ));
-                    }
-                }
-                NodeKind::File(file) => {
-                    if let Some(dir) = &file.was_directory {
-                        add_children_to_visit(
-                            &mut self.to_visit,
-                            &base_path,
-                            &dir,
-                        );
-                    }
-                    return Some((
-                        HgPathBuf::from_bytes(&base_path),
-                        file.entry,
-                    ));
-                }
-            }
-        }
-        None
-    }
-}
-
-impl<'a> FusedIterator for Iter<'a> {}
-
-/// Iterator of all entries in the dirstate tree, with a special filesystem
-/// handling for the directories containing said entries.
-///
-/// It checks every directory on-disk to see if it has become a symlink, to
-/// prevent a potential security issue.
-/// Using this information, it may dispatch `status` information early: it
-/// returns canonical paths along with `Shortcut`s, which are either a
-/// `DirstateEntry` or a `Dispatch`, if the fate of said path has already been
-/// determined.
-///
-/// Like `Iter`, it has no particular ordering.
-pub struct FsIter<'a> {
-    root_dir: PathBuf,
-    to_visit: VecDeque<(Cow<'a, [u8]>, &'a Node)>,
-    shortcuts: VecDeque<(HgPathBuf, StatusShortcut)>,
-}
-
-impl<'a> FsIter<'a> {
-    pub fn new(node: &'a Node, root_dir: PathBuf) -> FsIter<'a> {
-        let mut to_visit = VecDeque::new();
-        to_visit.push_back((Cow::Borrowed(&b""[..]), node));
-        Self {
-            root_dir,
-            to_visit,
-            shortcuts: Default::default(),
-        }
-    }
-
-    /// Mercurial tracks symlinks but *not* what they point to.
-    /// If a directory is moved and symlinked:
-    ///
-    /// ```bash
-    /// $ mkdir foo
-    /// $ touch foo/a
-    /// $ # commit...
-    /// $ mv foo bar
-    /// $ ln -s bar foo
-    /// ```
-    /// We need to dispatch the new symlink as `Unknown` and all the
-    /// descendents of the directory it replace as `Deleted`.
-    fn dispatch_symlinked_directory(
-        &mut self,
-        path: impl AsRef<HgPath>,
-        node: &Node,
-    ) {
-        let path = path.as_ref();
-        self.shortcuts.push_back((
-            path.to_owned(),
-            StatusShortcut::Dispatch(Dispatch::Unknown),
-        ));
-        for (file, _) in node.iter() {
-            self.shortcuts.push_back((
-                path.join(&file),
-                StatusShortcut::Dispatch(Dispatch::Deleted),
-            ));
-        }
-    }
-
-    /// Returns `true` if the canonical `path` of a directory corresponds to a
-    /// symlink on disk. It means it was moved and symlinked after the last
-    /// dirstate update.
-    ///
-    /// # Special cases
-    ///
-    /// Returns `false` for the repository root.
-    /// Returns `false` on io error, error handling is outside of the iterator.
-    fn directory_became_symlink(&mut self, path: &HgPath) -> bool {
-        if path.is_empty() {
-            return false;
-        }
-        let filename_as_path = match hg_path_to_path_buf(&path) {
-            Ok(p) => p,
-            _ => return false,
-        };
-        let meta = self.root_dir.join(filename_as_path).symlink_metadata();
-        match meta {
-            Ok(ref m) if m.file_type().is_symlink() => true,
-            _ => false,
-        }
-    }
-}
-
-/// Returned by `FsIter`, since the `Dispatch` of any given entry may already
-/// be determined during the iteration. This is necessary for performance
-/// reasons, since hierarchical information is needed to `Dispatch` an entire
-/// subtree efficiently.
-#[derive(Debug, Copy, Clone)]
-pub enum StatusShortcut {
-    /// A entry in the dirstate for further inspection
-    Entry(DirstateEntry),
-    /// The result of the status of the corresponding file
-    Dispatch(Dispatch),
-}
-
-impl<'a> Iterator for FsIter<'a> {
-    type Item = (HgPathBuf, StatusShortcut);
-
-    fn next(&mut self) -> Option<Self::Item> {
-        // If any paths have already been `Dispatch`-ed, return them
-        if let Some(res) = self.shortcuts.pop_front() {
-            return Some(res);
-        }
-
-        while let Some((base_path, node)) = self.to_visit.pop_front() {
-            match &node.kind {
-                NodeKind::Directory(dir) => {
-                    let canonical_path = HgPath::new(&base_path);
-                    if self.directory_became_symlink(canonical_path) {
-                        // Potential security issue, don't do a normal
-                        // traversal, force the results.
-                        self.dispatch_symlinked_directory(
-                            canonical_path,
-                            &node,
-                        );
-                        continue;
-                    }
-                    add_children_to_visit(
-                        &mut self.to_visit,
-                        &base_path,
-                        &dir,
-                    );
-                    if let Some(file) = &dir.was_file {
-                        return Some((
-                            HgPathBuf::from_bytes(&base_path),
-                            StatusShortcut::Entry(file.entry),
-                        ));
-                    }
-                }
-                NodeKind::File(file) => {
-                    if let Some(dir) = &file.was_directory {
-                        add_children_to_visit(
-                            &mut self.to_visit,
-                            &base_path,
-                            &dir,
-                        );
-                    }
-                    return Some((
-                        HgPathBuf::from_bytes(&base_path),
-                        StatusShortcut::Entry(file.entry),
-                    ));
-                }
-            }
-        }
-
-        None
-    }
-}
-
-impl<'a> FusedIterator for FsIter<'a> {}
-
-fn join_path<'a, 'b>(path: &'a [u8], other: &'b [u8]) -> Cow<'b, [u8]> {
-    if path.is_empty() {
-        other.into()
-    } else {
-        [path, &b"/"[..], other].concat().into()
-    }
-}
-
-/// Adds all children of a given directory `dir` to the visit queue `to_visit`
-/// prefixed by a `base_path`.
-fn add_children_to_visit<'a>(
-    to_visit: &mut VecDeque<(Cow<'a, [u8]>, &'a Node)>,
-    base_path: &[u8],
-    dir: &'a Directory,
-) {
-    to_visit.extend(dir.children.iter().map(|(path, child)| {
-        let full_path = join_path(&base_path, &path);
-        (full_path, child)
-    }));
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::utils::hg_path::HgPath;
-    use crate::{EntryState, FastHashMap};
-    use std::collections::HashSet;
-
-    #[test]
-    fn test_iteration() {
-        let mut tree = Tree::new();
-
-        assert_eq!(
-            tree.insert(
-                HgPathBuf::from_bytes(b"foo/bar"),
-                DirstateEntry {
-                    state: EntryState::Merged,
-                    mode: 41,
-                    mtime: 42,
-                    size: 43,
-                }
-            ),
-            None
-        );
-
-        assert_eq!(
-            tree.insert(
-                HgPathBuf::from_bytes(b"foo2"),
-                DirstateEntry {
-                    state: EntryState::Merged,
-                    mode: 40,
-                    mtime: 41,
-                    size: 42,
-                }
-            ),
-            None
-        );
-
-        assert_eq!(
-            tree.insert(
-                HgPathBuf::from_bytes(b"foo/baz"),
-                DirstateEntry {
-                    state: EntryState::Normal,
-                    mode: 0,
-                    mtime: 0,
-                    size: 0,
-                }
-            ),
-            None
-        );
-
-        assert_eq!(
-            tree.insert(
-                HgPathBuf::from_bytes(b"foo/bap/nested"),
-                DirstateEntry {
-                    state: EntryState::Normal,
-                    mode: 0,
-                    mtime: 0,
-                    size: 0,
-                }
-            ),
-            None
-        );
-
-        assert_eq!(tree.len(), 4);
-
-        let results: HashSet<_> =
-            tree.iter().map(|(c, _)| c.to_owned()).collect();
-        dbg!(&results);
-        assert!(results.contains(HgPath::new(b"foo2")));
-        assert!(results.contains(HgPath::new(b"foo/bar")));
-        assert!(results.contains(HgPath::new(b"foo/baz")));
-        assert!(results.contains(HgPath::new(b"foo/bap/nested")));
-
-        let mut iter = tree.iter();
-        assert!(iter.next().is_some());
-        assert!(iter.next().is_some());
-        assert!(iter.next().is_some());
-        assert!(iter.next().is_some());
-        assert_eq!(None, iter.next());
-        assert_eq!(None, iter.next());
-        drop(iter);
-
-        assert_eq!(
-            tree.insert(
-                HgPathBuf::from_bytes(b"foo/bap/nested/a"),
-                DirstateEntry {
-                    state: EntryState::Normal,
-                    mode: 0,
-                    mtime: 0,
-                    size: 0,
-                }
-            ),
-            None
-        );
-
-        let results: FastHashMap<_, _> = tree.iter().collect();
-        assert!(results.contains_key(HgPath::new(b"foo2")));
-        assert!(results.contains_key(HgPath::new(b"foo/bar")));
-        assert!(results.contains_key(HgPath::new(b"foo/baz")));
-        // Is a dir but `was_file`, so it's listed as a removed file
-        assert!(results.contains_key(HgPath::new(b"foo/bap/nested")));
-        assert!(results.contains_key(HgPath::new(b"foo/bap/nested/a")));
-
-        // insert removed file (now directory) after nested file
-        assert_eq!(
-            tree.insert(
-                HgPathBuf::from_bytes(b"a/a"),
-                DirstateEntry {
-                    state: EntryState::Normal,
-                    mode: 0,
-                    mtime: 0,
-                    size: 0,
-                }
-            ),
-            None
-        );
-
-        // `insert` returns `None` for a directory
-        assert_eq!(
-            tree.insert(
-                HgPathBuf::from_bytes(b"a"),
-                DirstateEntry {
-                    state: EntryState::Removed,
-                    mode: 0,
-                    mtime: 0,
-                    size: 0,
-                }
-            ),
-            None
-        );
-
-        let results: FastHashMap<_, _> = tree.iter().collect();
-        assert!(results.contains_key(HgPath::new(b"a")));
-        assert!(results.contains_key(HgPath::new(b"a/a")));
-    }
-}
diff --git a/rust/hg-core/src/dirstate/dirstate_tree/node.rs b/rust/hg-core/src/dirstate/dirstate_tree/node.rs
deleted file mode 100644
index 8759e22f1649cd71a59d05e8f5ef8dce77ca4b44_cnVzdC9oZy1jb3JlL3NyYy9kaXJzdGF0ZS9kaXJzdGF0ZV90cmVlL25vZGUucnM=..0000000000000000000000000000000000000000
--- a/rust/hg-core/src/dirstate/dirstate_tree/node.rs
+++ /dev/null
@@ -1,398 +0,0 @@
-// node.rs
-//
-// Copyright 2020, Raphaël Gomès <rgomes@octobus.net>
-//
-// This software may be used and distributed according to the terms of the
-// GNU General Public License version 2 or any later version.
-
-use super::iter::Iter;
-use crate::utils::hg_path::HgPathBuf;
-use crate::{DirstateEntry, EntryState, FastHashMap};
-
-/// Represents a filesystem directory in the dirstate tree
-#[derive(Debug, Default, Clone, PartialEq)]
-pub struct Directory {
-    /// Contains the old file information if it existed between changesets.
-    /// Happens if a file `foo` is marked as removed, removed from the
-    /// filesystem then a directory `foo` is created and at least one of its
-    /// descendents is added to Mercurial.
-    pub(super) was_file: Option<Box<File>>,
-    pub(super) children: FastHashMap<Vec<u8>, Node>,
-}
-
-/// Represents a filesystem file (or symlink) in the dirstate tree
-#[derive(Debug, Clone, PartialEq)]
-pub struct File {
-    /// Contains the old structure if it existed between changesets.
-    /// Happens all descendents of `foo` marked as removed and removed from
-    /// the filesystem, then a file `foo` is created and added to Mercurial.
-    pub(super) was_directory: Option<Box<Directory>>,
-    pub(super) entry: DirstateEntry,
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum NodeKind {
-    Directory(Directory),
-    File(File),
-}
-
-#[derive(Debug, Default, Clone, PartialEq)]
-pub struct Node {
-    pub kind: NodeKind,
-}
-
-impl Default for NodeKind {
-    fn default() -> Self {
-        NodeKind::Directory(Default::default())
-    }
-}
-
-impl Node {
-    pub fn insert(
-        &mut self,
-        path: &[u8],
-        new_entry: DirstateEntry,
-    ) -> InsertResult {
-        let mut split = path.splitn(2, |&c| c == b'/');
-        let head = split.next().unwrap_or(b"");
-        let tail = split.next().unwrap_or(b"");
-
-        // Are we're modifying the current file ? Is the the end of the path ?
-        let is_current_file = tail.is_empty() && head.is_empty();
-
-        // Potentially Replace the current file with a directory if it's marked
-        // as `Removed`
-        if !is_current_file {
-            if let NodeKind::File(file) = &mut self.kind {
-                if file.entry.state == EntryState::Removed {
-                    self.kind = NodeKind::Directory(Directory {
-                        was_file: Some(Box::from(file.clone())),
-                        children: Default::default(),
-                    })
-                }
-            }
-        }
-        match &mut self.kind {
-            NodeKind::Directory(directory) => {
-                Node::insert_in_directory(directory, new_entry, head, tail)
-            }
-            NodeKind::File(file) => {
-                if is_current_file {
-                    let new = Self {
-                        kind: NodeKind::File(File {
-                            entry: new_entry,
-                            ..file.clone()
-                        }),
-                    };
-                    InsertResult {
-                        did_insert: false,
-                        old_entry: Some(std::mem::replace(self, new)),
-                    }
-                } else {
-                    match file.entry.state {
-                        EntryState::Removed => {
-                            unreachable!("Removed file turning into a directory was dealt with earlier")
-                        }
-                        _ => {
-                            Node::insert_in_file(
-                                file, new_entry, head, tail,
-                            )
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    /// The current file still exists and is not marked as `Removed`.
-    /// Insert the entry in its `was_directory`.
-    fn insert_in_file(
-        file: &mut File,
-        new_entry: DirstateEntry,
-        head: &[u8],
-        tail: &[u8],
-    ) -> InsertResult {
-        if let Some(d) = &mut file.was_directory {
-            Node::insert_in_directory(d, new_entry, head, tail)
-        } else {
-            let mut dir = Directory {
-                was_file: None,
-                children: FastHashMap::default(),
-            };
-            let res =
-                Node::insert_in_directory(&mut dir, new_entry, head, tail);
-            file.was_directory = Some(Box::new(dir));
-            res
-        }
-    }
-
-    /// Insert an entry in the subtree of `directory`
-    fn insert_in_directory(
-        directory: &mut Directory,
-        new_entry: DirstateEntry,
-        head: &[u8],
-        tail: &[u8],
-    ) -> InsertResult {
-        let mut res = InsertResult::default();
-
-        if let Some(node) = directory.children.get_mut(head) {
-            // Node exists
-            match &mut node.kind {
-                NodeKind::Directory(subdir) => {
-                    if tail.is_empty() {
-                        let becomes_file = Self {
-                            kind: NodeKind::File(File {
-                                was_directory: Some(Box::from(subdir.clone())),
-                                entry: new_entry,
-                            }),
-                        };
-                        let old_entry = directory
-                            .children
-                            .insert(head.to_owned(), becomes_file);
-                        return InsertResult {
-                            did_insert: true,
-                            old_entry,
-                        };
-                    } else {
-                        res = node.insert(tail, new_entry);
-                    }
-                }
-                NodeKind::File(_) => {
-                    res = node.insert(tail, new_entry);
-                }
-            }
-        } else if tail.is_empty() {
-            // File does not already exist
-            directory.children.insert(
-                head.to_owned(),
-                Self {
-                    kind: NodeKind::File(File {
-                        was_directory: None,
-                        entry: new_entry,
-                    }),
-                },
-            );
-            res.did_insert = true;
-        } else {
-            // Directory does not already exist
-            let mut nested = Self {
-                kind: NodeKind::Directory(Directory {
-                    was_file: None,
-                    children: Default::default(),
-                }),
-            };
-            res = nested.insert(tail, new_entry);
-            directory.children.insert(head.to_owned(), nested);
-        }
-        res
-    }
-
-    /// Removes an entry from the tree, returns a `RemoveResult`.
-    pub fn remove(&mut self, path: &[u8]) -> RemoveResult {
-        let empty_result = RemoveResult::default();
-        if path.is_empty() {
-            return empty_result;
-        }
-        let mut split = path.splitn(2, |&c| c == b'/');
-        let head = split.next();
-        let tail = split.next().unwrap_or(b"");
-
-        let head = match head {
-            None => {
-                return empty_result;
-            }
-            Some(h) => h,
-        };
-        if head == path {
-            match &mut self.kind {
-                NodeKind::Directory(d) => {
-                    return Node::remove_from_directory(head, d);
-                }
-                NodeKind::File(f) => {
-                    if let Some(d) = &mut f.was_directory {
-                        let RemoveResult { old_entry, .. } =
-                            Node::remove_from_directory(head, d);
-                        return RemoveResult {
-                            cleanup: false,
-                            old_entry,
-                        };
-                    }
-                }
-            }
-            empty_result
-        } else {
-            // Look into the dirs
-            match &mut self.kind {
-                NodeKind::Directory(d) => {
-                    if let Some(child) = d.children.get_mut(head) {
-                        let mut res = child.remove(tail);
-                        if res.cleanup {
-                            d.children.remove(head);
-                        }
-                        res.cleanup =
-                            d.children.is_empty() && d.was_file.is_none();
-                        res
-                    } else {
-                        empty_result
-                    }
-                }
-                NodeKind::File(f) => {
-                    if let Some(d) = &mut f.was_directory {
-                        if let Some(child) = d.children.get_mut(head) {
-                            let RemoveResult { cleanup, old_entry } =
-                                child.remove(tail);
-                            if cleanup {
-                                d.children.remove(head);
-                            }
-                            if d.children.is_empty() && d.was_file.is_none() {
-                                f.was_directory = None;
-                            }
-
-                            return RemoveResult {
-                                cleanup: false,
-                                old_entry,
-                            };
-                        }
-                    }
-                    empty_result
-                }
-            }
-        }
-    }
-
-    fn remove_from_directory(head: &[u8], d: &mut Directory) -> RemoveResult {
-        if let Some(node) = d.children.get_mut(head) {
-            return match &mut node.kind {
-                NodeKind::Directory(d) => {
-                    if let Some(f) = &mut d.was_file {
-                        let entry = f.entry;
-                        d.was_file = None;
-                        RemoveResult {
-                            cleanup: false,
-                            old_entry: Some(entry),
-                        }
-                    } else {
-                        RemoveResult::default()
-                    }
-                }
-                NodeKind::File(f) => {
-                    let entry = f.entry;
-                    let mut cleanup = false;
-                    match &f.was_directory {
-                        None => {
-                            if d.children.len() == 1 {
-                                cleanup = true;
-                            }
-                            d.children.remove(head);
-                        }
-                        Some(dir) => {
-                            node.kind = NodeKind::Directory(*dir.clone());
-                        }
-                    }
-
-                    RemoveResult {
-                        cleanup,
-                        old_entry: Some(entry),
-                    }
-                }
-            };
-        }
-        RemoveResult::default()
-    }
-
-    pub fn get(&self, path: &[u8]) -> Option<&Node> {
-        if path.is_empty() {
-            return Some(&self);
-        }
-        let mut split = path.splitn(2, |&c| c == b'/');
-        let head = split.next();
-        let tail = split.next().unwrap_or(b"");
-
-        let head = match head {
-            None => {
-                return Some(&self);
-            }
-            Some(h) => h,
-        };
-        match &self.kind {
-            NodeKind::Directory(d) => {
-                if let Some(child) = d.children.get(head) {
-                    return child.get(tail);
-                }
-            }
-            NodeKind::File(f) => {
-                if let Some(d) = &f.was_directory {
-                    if let Some(child) = d.children.get(head) {
-                        return child.get(tail);
-                    }
-                }
-            }
-        }
-
-        None
-    }
-
-    pub fn get_mut(&mut self, path: &[u8]) -> Option<&mut NodeKind> {
-        if path.is_empty() {
-            return Some(&mut self.kind);
-        }
-        let mut split = path.splitn(2, |&c| c == b'/');
-        let head = split.next();
-        let tail = split.next().unwrap_or(b"");
-
-        let head = match head {
-            None => {
-                return Some(&mut self.kind);
-            }
-            Some(h) => h,
-        };
-        match &mut self.kind {
-            NodeKind::Directory(d) => {
-                if let Some(child) = d.children.get_mut(head) {
-                    return child.get_mut(tail);
-                }
-            }
-            NodeKind::File(f) => {
-                if let Some(d) = &mut f.was_directory {
-                    if let Some(child) = d.children.get_mut(head) {
-                        return child.get_mut(tail);
-                    }
-                }
-            }
-        }
-
-        None
-    }
-
-    pub fn iter(&self) -> Iter {
-        Iter::new(self)
-    }
-}
-
-/// Information returned to the caller of an `insert` operation for integrity.
-#[derive(Debug, Default)]
-pub struct InsertResult {
-    /// Whether the insertion resulted in an actual insertion and not an
-    /// update
-    pub(super) did_insert: bool,
-    /// The entry that was replaced, if it exists
-    pub(super) old_entry: Option<Node>,
-}
-
-/// Information returned to the caller of a `remove` operation integrity.
-#[derive(Debug, Default)]
-pub struct RemoveResult {
-    /// If the caller needs to remove the current node
-    pub(super) cleanup: bool,
-    /// The entry that was replaced, if it exists
-    pub(super) old_entry: Option<DirstateEntry>,
-}
-
-impl<'a> IntoIterator for &'a Node {
-    type Item = (HgPathBuf, DirstateEntry);
-    type IntoIter = Iter<'a>;
-
-    fn into_iter(self) -> Self::IntoIter {
-        self.iter()
-    }
-}
diff --git a/rust/hg-core/src/dirstate/dirstate_tree/tree.rs b/rust/hg-core/src/dirstate/dirstate_tree/tree.rs
deleted file mode 100644
index 8759e22f1649cd71a59d05e8f5ef8dce77ca4b44_cnVzdC9oZy1jb3JlL3NyYy9kaXJzdGF0ZS9kaXJzdGF0ZV90cmVlL3RyZWUucnM=..0000000000000000000000000000000000000000
--- a/rust/hg-core/src/dirstate/dirstate_tree/tree.rs
+++ /dev/null
@@ -1,682 +0,0 @@
-// tree.rs
-//
-// Copyright 2020, Raphaël Gomès <rgomes@octobus.net>
-//
-// This software may be used and distributed according to the terms of the
-// GNU General Public License version 2 or any later version.
-
-use super::iter::Iter;
-use super::node::{Directory, Node, NodeKind};
-use crate::dirstate::dirstate_tree::iter::FsIter;
-use crate::dirstate::dirstate_tree::node::{InsertResult, RemoveResult};
-use crate::utils::hg_path::{HgPath, HgPathBuf};
-use crate::DirstateEntry;
-use std::path::PathBuf;
-
-/// A specialized tree to represent the Mercurial dirstate.
-///
-/// # Advantages over a flat structure
-///
-/// The dirstate is inherently hierarchical, since it's a representation of the
-/// file structure of the project. The current dirstate format is flat, and
-/// while that affords us potentially great (unordered) iteration speeds, the
-/// need to retrieve a given path is great enough that you need some kind of
-/// hashmap or tree in a lot of cases anyway.
-///
-/// Going with a tree allows us to be smarter:
-///   - Skipping an ignored directory means we don't visit its entire subtree
-///   - Security auditing does not need to reconstruct paths backwards to check
-///     for symlinked directories, this can be done during the iteration in a
-///     very efficient fashion
-///   - We don't need to build the directory information in another struct,
-///     simplifying the code a lot, reducing the memory footprint and
-///     potentially going faster depending on the implementation.
-///   - We can use it to store a (platform-dependent) caching mechanism [1]
-///   - And probably other types of optimizations.
-///
-/// Only the first two items in this list are implemented as of this commit.
-///
-/// [1]: https://www.mercurial-scm.org/wiki/DirsCachePlan
-///
-///
-/// # Structure
-///
-/// It's a prefix (radix) tree with no fixed arity, with a granularity of a
-/// folder, allowing it to mimic a filesystem hierarchy:
-///
-/// ```text
-/// foo/bar
-/// foo/baz
-/// test
-/// ```
-/// Will be represented (simplified) by:
-///
-/// ```text
-/// Directory(root):
-///   - File("test")
-///   - Directory("foo"):
-///     - File("bar")
-///     - File("baz")
-/// ```
-///
-/// Moreover, it is special-cased for storing the dirstate and as such handles
-/// cases that a simple `HashMap` would handle, but while preserving the
-/// hierarchy.
-/// For example:
-///
-/// ```shell
-/// $ touch foo
-/// $ hg add foo
-/// $ hg commit -m "foo"
-/// $ hg remove foo
-/// $ rm foo
-/// $ mkdir foo
-/// $ touch foo/a
-/// $ hg add foo/a
-/// $ hg status
-///   R foo
-///   A foo/a
-/// ```
-/// To represent this in a tree, one needs to keep track of whether any given
-/// file was a directory and whether any given directory was a file at the last
-/// dirstate update. This tree stores that information, but only in the right
-/// circumstances by respecting the high-level rules that prevent nonsensical
-/// structures to exist:
-///     - a file can only be added as a child of another file if the latter is
-///       marked as `Removed`
-///     - a file cannot replace a folder unless all its descendents are removed
-///
-/// This second rule is not checked by the tree for performance reasons, and
-/// because high-level logic already prevents that state from happening.
-///
-/// # Ordering
-///
-/// It makes no guarantee of ordering for now.
-#[derive(Debug, Default, Clone, PartialEq)]
-pub struct Tree {
-    pub root: Node,
-    files_count: usize,
-}
-
-impl Tree {
-    pub fn new() -> Self {
-        Self {
-            root: Node {
-                kind: NodeKind::Directory(Directory {
-                    was_file: None,
-                    children: Default::default(),
-                }),
-            },
-            files_count: 0,
-        }
-    }
-
-    /// How many files (not directories) are stored in the tree, including ones
-    /// marked as `Removed`.
-    pub fn len(&self) -> usize {
-        self.files_count
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.len() == 0
-    }
-
-    /// Inserts a file in the tree and returns the previous entry if any.
-    pub fn insert(
-        &mut self,
-        path: impl AsRef<HgPath>,
-        kind: DirstateEntry,
-    ) -> Option<DirstateEntry> {
-        let old = self.insert_node(path, kind);
-        match old?.kind {
-            NodeKind::Directory(_) => None,
-            NodeKind::File(f) => Some(f.entry),
-        }
-    }
-
-    /// Low-level insertion method that returns the previous node (directories
-    /// included).
-    fn insert_node(
-        &mut self,
-        path: impl AsRef<HgPath>,
-        kind: DirstateEntry,
-    ) -> Option<Node> {
-        let InsertResult {
-            did_insert,
-            old_entry,
-        } = self.root.insert(path.as_ref().as_bytes(), kind);
-        self.files_count += if did_insert { 1 } else { 0 };
-        old_entry
-    }
-
-    /// Returns a reference to a node if it exists.
-    pub fn get_node(&self, path: impl AsRef<HgPath>) -> Option<&Node> {
-        self.root.get(path.as_ref().as_bytes())
-    }
-
-    /// Returns a reference to the entry corresponding to `path` if it exists.
-    pub fn get(&self, path: impl AsRef<HgPath>) -> Option<&DirstateEntry> {
-        if let Some(node) = self.get_node(&path) {
-            return match &node.kind {
-                NodeKind::Directory(d) => {
-                    d.was_file.as_ref().map(|f| &f.entry)
-                }
-                NodeKind::File(f) => Some(&f.entry),
-            };
-        }
-        None
-    }
-
-    /// Returns `true` if an entry is found for the given `path`.
-    pub fn contains_key(&self, path: impl AsRef<HgPath>) -> bool {
-        self.get(path).is_some()
-    }
-
-    /// Returns a mutable reference to the entry corresponding to `path` if it
-    /// exists.
-    pub fn get_mut(
-        &mut self,
-        path: impl AsRef<HgPath>,
-    ) -> Option<&mut DirstateEntry> {
-        if let Some(kind) = self.root.get_mut(path.as_ref().as_bytes()) {
-            return match kind {
-                NodeKind::Directory(d) => {
-                    d.was_file.as_mut().map(|f| &mut f.entry)
-                }
-                NodeKind::File(f) => Some(&mut f.entry),
-            };
-        }
-        None
-    }
-
-    /// Returns an iterator over the paths and corresponding entries in the
-    /// tree.
-    pub fn iter(&self) -> Iter {
-        Iter::new(&self.root)
-    }
-
-    /// Returns an iterator of all entries in the tree, with a special
-    /// filesystem handling for the directories containing said entries. See
-    /// the documentation of `FsIter` for more.
-    pub fn fs_iter(&self, root_dir: PathBuf) -> FsIter {
-        FsIter::new(&self.root, root_dir)
-    }
-
-    /// Remove the entry at `path` and returns it, if it exists.
-    pub fn remove(
-        &mut self,
-        path: impl AsRef<HgPath>,
-    ) -> Option<DirstateEntry> {
-        let RemoveResult { old_entry, .. } =
-            self.root.remove(path.as_ref().as_bytes());
-        self.files_count = self
-            .files_count
-            .checked_sub(if old_entry.is_some() { 1 } else { 0 })
-            .expect("removed too many files");
-        old_entry
-    }
-}
-
-impl<P: AsRef<HgPath>> Extend<(P, DirstateEntry)> for Tree {
-    fn extend<T: IntoIterator<Item = (P, DirstateEntry)>>(&mut self, iter: T) {
-        for (path, entry) in iter {
-            self.insert(path, entry);
-        }
-    }
-}
-
-impl<'a> IntoIterator for &'a Tree {
-    type Item = (HgPathBuf, DirstateEntry);
-    type IntoIter = Iter<'a>;
-
-    fn into_iter(self) -> Self::IntoIter {
-        self.iter()
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::dirstate::dirstate_tree::node::File;
-    use crate::{EntryState, FastHashMap};
-    use pretty_assertions::assert_eq;
-
-    impl Node {
-        /// Shortcut for getting children of a node in tests.
-        fn children(&self) -> Option<&FastHashMap<Vec<u8>, Node>> {
-            match &self.kind {
-                NodeKind::Directory(d) => Some(&d.children),
-                NodeKind::File(_) => None,
-            }
-        }
-    }
-
-    #[test]
-    fn test_dirstate_tree() {
-        let mut tree = Tree::new();
-
-        assert_eq!(
-            tree.insert_node(
-                HgPath::new(b"we/p"),
-                DirstateEntry {
-                    state: EntryState::Normal,
-                    mode: 0,
-                    mtime: 0,
-                    size: 0
-                }
-            ),
-            None
-        );
-        dbg!(&tree);
-        assert!(tree.get_node(HgPath::new(b"we")).is_some());
-        let entry = DirstateEntry {
-            state: EntryState::Merged,
-            mode: 41,
-            mtime: 42,
-            size: 43,
-        };
-        assert_eq!(tree.insert_node(HgPath::new(b"foo/bar"), entry), None);
-        assert_eq!(
-            tree.get_node(HgPath::new(b"foo/bar")),
-            Some(&Node {
-                kind: NodeKind::File(File {
-                    was_directory: None,
-                    entry
-                })
-            })
-        );
-        // We didn't override the first entry we made
-        assert!(tree.get_node(HgPath::new(b"we")).is_some(),);
-        // Inserting the same key again
-        assert_eq!(
-            tree.insert_node(HgPath::new(b"foo/bar"), entry),
-            Some(Node {
-                kind: NodeKind::File(File {
-                    was_directory: None,
-                    entry
-                }),
-            })
-        );
-        // Inserting the two levels deep
-        assert_eq!(tree.insert_node(HgPath::new(b"foo/bar/baz"), entry), None);
-        // Getting a file "inside a file" should return `None`
-        assert_eq!(tree.get_node(HgPath::new(b"foo/bar/baz/bap"),), None);
-
-        assert_eq!(
-            tree.insert_node(HgPath::new(b"wasdir/subfile"), entry),
-            None,
-        );
-        let removed_entry = DirstateEntry {
-            state: EntryState::Removed,
-            mode: 0,
-            mtime: 0,
-            size: 0,
-        };
-        assert!(tree
-            .insert_node(HgPath::new(b"wasdir"), removed_entry)
-            .is_some());
-
-        assert_eq!(
-            tree.get_node(HgPath::new(b"wasdir")),
-            Some(&Node {
-                kind: NodeKind::File(File {
-                    was_directory: Some(Box::new(Directory {
-                        was_file: None,
-                        children: [(
-                            b"subfile".to_vec(),
-                            Node {
-                                kind: NodeKind::File(File {
-                                    was_directory: None,
-                                    entry,
-                                })
-                            }
-                        )]
-                        .to_vec()
-                        .into_iter()
-                        .collect()
-                    })),
-                    entry: removed_entry
-                })
-            })
-        );
-
-        assert!(tree.get(HgPath::new(b"wasdir/subfile")).is_some())
-    }
-
-    #[test]
-    fn test_insert_removed() {
-        let mut tree = Tree::new();
-        let entry = DirstateEntry {
-            state: EntryState::Merged,
-            mode: 1,
-            mtime: 2,
-            size: 3,
-        };
-        let removed_entry = DirstateEntry {
-            state: EntryState::Removed,
-            mode: 10,
-            mtime: 20,
-            size: 30,
-        };
-        assert_eq!(tree.insert_node(HgPath::new(b"foo"), entry), None);
-        assert_eq!(
-            tree.insert_node(HgPath::new(b"foo/a"), removed_entry),
-            None
-        );
-        // The insert should not turn `foo` into a directory as `foo` is not
-        // `Removed`.
-        match tree.get_node(HgPath::new(b"foo")).unwrap().kind {
-            NodeKind::Directory(_) => panic!("should be a file"),
-            NodeKind::File(_) => {}
-        }
-
-        let mut tree = Tree::new();
-        let entry = DirstateEntry {
-            state: EntryState::Merged,
-            mode: 1,
-            mtime: 2,
-            size: 3,
-        };
-        let removed_entry = DirstateEntry {
-            state: EntryState::Removed,
-            mode: 10,
-            mtime: 20,
-            size: 30,
-        };
-        // The insert *should* turn `foo` into a directory as it is `Removed`.
-        assert_eq!(tree.insert_node(HgPath::new(b"foo"), removed_entry), None);
-        assert_eq!(tree.insert_node(HgPath::new(b"foo/a"), entry), None);
-        match tree.get_node(HgPath::new(b"foo")).unwrap().kind {
-            NodeKind::Directory(_) => {}
-            NodeKind::File(_) => panic!("should be a directory"),
-        }
-    }
-
-    #[test]
-    fn test_get() {
-        let mut tree = Tree::new();
-        let entry = DirstateEntry {
-            state: EntryState::Merged,
-            mode: 1,
-            mtime: 2,
-            size: 3,
-        };
-        assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None);
-        assert_eq!(tree.files_count, 1);
-        assert_eq!(tree.get(HgPath::new(b"a/b/c")), Some(&entry));
-        assert_eq!(tree.get(HgPath::new(b"a/b")), None);
-        assert_eq!(tree.get(HgPath::new(b"a")), None);
-        assert_eq!(tree.get(HgPath::new(b"a/b/c/d")), None);
-        let entry2 = DirstateEntry {
-            state: EntryState::Removed,
-            mode: 0,
-            mtime: 5,
-            size: 1,
-        };
-        // was_directory
-        assert_eq!(tree.insert(HgPath::new(b"a/b"), entry2), None);
-        assert_eq!(tree.files_count, 2);
-        assert_eq!(tree.get(HgPath::new(b"a/b")), Some(&entry2));
-        assert_eq!(tree.get(HgPath::new(b"a/b/c")), Some(&entry));
-
-        let mut tree = Tree::new();
-
-        // was_file
-        assert_eq!(tree.insert_node(HgPath::new(b"a"), entry), None);
-        assert_eq!(tree.files_count, 1);
-        assert_eq!(tree.insert_node(HgPath::new(b"a/b"), entry2), None);
-        assert_eq!(tree.files_count, 2);
-        assert_eq!(tree.get(HgPath::new(b"a/b")), Some(&entry2));
-    }
-
-    #[test]
-    fn test_get_mut() {
-        let mut tree = Tree::new();
-        let mut entry = DirstateEntry {
-            state: EntryState::Merged,
-            mode: 1,
-            mtime: 2,
-            size: 3,
-        };
-        assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None);
-        assert_eq!(tree.files_count, 1);
-        assert_eq!(tree.get_mut(HgPath::new(b"a/b/c")), Some(&mut entry));
-        assert_eq!(tree.get_mut(HgPath::new(b"a/b")), None);
-        assert_eq!(tree.get_mut(HgPath::new(b"a")), None);
-        assert_eq!(tree.get_mut(HgPath::new(b"a/b/c/d")), None);
-        let mut entry2 = DirstateEntry {
-            state: EntryState::Removed,
-            mode: 0,
-            mtime: 5,
-            size: 1,
-        };
-        // was_directory
-        assert_eq!(tree.insert(HgPath::new(b"a/b"), entry2), None);
-        assert_eq!(tree.files_count, 2);
-        assert_eq!(tree.get_mut(HgPath::new(b"a/b")), Some(&mut entry2));
-        assert_eq!(tree.get_mut(HgPath::new(b"a/b/c")), Some(&mut entry));
-
-        let mut tree = Tree::new();
-
-        // was_file
-        assert_eq!(tree.insert_node(HgPath::new(b"a"), entry), None);
-        assert_eq!(tree.files_count, 1);
-        assert_eq!(tree.insert_node(HgPath::new(b"a/b"), entry2), None);
-        assert_eq!(tree.files_count, 2);
-        assert_eq!(tree.get_mut(HgPath::new(b"a/b")), Some(&mut entry2));
-    }
-
-    #[test]
-    fn test_remove() {
-        let mut tree = Tree::new();
-        assert_eq!(tree.files_count, 0);
-        assert_eq!(tree.remove(HgPath::new(b"foo")), None);
-        assert_eq!(tree.files_count, 0);
-
-        let entry = DirstateEntry {
-            state: EntryState::Normal,
-            mode: 0,
-            mtime: 0,
-            size: 0,
-        };
-        assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None);
-        assert_eq!(tree.files_count, 1);
-
-        assert_eq!(tree.remove(HgPath::new(b"a/b/c")), Some(entry));
-        assert_eq!(tree.files_count, 0);
-
-        assert_eq!(tree.insert_node(HgPath::new(b"a/b/x"), entry), None);
-        assert_eq!(tree.insert_node(HgPath::new(b"a/b/y"), entry), None);
-        assert_eq!(tree.insert_node(HgPath::new(b"a/b/z"), entry), None);
-        assert_eq!(tree.insert_node(HgPath::new(b"x"), entry), None);
-        assert_eq!(tree.insert_node(HgPath::new(b"y"), entry), None);
-        assert_eq!(tree.files_count, 5);
-
-        assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(entry));
-        assert_eq!(tree.files_count, 4);
-        assert_eq!(tree.remove(HgPath::new(b"a/b/x")), None);
-        assert_eq!(tree.files_count, 4);
-        assert_eq!(tree.remove(HgPath::new(b"a/b/y")), Some(entry));
-        assert_eq!(tree.files_count, 3);
-        assert_eq!(tree.remove(HgPath::new(b"a/b/z")), Some(entry));
-        assert_eq!(tree.files_count, 2);
-
-        assert_eq!(tree.remove(HgPath::new(b"x")), Some(entry));
-        assert_eq!(tree.files_count, 1);
-        assert_eq!(tree.remove(HgPath::new(b"y")), Some(entry));
-        assert_eq!(tree.files_count, 0);
-
-        // `a` should have been cleaned up, no more files anywhere in its
-        // descendents
-        assert_eq!(tree.get_node(HgPath::new(b"a")), None);
-        assert_eq!(tree.root.children().unwrap().len(), 0);
-
-        let removed_entry = DirstateEntry {
-            state: EntryState::Removed,
-            ..entry
-        };
-        assert_eq!(tree.insert(HgPath::new(b"a"), removed_entry), None);
-        assert_eq!(tree.insert_node(HgPath::new(b"a/b/x"), entry), None);
-        assert_eq!(tree.files_count, 2);
-        dbg!(&tree);
-        assert_eq!(tree.remove(HgPath::new(b"a")), Some(removed_entry));
-        assert_eq!(tree.files_count, 1);
-        dbg!(&tree);
-        assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(entry));
-        assert_eq!(tree.files_count, 0);
-
-        // The entire tree should have been cleaned up, no more files anywhere
-        // in its descendents
-        assert_eq!(tree.root.children().unwrap().len(), 0);
-
-        let removed_entry = DirstateEntry {
-            state: EntryState::Removed,
-            ..entry
-        };
-        assert_eq!(tree.insert(HgPath::new(b"a"), entry), None);
-        assert_eq!(
-            tree.insert_node(HgPath::new(b"a/b/x"), removed_entry),
-            None
-        );
-        assert_eq!(tree.files_count, 2);
-        dbg!(&tree);
-        assert_eq!(tree.remove(HgPath::new(b"a")), Some(entry));
-        assert_eq!(tree.files_count, 1);
-        dbg!(&tree);
-        assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(removed_entry));
-        assert_eq!(tree.files_count, 0);
-
-        dbg!(&tree);
-        // The entire tree should have been cleaned up, no more files anywhere
-        // in its descendents
-        assert_eq!(tree.root.children().unwrap().len(), 0);
-
-        assert_eq!(tree.insert(HgPath::new(b"d"), entry), None);
-        assert_eq!(tree.insert(HgPath::new(b"d/d/d"), entry), None);
-        assert_eq!(tree.files_count, 2);
-
-        // Deleting the nested file should not delete the top directory as it
-        // used to be a file
-        assert_eq!(tree.remove(HgPath::new(b"d/d/d")), Some(entry));
-        assert_eq!(tree.files_count, 1);
-        assert!(tree.get_node(HgPath::new(b"d")).is_some());
-        assert!(tree.remove(HgPath::new(b"d")).is_some());
-        assert_eq!(tree.files_count, 0);
-
-        // Deleting the nested file should not delete the top file (other way
-        // around from the last case)
-        assert_eq!(tree.insert(HgPath::new(b"a/a"), entry), None);
-        assert_eq!(tree.files_count, 1);
-        assert_eq!(tree.insert(HgPath::new(b"a"), entry), None);
-        assert_eq!(tree.files_count, 2);
-        dbg!(&tree);
-        assert_eq!(tree.remove(HgPath::new(b"a/a")), Some(entry));
-        assert_eq!(tree.files_count, 1);
-        dbg!(&tree);
-        assert!(tree.get_node(HgPath::new(b"a")).is_some());
-        assert!(tree.get_node(HgPath::new(b"a/a")).is_none());
-    }
-
-    #[test]
-    fn test_was_directory() {
-        let mut tree = Tree::new();
-
-        let entry = DirstateEntry {
-            state: EntryState::Removed,
-            mode: 0,
-            mtime: 0,
-            size: 0,
-        };
-        assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None);
-        assert_eq!(tree.files_count, 1);
-
-        assert!(tree.insert_node(HgPath::new(b"a"), entry).is_some());
-        let new_a = tree.root.children().unwrap().get(&b"a".to_vec()).unwrap();
-
-        match &new_a.kind {
-            NodeKind::Directory(_) => panic!(),
-            NodeKind::File(f) => {
-                let dir = f.was_directory.clone().unwrap();
-                let c = dir
-                    .children
-                    .get(&b"b".to_vec())
-                    .unwrap()
-                    .children()
-                    .unwrap()
-                    .get(&b"c".to_vec())
-                    .unwrap();
-
-                assert_eq!(
-                    match &c.kind {
-                        NodeKind::Directory(_) => panic!(),
-                        NodeKind::File(f) => f.entry,
-                    },
-                    entry
-                );
-            }
-        }
-        assert_eq!(tree.files_count, 2);
-        dbg!(&tree);
-        assert_eq!(tree.remove(HgPath::new(b"a/b/c")), Some(entry));
-        assert_eq!(tree.files_count, 1);
-        dbg!(&tree);
-        let a = tree.get_node(HgPath::new(b"a")).unwrap();
-        match &a.kind {
-            NodeKind::Directory(_) => panic!(),
-            NodeKind::File(f) => {
-                // Directory in `was_directory` was emptied, should be removed
-                assert_eq!(f.was_directory, None);
-            }
-        }
-    }
-    #[test]
-    fn test_extend() {
-        let insertions = [
-            (
-                HgPathBuf::from_bytes(b"d"),
-                DirstateEntry {
-                    state: EntryState::Added,
-                    mode: 0,
-                    mtime: -1,
-                    size: -1,
-                },
-            ),
-            (
-                HgPathBuf::from_bytes(b"b"),
-                DirstateEntry {
-                    state: EntryState::Normal,
-                    mode: 33188,
-                    mtime: 1599647984,
-                    size: 2,
-                },
-            ),
-            (
-                HgPathBuf::from_bytes(b"a/a"),
-                DirstateEntry {
-                    state: EntryState::Normal,
-                    mode: 33188,
-                    mtime: 1599647984,
-                    size: 2,
-                },
-            ),
-            (
-                HgPathBuf::from_bytes(b"d/d/d"),
-                DirstateEntry {
-                    state: EntryState::Removed,
-                    mode: 0,
-                    mtime: 0,
-                    size: 0,
-                },
-            ),
-        ]
-        .to_vec();
-        let mut tree = Tree::new();
-
-        tree.extend(insertions.clone().into_iter());
-
-        for (path, _) in &insertions {
-            assert!(tree.contains_key(path), true);
-        }
-        assert_eq!(tree.files_count, 4);
-    }
-}
diff --git a/rust/hg-core/src/dirstate/parsers.rs b/rust/hg-core/src/dirstate/parsers.rs
index 8759e22f1649cd71a59d05e8f5ef8dce77ca4b44_cnVzdC9oZy1jb3JlL3NyYy9kaXJzdGF0ZS9wYXJzZXJzLnJz..441024b279a635f3bf9bd0e857c8e346abb48d98_cnVzdC9oZy1jb3JlL3NyYy9kaXJzdGF0ZS9wYXJzZXJzLnJz 100644
--- a/rust/hg-core/src/dirstate/parsers.rs
+++ b/rust/hg-core/src/dirstate/parsers.rs
@@ -73,7 +73,6 @@
 }
 
 /// `now` is the duration in seconds since the Unix epoch
-#[cfg(not(feature = "dirstate-tree"))]
 pub fn pack_dirstate(
     state_map: &mut StateMap,
     copy_map: &CopyMap,
@@ -146,79 +145,6 @@
 
     Ok(packed)
 }
-/// `now` is the duration in seconds since the Unix epoch
-#[cfg(feature = "dirstate-tree")]
-pub fn pack_dirstate(
-    state_map: &mut StateMap,
-    copy_map: &CopyMap,
-    parents: DirstateParents,
-    now: Duration,
-) -> Result<Vec<u8>, DirstatePackError> {
-    // TODO move away from i32 before 2038.
-    let now: i32 = now.as_secs().try_into().expect("time overflow");
-
-    let expected_size: usize = state_map
-        .iter()
-        .map(|(filename, _)| {
-            let mut length = MIN_ENTRY_SIZE + filename.len();
-            if let Some(copy) = copy_map.get(&filename) {
-                length += copy.len() + 1;
-            }
-            length
-        })
-        .sum();
-    let expected_size = expected_size + PARENT_SIZE * 2;
-
-    let mut packed = Vec::with_capacity(expected_size);
-    let mut new_state_map = vec![];
-
-    packed.extend(&parents.p1);
-    packed.extend(&parents.p2);
-
-    for (filename, entry) in state_map.iter() {
-        let new_filename = filename.to_owned();
-        let mut new_mtime: i32 = entry.mtime;
-        if entry.state == EntryState::Normal && entry.mtime == now {
-            // The file was last modified "simultaneously" with the current
-            // write to dirstate (i.e. within the same second for file-
-            // systems with a granularity of 1 sec). This commonly happens
-            // for at least a couple of files on 'update'.
-            // The user could change the file without changing its size
-            // within the same second. Invalidate the file's mtime in
-            // dirstate, forcing future 'status' calls to compare the
-            // contents of the file if the size is the same. This prevents
-            // mistakenly treating such files as clean.
-            new_mtime = -1;
-            new_state_map.push((
-                filename.to_owned(),
-                DirstateEntry {
-                    mtime: new_mtime,
-                    ..entry
-                },
-            ));
-        }
-        let mut new_filename = new_filename.into_vec();
-        if let Some(copy) = copy_map.get(&filename) {
-            new_filename.push(b'\0');
-            new_filename.extend(copy.bytes());
-        }
-
-        packed.write_u8(entry.state.into())?;
-        packed.write_i32::<BigEndian>(entry.mode)?;
-        packed.write_i32::<BigEndian>(entry.size)?;
-        packed.write_i32::<BigEndian>(new_mtime)?;
-        packed.write_i32::<BigEndian>(new_filename.len() as i32)?;
-        packed.extend(new_filename)
-    }
-
-    if packed.len() != expected_size {
-        return Err(DirstatePackError::BadSize(expected_size, packed.len()));
-    }
-
-    state_map.extend(new_state_map);
-
-    Ok(packed)
-}
 
 #[cfg(test)]
 mod tests {
diff --git a/rust/hg-core/src/dirstate/status.rs b/rust/hg-core/src/dirstate/status.rs
index 8759e22f1649cd71a59d05e8f5ef8dce77ca4b44_cnVzdC9oZy1jb3JlL3NyYy9kaXJzdGF0ZS9zdGF0dXMucnM=..441024b279a635f3bf9bd0e857c8e346abb48d98_cnVzdC9oZy1jb3JlL3NyYy9kaXJzdGF0ZS9zdGF0dXMucnM= 100644
--- a/rust/hg-core/src/dirstate/status.rs
+++ b/rust/hg-core/src/dirstate/status.rs
@@ -9,9 +9,6 @@
 //! It is currently missing a lot of functionality compared to the Python one
 //! and will only be triggered in narrow cases.
 
-#[cfg(feature = "dirstate-tree")]
-use crate::dirstate::dirstate_tree::iter::StatusShortcut;
-#[cfg(not(feature = "dirstate-tree"))]
 use crate::utils::path_auditor::PathAuditor;
 use crate::{
     dirstate::SIZE_FROM_OTHER_PARENT,
@@ -703,83 +700,6 @@
     ///
     /// This takes a mutable reference to the results to account for the
     /// `extend` in timings
-    #[cfg(feature = "dirstate-tree")]
-    #[timed]
-    pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) {
-        results.par_extend(
-            self.dmap
-                .fs_iter(self.root_dir.clone())
-                .par_bridge()
-                .filter(|(path, _)| self.matcher.matches(path))
-                .map(move |(filename, shortcut)| {
-                    let entry = match shortcut {
-                        StatusShortcut::Entry(e) => e,
-                        StatusShortcut::Dispatch(d) => {
-                            return (Cow::Owned(filename), d)
-                        }
-                    };
-                    let filename_as_path = match hg_path_to_path_buf(&filename)
-                    {
-                        Ok(f) => f,
-                        Err(_) => {
-                            return (
-                                Cow::Owned(filename),
-                                INVALID_PATH_DISPATCH,
-                            )
-                        }
-                    };
-                    let meta = self
-                        .root_dir
-                        .join(filename_as_path)
-                        .symlink_metadata();
-
-                    match meta {
-                        Ok(m)
-                            if !(m.file_type().is_file()
-                                || m.file_type().is_symlink()) =>
-                        {
-                            (
-                                Cow::Owned(filename),
-                                dispatch_missing(entry.state),
-                            )
-                        }
-                        Ok(m) => {
-                            let dispatch = dispatch_found(
-                                &filename,
-                                entry,
-                                HgMetadata::from_metadata(m),
-                                &self.dmap.copy_map,
-                                self.options,
-                            );
-                            (Cow::Owned(filename), dispatch)
-                        }
-                        Err(e)
-                            if e.kind() == ErrorKind::NotFound
-                                || e.raw_os_error() == Some(20) =>
-                        {
-                            // Rust does not yet have an `ErrorKind` for
-                            // `NotADirectory` (errno 20)
-                            // It happens if the dirstate contains `foo/bar`
-                            // and foo is not a
-                            // directory
-                            (
-                                Cow::Owned(filename),
-                                dispatch_missing(entry.state),
-                            )
-                        }
-                        Err(e) => {
-                            (Cow::Owned(filename), dispatch_os_error(&e))
-                        }
-                    }
-                }),
-        );
-    }
-
-    /// Add the files in the dirstate to the results.
-    ///
-    /// This takes a mutable reference to the results to account for the
-    /// `extend` in timings
-    #[cfg(not(feature = "dirstate-tree"))]
     #[timed]
     pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) {
         results.par_extend(
@@ -850,7 +770,6 @@
     ///
     /// This takes a mutable reference to the results to account for the
     /// `extend` in timings
-    #[cfg(not(feature = "dirstate-tree"))]
     #[timed]
     pub fn handle_unknowns(&self, results: &mut Vec<DispatchedPath<'a>>) {
         let to_visit: Vec<(&HgPath, &DirstateEntry)> =
diff --git a/rust/hg-core/src/operations/dirstate_status.rs b/rust/hg-core/src/operations/dirstate_status.rs
index 8759e22f1649cd71a59d05e8f5ef8dce77ca4b44_cnVzdC9oZy1jb3JlL3NyYy9vcGVyYXRpb25zL2RpcnN0YXRlX3N0YXR1cy5ycw==..441024b279a635f3bf9bd0e857c8e346abb48d98_cnVzdC9oZy1jb3JlL3NyYy9vcGVyYXRpb25zL2RpcnN0YXRlX3N0YXR1cy5ycw== 100644
--- a/rust/hg-core/src/operations/dirstate_status.rs
+++ b/rust/hg-core/src/operations/dirstate_status.rs
@@ -14,66 +14,6 @@
 /// files.
 pub type LookupAndStatus<'a> = (Vec<HgPathCow<'a>>, DirstateStatus<'a>);
 
-#[cfg(feature = "dirstate-tree")]
-impl<'a, M: Matcher + Sync> Status<'a, M> {
-    pub(crate) fn run(&self) -> Result<LookupAndStatus<'a>, StatusError> {
-        let (traversed_sender, traversed_receiver) =
-            crossbeam_channel::unbounded();
-
-        // Step 1: check the files explicitly mentioned by the user
-        let (work, mut results) = self.walk_explicit(traversed_sender.clone());
-
-        // Step 2: Check files in the dirstate
-        if !self.matcher.is_exact() {
-            self.extend_from_dmap(&mut results);
-        }
-        // Step 3: Check the working directory if listing unknowns
-        if !work.is_empty() {
-            // Hashmaps are quite a bit slower to build than vecs, so only
-            // build it if needed.
-            let mut old_results = None;
-
-            // Step 2: recursively check the working directory for changes if
-            // needed
-            for (dir, dispatch) in work {
-                match dispatch {
-                    Dispatch::Directory { was_file } => {
-                        if was_file {
-                            results.push((dir.to_owned(), Dispatch::Removed));
-                        }
-                        if self.options.list_ignored
-                            || self.options.list_unknown
-                                && !self.dir_ignore(&dir)
-                        {
-                            if old_results.is_none() {
-                                old_results =
-                                    Some(results.iter().cloned().collect());
-                            }
-                            self.traverse(
-                                &dir,
-                                old_results
-                                    .as_ref()
-                                    .expect("old results should exist"),
-                                &mut results,
-                                traversed_sender.clone(),
-                            );
-                        }
-                    }
-                    _ => {
-                        unreachable!("There can only be directories in `work`")
-                    }
-                }
-            }
-        }
-
-        drop(traversed_sender);
-        let traversed = traversed_receiver.into_iter().collect();
-
-        Ok(build_response(results, traversed))
-    }
-}
-
-#[cfg(not(feature = "dirstate-tree"))]
 impl<'a, M: Matcher + Sync> Status<'a, M> {
     pub(crate) fn run(&self) -> Result<LookupAndStatus<'a>, StatusError> {
         let (traversed_sender, traversed_receiver) =
diff --git a/rust/hg-cpython/Cargo.toml b/rust/hg-cpython/Cargo.toml
index 8759e22f1649cd71a59d05e8f5ef8dce77ca4b44_cnVzdC9oZy1jcHl0aG9uL0NhcmdvLnRvbWw=..441024b279a635f3bf9bd0e857c8e346abb48d98_cnVzdC9oZy1jcHl0aG9uL0NhcmdvLnRvbWw= 100644
--- a/rust/hg-cpython/Cargo.toml
+++ b/rust/hg-cpython/Cargo.toml
@@ -10,7 +10,6 @@
 
 [features]
 default = ["python27"]
-dirstate-tree = ["hg-core/dirstate-tree"]
 
 # Features to build an extension module:
 python27 = ["cpython/python27-sys", "cpython/extension-module-2-7"]
diff --git a/rust/hg-cpython/src/dirstate/dirstate_map.rs b/rust/hg-cpython/src/dirstate/dirstate_map.rs
index 8759e22f1649cd71a59d05e8f5ef8dce77ca4b44_cnVzdC9oZy1jcHl0aG9uL3NyYy9kaXJzdGF0ZS9kaXJzdGF0ZV9tYXAucnM=..441024b279a635f3bf9bd0e857c8e346abb48d98_cnVzdC9oZy1jcHl0aG9uL3NyYy9kaXJzdGF0ZS9kaXJzdGF0ZV9tYXAucnM= 100644
--- a/rust/hg-cpython/src/dirstate/dirstate_map.rs
+++ b/rust/hg-cpython/src/dirstate/dirstate_map.rs
@@ -547,10 +547,9 @@
     ) -> Ref<'a, RustDirstateMap> {
         self.inner(py).borrow()
     }
-    #[cfg(not(feature = "dirstate-tree"))]
     fn translate_key(
         py: Python,
         res: (&HgPathBuf, &DirstateEntry),
     ) -> PyResult<Option<PyBytes>> {
         Ok(Some(PyBytes::new(py, res.0.as_bytes())))
     }
@@ -551,10 +550,9 @@
     fn translate_key(
         py: Python,
         res: (&HgPathBuf, &DirstateEntry),
     ) -> PyResult<Option<PyBytes>> {
         Ok(Some(PyBytes::new(py, res.0.as_bytes())))
     }
-    #[cfg(not(feature = "dirstate-tree"))]
     fn translate_key_value(
         py: Python,
         res: (&HgPathBuf, &DirstateEntry),
@@ -565,24 +563,6 @@
             make_dirstate_tuple(py, &entry)?,
         )))
     }
-    #[cfg(feature = "dirstate-tree")]
-    fn translate_key(
-        py: Python,
-        res: (HgPathBuf, DirstateEntry),
-    ) -> PyResult<Option<PyBytes>> {
-        Ok(Some(PyBytes::new(py, res.0.as_bytes())))
-    }
-    #[cfg(feature = "dirstate-tree")]
-    fn translate_key_value(
-        py: Python,
-        res: (HgPathBuf, DirstateEntry),
-    ) -> PyResult<Option<(PyBytes, PyObject)>> {
-        let (f, entry) = res;
-        Ok(Some((
-            PyBytes::new(py, f.as_bytes()),
-            make_dirstate_tuple(py, &entry)?,
-        )))
-    }
 }
 
 py_shared_iterator!(