diff --git a/rust/hg-core/src/dirstate_tree/dirstate_map.rs b/rust/hg-core/src/dirstate_tree/dirstate_map.rs --- a/rust/hg-core/src/dirstate_tree/dirstate_map.rs +++ b/rust/hg-core/src/dirstate_tree/dirstate_map.rs @@ -495,6 +495,19 @@ impl<'on_disk> DirstateMap<'on_disk> { } } + pub(super) fn get_or_insert<'tree, 'path>( + &'tree mut self, + path: &HgPath, + ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> { + Self::get_or_insert_node( + self.on_disk, + &mut self.root, + path, + WithBasename::to_cow_owned, + |_| {}, + ) + } + pub(super) fn get_or_insert_node<'tree, 'path>( on_disk: &'on_disk [u8], root: &'tree mut ChildNodes<'on_disk>, diff --git a/rust/hg-core/src/dirstate_tree/status.rs b/rust/hg-core/src/dirstate_tree/status.rs --- a/rust/hg-core/src/dirstate_tree/status.rs +++ b/rust/hg-core/src/dirstate_tree/status.rs @@ -6,7 +6,6 @@ use crate::dirstate_tree::dirstate_map:: use crate::dirstate_tree::dirstate_map::NodeRef; use crate::dirstate_tree::on_disk::DirstateV2ParseError; use crate::dirstate_tree::on_disk::Timestamp; -use crate::dirstate_tree::path_with_basename::WithBasename; use crate::matchers::get_ignore_function; use crate::matchers::Matcher; use crate::utils::files::get_bytes_from_os_string; @@ -70,6 +69,7 @@ pub fn status<'tree, 'on_disk: 'tree>( outcome: Default::default(), ignore_patterns_have_changed: patterns_changed, new_cachable_directories: Default::default(), + outated_cached_directories: Default::default(), filesystem_time_at_status_start: filesystem_now(&root_dir).ok(), }; let is_at_repo_root = true; @@ -91,18 +91,22 @@ pub fn status<'tree, 'on_disk: 'tree>( )?; let mut outcome = common.outcome.into_inner().unwrap(); let new_cachable = common.new_cachable_directories.into_inner().unwrap(); + let outdated = common.outated_cached_directories.into_inner().unwrap(); outcome.dirty = common.ignore_patterns_have_changed == Some(true) + || !outdated.is_empty() || !new_cachable.is_empty(); + // Remove outdated mtimes before adding new mtimes, in case a given + // directory is both + for path in &outdated { + let node = dmap.get_or_insert(path)?; + if let NodeData::CachedDirectory { .. } = &node.data { + node.data = NodeData::None + } + } for (path, mtime) in &new_cachable { - let node = DirstateMap::get_or_insert_node( - dmap.on_disk, - &mut dmap.root, - path, - WithBasename::to_cow_owned, - |_| {}, - )?; + let node = dmap.get_or_insert(path)?; match &node.data { NodeData::Entry(_) => {} // Don’t overwrite an entry NodeData::CachedDirectory { .. } | NodeData::None => { @@ -123,6 +127,7 @@ struct StatusCommon<'a, 'tree, 'on_disk: ignore_fn: IgnoreFnType<'a>, outcome: Mutex>, new_cachable_directories: Mutex, Timestamp)>>, + outated_cached_directories: Mutex>>, /// Whether ignore files like `.hgignore` have changed since the previous /// time a `status()` call wrote their hash to the dirstate. `None` means @@ -155,6 +160,22 @@ impl<'a, 'tree, 'on_disk> StatusCommon<' .push((hg_path.to_owned().into(), BadMatch::OsError(errno))) } + fn check_for_outdated_directory_cache( + &self, + dirstate_node: &NodeRef<'tree, 'on_disk>, + ) -> Result<(), DirstateV2ParseError> { + if self.ignore_patterns_have_changed == Some(true) + && dirstate_node.cached_directory_mtime().is_some() + { + self.outated_cached_directories.lock().unwrap().push( + dirstate_node + .full_path_borrowed(self.dmap.on_disk)? + .detach_from_tree(), + ) + } + Ok(()) + } + /// If this returns true, we can get accurate results by only using /// `symlink_metadata` for child nodes that exist in the dirstate and don’t /// need to call `read_dir`. @@ -304,6 +325,7 @@ impl<'a, 'tree, 'on_disk> StatusCommon<' dirstate_node: NodeRef<'tree, 'on_disk>, has_ignored_ancestor: bool, ) -> Result<(), DirstateV2ParseError> { + self.check_for_outdated_directory_cache(&dirstate_node)?; let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?; let file_type = fs_metadata.file_type(); let file_or_symlink = file_type.is_file() || file_type.is_symlink(); @@ -521,6 +543,7 @@ impl<'a, 'tree, 'on_disk> StatusCommon<' &self, dirstate_node: NodeRef<'tree, 'on_disk>, ) -> Result<(), DirstateV2ParseError> { + self.check_for_outdated_directory_cache(&dirstate_node)?; self.mark_removed_or_deleted_if_file( &dirstate_node.full_path_borrowed(self.dmap.on_disk)?, dirstate_node.state()?,