Mercurial > hg-stable
changeset 47120:52906934b775
dirstate-tree: Add has_dir and has_tracked_dir
A node without a `DirstateMap` entry represents a directory.
Only some values of `EntryState` represent tracked files.
A directory is considered "tracked" if it contains any descendant file that
is tracked. To avoid a sub-tree traversal in `has_tracked_dir` we add a
counter for this. A boolean flag would become insufficent when we implement
remove_file and drop_file.
`add_file_node` is more general than needed here, in anticipation of adding
the `add_file` and `remove_file` methods.
Differential Revision: https://phab.mercurial-scm.org/D10490
author | Simon Sapin <simon.sapin@octobus.net> |
---|---|
date | Mon, 12 Apr 2021 19:46:24 +0200 |
parents | ba17a2ee85ac |
children | 7dfc598ddcfe |
files | rust/hg-core/src/dirstate.rs rust/hg-core/src/dirstate_tree/dirstate_map.rs |
diffstat | 2 files changed, 99 insertions(+), 6 deletions(-) [+] |
line wrap: on
line diff
--- a/rust/hg-core/src/dirstate.rs Mon Apr 12 18:42:51 2021 +0200 +++ b/rust/hg-core/src/dirstate.rs Mon Apr 12 19:46:24 2021 +0200 @@ -66,6 +66,16 @@ Unknown, } +impl EntryState { + pub fn is_tracked(self) -> bool { + use EntryState::*; + match self { + Normal | Added | Merged => true, + Removed | Unknown => false, + } + } +} + impl TryFrom<u8> for EntryState { type Error = HgError;
--- a/rust/hg-core/src/dirstate_tree/dirstate_map.rs Mon Apr 12 18:42:51 2021 +0200 +++ b/rust/hg-core/src/dirstate_tree/dirstate_map.rs Mon Apr 12 19:46:24 2021 +0200 @@ -46,11 +46,29 @@ /// string prefix. type ChildNodes = BTreeMap<WithBasename<HgPathBuf>, Node>; +/// Represents a file or a directory #[derive(Default)] struct Node { + /// `None` for directories entry: Option<DirstateEntry>, + copy_source: Option<HgPathBuf>, + children: ChildNodes, + + /// How many (non-inclusive) descendants of this node are tracked files + tracked_descendants_count: usize, +} + +impl Node { + /// Whether this node has a `DirstateEntry` with `.state.is_tracked()` + fn is_tracked_file(&self) -> bool { + if let Some(entry) = &self.entry { + entry.state.is_tracked() + } else { + false + } + } } /// `(full_path, entry, copy_source)` @@ -87,18 +105,48 @@ } } + /// Returns a mutable reference to the node at `path` if it exists + /// /// This takes `root` instead of `&mut self` so that callers can mutate /// other fields while the returned borrow is still valid fn get_node_mut<'tree>( root: &'tree mut ChildNodes, path: &HgPath, ) -> Option<&'tree mut Node> { + Self::each_and_get(root, path, |_| {}) + } + + /// Call `each` for each ancestor node of the one at `path` (not including + /// that node itself), starting from nearest the root. + /// + /// Panics (possibly after some calls to `each`) if there is no node at + /// `path`. + fn for_each_ancestor_node<'tree>( + &mut self, + path: &HgPath, + each: impl FnMut(&mut Node), + ) { + let parent = path.parent(); + if !parent.is_empty() { + Self::each_and_get(&mut self.root, parent, each) + .expect("missing dirstate node"); + } + } + + /// Common implementation detail of `get_node_mut` and + /// `for_each_ancestor_node` + fn each_and_get<'tree>( + root: &'tree mut ChildNodes, + path: &HgPath, + mut each: impl FnMut(&mut Node), + ) -> Option<&'tree mut Node> { let mut children = root; let mut components = path.components(); let mut component = components.next().expect("expected at least one components"); loop { let child = children.get_mut(component)?; + each(child); if let Some(next_component) = components.next() { component = next_component; children = &mut child.children; @@ -162,10 +210,29 @@ self.nodes_with_copy_source_count -= 1 } } + let tracked_count_increment = + match (node.is_tracked_file(), new_entry.state.is_tracked()) { + (false, true) => 1, + (true, false) => -1, + _ => 0, + }; + node.entry = Some(new_entry); if let Some(source) = new_copy_source { node.copy_source = source } + // Borrow of `self.root` through `node` ends here + + match tracked_count_increment { + 1 => self.for_each_ancestor_node(path, |node| { + node.tracked_descendants_count += 1 + }), + // We can’t use `+= -1` because the counter is unsigned + -1 => self.for_each_ancestor_node(path, |node| { + node.tracked_descendants_count -= 1 + }), + _ => {} + } } fn iter_nodes<'a>( @@ -335,16 +402,28 @@ fn has_tracked_dir( &mut self, - _directory: &HgPath, + directory: &HgPath, ) -> Result<bool, DirstateMapError> { - todo!() + if let Some(node) = self.get_node(directory) { + // A node without a `DirstateEntry` was created to hold child + // nodes, and is therefore a directory. + Ok(node.entry.is_none() && node.tracked_descendants_count > 0) + } else { + Ok(false) + } } fn has_dir( &mut self, - _directory: &HgPath, + directory: &HgPath, ) -> Result<bool, DirstateMapError> { - todo!() + if let Some(node) = self.get_node(directory) { + // A node without a `DirstateEntry` was created to hold child + // nodes, and is therefore a directory. + Ok(node.entry.is_none()) + } else { + Ok(false) + } } fn parents( @@ -437,11 +516,15 @@ } fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> { - todo!() + // Do nothing, this `DirstateMap` does not a separate `all_dirs` that + // needs to be recomputed + Ok(()) } fn set_dirs(&mut self) -> Result<(), DirstateMapError> { - todo!() + // Do nothing, this `DirstateMap` does not a separate `dirs` that needs + // to be recomputed + Ok(()) } fn status<'a>(