# HG changeset patch # User Simon Sapin # Date 1622559359 -7200 # Node ID 9d58e54b5966440dbc19c9f998df05622e66277e # Parent 3b9914b28133c0918186b6e8b9e4f1916e21338d dirstate-v2: Drop parent directory cache when removing a dirstate node The premise of the directory cache is that the dirstate contains child nodes for every entry that `read_dir` would return. When removing nodes, that may not be the case anymore so the cache should be invalidated. Differential Revision: https://phab.mercurial-scm.org/D10829 diff -r 3b9914b28133 -r 9d58e54b5966 rust/hg-core/src/dirstate_tree/dirstate_map.rs --- a/rust/hg-core/src/dirstate_tree/dirstate_map.rs Mon May 31 19:54:41 2021 +0200 +++ b/rust/hg-core/src/dirstate_tree/dirstate_map.rs Tue Jun 01 16:55:59 2021 +0200 @@ -712,11 +712,17 @@ had_entry: bool, had_copy_source: bool, } + + /// If this returns `Ok(Some((dropped, removed)))`, then + /// + /// * `dropped` is about the leaf node that was at `filename` + /// * `removed` is whether this particular level of recursion just + /// removed a node in `nodes`. fn recur<'on_disk>( on_disk: &'on_disk [u8], nodes: &mut ChildNodes<'on_disk>, path: &HgPath, - ) -> Result, DirstateV2ParseError> { + ) -> Result, DirstateV2ParseError> { let (first_path_component, rest_of_path) = path.split_first_component(); let node = if let Some(node) = @@ -728,11 +734,21 @@ }; let dropped; if let Some(rest) = rest_of_path { - if let Some(d) = recur(on_disk, &mut node.children, rest)? { + if let Some((d, removed)) = + recur(on_disk, &mut node.children, rest)? + { dropped = d; if dropped.was_tracked { node.tracked_descendants_count -= 1; } + + // Directory caches must be invalidated when removing a + // child node + if removed { + if let NodeData::CachedDirectory { .. } = &node.data { + node.data = NodeData::None + } + } } else { return Ok(None); } @@ -752,16 +768,18 @@ } // After recursion, for both leaf (rest_of_path is None) nodes and // parent nodes, remove a node if it just became empty. - if !node.data.has_entry() + let remove = !node.data.has_entry() && node.copy_source.is_none() - && node.children.is_empty() - { + && node.children.is_empty(); + if remove { nodes.make_mut(on_disk)?.remove(first_path_component); } - Ok(Some(dropped)) + Ok(Some((dropped, remove))) } - if let Some(dropped) = recur(self.on_disk, &mut self.root, filename)? { + if let Some((dropped, _removed)) = + recur(self.on_disk, &mut self.root, filename)? + { if dropped.had_entry { self.nodes_with_entry_count -= 1 } diff -r 3b9914b28133 -r 9d58e54b5966 tests/test-status.t --- a/tests/test-status.t Mon May 31 19:54:41 2021 +0200 +++ b/tests/test-status.t Tue Jun 01 16:55:59 2021 +0200 @@ -957,4 +957,18 @@ $ hg status ? subdir/unknown + $ rm subdir/unknown + $ hg status + +Removing a node from the dirstate resets the cache for its parent directory + + $ hg forget subdir/a + $ hg debugdirstate --dirs --no-dates | grep '^d' + d 0 0 set subdir + $ hg ci -qm '#1' + $ hg debugdirstate --dirs --no-dates | grep '^d' + d 0 0 unset subdir + $ hg status + ? subdir/a + #endif