##// END OF EJS Templates
status: Extend read_dir caching to directories with ignored files...
Simon Sapin -
r48269:94e38822 default
parent child Browse files
Show More
@@ -98,13 +98,16 b' pub(super) struct Node {'
98 98 /// and must cause a different modification time (unless the system
99 99 /// clock jumps back and we get unlucky, which is not impossible but
100 100 /// but deemed unlikely enough).
101 /// - The directory did not contain any child entry that did not have a
102 /// corresponding dirstate node.
101 /// - All direct children of this directory (as returned by
102 /// `std::fs::read_dir`) either have a corresponding dirstate node, or
103 /// are ignored by ignore patterns whose hash is in
104 /// `Header::ignore_patterns_hash`.
103 105 ///
104 106 /// This means that if `std::fs::symlink_metadata` later reports the
105 /// same modification time, we don’t need to call `std::fs::read_dir`
106 /// again for this directory and can iterate child dirstate nodes
107 /// instead.
107 /// same modification time and ignored patterns haven’t changed, a run
108 /// of status that is not listing ignored files can skip calling
109 /// `std::fs::read_dir` again for this directory, iterate child
110 /// dirstate nodes instead.
108 111 state: u8,
109 112 data: Entry,
110 113 }
@@ -190,18 +190,21 b" impl<'a, 'tree, 'on_disk> StatusCommon<'"
190 190 // This happens for example with `hg status -mard`.
191 191 return true;
192 192 }
193 if let Some(cached_mtime) = cached_directory_mtime {
194 // The dirstate contains a cached mtime for this directory, set by
195 // a previous run of the `status` algorithm which found this
196 // directory eligible for `read_dir` caching.
197 if let Some(meta) = directory_metadata {
198 if let Ok(current_mtime) = meta.modified() {
199 if current_mtime == cached_mtime.into() {
200 // The mtime of that directory has not changed since
201 // then, which means that the
202 // results of `read_dir` should also
203 // be unchanged.
204 return true;
193 if !self.options.list_ignored
194 && self.ignore_patterns_have_changed == Some(false)
195 {
196 if let Some(cached_mtime) = cached_directory_mtime {
197 // The dirstate contains a cached mtime for this directory, set
198 // by a previous run of the `status` algorithm which found this
199 // directory eligible for `read_dir` caching.
200 if let Some(meta) = directory_metadata {
201 if let Ok(current_mtime) = meta.modified() {
202 if current_mtime == cached_mtime.into() {
203 // The mtime of that directory has not changed
204 // since then, which means that the results of
205 // `read_dir` should also be unchanged.
206 return true;
207 }
205 208 }
206 209 }
207 210 }
@@ -209,8 +212,8 b" impl<'a, 'tree, 'on_disk> StatusCommon<'"
209 212 false
210 213 }
211 214
212 /// Returns whether the filesystem directory was found to have any entry
213 /// that does not have a corresponding dirstate tree node.
215 /// Returns whether all child entries of the filesystem directory have a
216 /// corresponding dirstate node or are ignored.
214 217 fn traverse_fs_directory_and_dirstate(
215 218 &self,
216 219 has_ignored_ancestor: bool,
@@ -248,11 +251,10 b" impl<'a, 'tree, 'on_disk> StatusCommon<'"
248 251 })
249 252 .collect::<Result<_, _>>()?;
250 253
251 // Conservatively don’t let the caller assume that there aren’t
252 // any, since we don’t know.
253 let directory_has_any_fs_only_entry = true;
254 // We don’t know, so conservatively say this isn’t the case
255 let children_all_have_dirstate_node_or_are_ignored = false;
254 256
255 return Ok(directory_has_any_fs_only_entry);
257 return Ok(children_all_have_dirstate_node_or_are_ignored);
256 258 }
257 259
258 260 let mut fs_entries = if let Ok(entries) = self.read_dir(
@@ -295,27 +297,32 b" impl<'a, 'tree, 'on_disk> StatusCommon<'"
295 297 .par_bridge()
296 298 .map(|pair| {
297 299 use itertools::EitherOrBoth::*;
298 let is_fs_only = pair.is_right();
300 let has_dirstate_node_or_is_ignored;
299 301 match pair {
300 Both(dirstate_node, fs_entry) => self
301 .traverse_fs_and_dirstate(
302 Both(dirstate_node, fs_entry) => {
303 self.traverse_fs_and_dirstate(
302 304 &fs_entry.full_path,
303 305 &fs_entry.metadata,
304 306 dirstate_node,
305 307 has_ignored_ancestor,
306 )?,
308 )?;
309 has_dirstate_node_or_is_ignored = true
310 }
307 311 Left(dirstate_node) => {
308 self.traverse_dirstate_only(dirstate_node)?
312 self.traverse_dirstate_only(dirstate_node)?;
313 has_dirstate_node_or_is_ignored = true;
309 314 }
310 Right(fs_entry) => self.traverse_fs_only(
311 has_ignored_ancestor,
312 directory_hg_path,
313 fs_entry,
314 ),
315 Right(fs_entry) => {
316 has_dirstate_node_or_is_ignored = self.traverse_fs_only(
317 has_ignored_ancestor,
318 directory_hg_path,
319 fs_entry,
320 )
321 }
315 322 }
316 Ok(is_fs_only)
323 Ok(has_dirstate_node_or_is_ignored)
317 324 })
318 .try_reduce(|| false, |a, b| Ok(a || b))
325 .try_reduce(|| true, |a, b| Ok(a && b))
319 326 }
320 327
321 328 fn traverse_fs_and_dirstate(
@@ -348,7 +355,7 b" impl<'a, 'tree, 'on_disk> StatusCommon<'"
348 355 }
349 356 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
350 357 let is_at_repo_root = false;
351 let directory_has_any_fs_only_entry = self
358 let children_all_have_dirstate_node_or_are_ignored = self
352 359 .traverse_fs_directory_and_dirstate(
353 360 is_ignored,
354 361 dirstate_node.children(self.dmap.on_disk)?,
@@ -359,7 +366,7 b" impl<'a, 'tree, 'on_disk> StatusCommon<'"
359 366 is_at_repo_root,
360 367 )?;
361 368 self.maybe_save_directory_mtime(
362 directory_has_any_fs_only_entry,
369 children_all_have_dirstate_node_or_are_ignored,
363 370 fs_metadata,
364 371 dirstate_node,
365 372 )?
@@ -394,7 +401,10 b" impl<'a, 'tree, 'on_disk> StatusCommon<'"
394 401 } else {
395 402 // `node.entry.is_none()` indicates a "directory"
396 403 // node, but the filesystem has a file
397 self.mark_unknown_or_ignored(has_ignored_ancestor, hg_path)
404 self.mark_unknown_or_ignored(
405 has_ignored_ancestor,
406 hg_path,
407 );
398 408 }
399 409 }
400 410
@@ -408,11 +418,11 b" impl<'a, 'tree, 'on_disk> StatusCommon<'"
408 418
409 419 fn maybe_save_directory_mtime(
410 420 &self,
411 directory_has_any_fs_only_entry: bool,
421 children_all_have_dirstate_node_or_are_ignored: bool,
412 422 directory_metadata: &std::fs::Metadata,
413 423 dirstate_node: NodeRef<'tree, 'on_disk>,
414 424 ) -> Result<(), DirstateV2ParseError> {
415 if !directory_has_any_fs_only_entry {
425 if children_all_have_dirstate_node_or_are_ignored {
416 426 // All filesystem directory entries from `read_dir` have a
417 427 // corresponding node in the dirstate, so we can reconstitute the
418 428 // names of those entries without calling `read_dir` again.
@@ -584,12 +594,14 b" impl<'a, 'tree, 'on_disk> StatusCommon<'"
584 594 }
585 595
586 596 /// Something in the filesystem has no corresponding dirstate node
597 ///
598 /// Returns whether that path is ignored
587 599 fn traverse_fs_only(
588 600 &self,
589 601 has_ignored_ancestor: bool,
590 602 directory_hg_path: &HgPath,
591 603 fs_entry: &DirEntry,
592 ) {
604 ) -> bool {
593 605 let hg_path = directory_hg_path.join(&fs_entry.base_name);
594 606 let file_type = fs_entry.metadata.file_type();
595 607 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
@@ -616,26 +628,43 b" impl<'a, 'tree, 'on_disk> StatusCommon<'"
616 628 is_ignored,
617 629 &hg_path,
618 630 child_fs_entry,
619 )
631 );
620 632 })
621 633 }
622 634 }
623 635 if self.options.collect_traversed_dirs {
624 636 self.outcome.lock().unwrap().traversed.push(hg_path.into())
625 637 }
626 } else if file_or_symlink && self.matcher.matches(&hg_path) {
627 self.mark_unknown_or_ignored(
628 has_ignored_ancestor,
629 &BorrowedPath::InMemory(&hg_path),
630 )
638 is_ignored
639 } else {
640 if file_or_symlink {
641 if self.matcher.matches(&hg_path) {
642 self.mark_unknown_or_ignored(
643 has_ignored_ancestor,
644 &BorrowedPath::InMemory(&hg_path),
645 )
646 } else {
647 // We haven’t computed whether this path is ignored. It
648 // might not be, and a future run of status might have a
649 // different matcher that matches it. So treat it as not
650 // ignored. That is, inhibit readdir caching of the parent
651 // directory.
652 false
653 }
654 } else {
655 // This is neither a directory, a plain file, or a symlink.
656 // Treat it like an ignored file.
657 true
658 }
631 659 }
632 660 }
633 661
662 /// Returns whether that path is ignored
634 663 fn mark_unknown_or_ignored(
635 664 &self,
636 665 has_ignored_ancestor: bool,
637 666 hg_path: &BorrowedPath<'_, 'on_disk>,
638 ) {
667 ) -> bool {
639 668 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
640 669 if is_ignored {
641 670 if self.options.list_ignored {
@@ -654,6 +683,7 b" impl<'a, 'tree, 'on_disk> StatusCommon<'"
654 683 .push(hg_path.detach_from_tree())
655 684 }
656 685 }
686 is_ignored
657 687 }
658 688 }
659 689
General Comments 0
You need to be logged in to leave comments. Login now