##// 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 /// and must cause a different modification time (unless the system
98 /// and must cause a different modification time (unless the system
99 /// clock jumps back and we get unlucky, which is not impossible but
99 /// clock jumps back and we get unlucky, which is not impossible but
100 /// but deemed unlikely enough).
100 /// but deemed unlikely enough).
101 /// - The directory did not contain any child entry that did not have a
101 /// - All direct children of this directory (as returned by
102 /// corresponding dirstate node.
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 /// This means that if `std::fs::symlink_metadata` later reports the
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`
107 /// same modification time and ignored patterns haven’t changed, a run
106 /// again for this directory and can iterate child dirstate nodes
108 /// of status that is not listing ignored files can skip calling
107 /// instead.
109 /// `std::fs::read_dir` again for this directory, iterate child
110 /// dirstate nodes instead.
108 state: u8,
111 state: u8,
109 data: Entry,
112 data: Entry,
110 }
113 }
@@ -190,18 +190,21 b" impl<'a, 'tree, 'on_disk> StatusCommon<'"
190 // This happens for example with `hg status -mard`.
190 // This happens for example with `hg status -mard`.
191 return true;
191 return true;
192 }
192 }
193 if let Some(cached_mtime) = cached_directory_mtime {
193 if !self.options.list_ignored
194 // The dirstate contains a cached mtime for this directory, set by
194 && self.ignore_patterns_have_changed == Some(false)
195 // a previous run of the `status` algorithm which found this
195 {
196 // directory eligible for `read_dir` caching.
196 if let Some(cached_mtime) = cached_directory_mtime {
197 if let Some(meta) = directory_metadata {
197 // The dirstate contains a cached mtime for this directory, set
198 if let Ok(current_mtime) = meta.modified() {
198 // by a previous run of the `status` algorithm which found this
199 if current_mtime == cached_mtime.into() {
199 // directory eligible for `read_dir` caching.
200 // The mtime of that directory has not changed since
200 if let Some(meta) = directory_metadata {
201 // then, which means that the
201 if let Ok(current_mtime) = meta.modified() {
202 // results of `read_dir` should also
202 if current_mtime == cached_mtime.into() {
203 // be unchanged.
203 // The mtime of that directory has not changed
204 return true;
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 false
212 false
210 }
213 }
211
214
212 /// Returns whether the filesystem directory was found to have any entry
215 /// Returns whether all child entries of the filesystem directory have a
213 /// that does not have a corresponding dirstate tree node.
216 /// corresponding dirstate node or are ignored.
214 fn traverse_fs_directory_and_dirstate(
217 fn traverse_fs_directory_and_dirstate(
215 &self,
218 &self,
216 has_ignored_ancestor: bool,
219 has_ignored_ancestor: bool,
@@ -248,11 +251,10 b" impl<'a, 'tree, 'on_disk> StatusCommon<'"
248 })
251 })
249 .collect::<Result<_, _>>()?;
252 .collect::<Result<_, _>>()?;
250
253
251 // Conservatively don’t let the caller assume that there aren’t
254 // We don’t know, so conservatively say this isn’t the case
252 // any, since we don’t know.
255 let children_all_have_dirstate_node_or_are_ignored = false;
253 let directory_has_any_fs_only_entry = true;
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 let mut fs_entries = if let Ok(entries) = self.read_dir(
260 let mut fs_entries = if let Ok(entries) = self.read_dir(
@@ -295,27 +297,32 b" impl<'a, 'tree, 'on_disk> StatusCommon<'"
295 .par_bridge()
297 .par_bridge()
296 .map(|pair| {
298 .map(|pair| {
297 use itertools::EitherOrBoth::*;
299 use itertools::EitherOrBoth::*;
298 let is_fs_only = pair.is_right();
300 let has_dirstate_node_or_is_ignored;
299 match pair {
301 match pair {
300 Both(dirstate_node, fs_entry) => self
302 Both(dirstate_node, fs_entry) => {
301 .traverse_fs_and_dirstate(
303 self.traverse_fs_and_dirstate(
302 &fs_entry.full_path,
304 &fs_entry.full_path,
303 &fs_entry.metadata,
305 &fs_entry.metadata,
304 dirstate_node,
306 dirstate_node,
305 has_ignored_ancestor,
307 has_ignored_ancestor,
306 )?,
308 )?;
309 has_dirstate_node_or_is_ignored = true
310 }
307 Left(dirstate_node) => {
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(
315 Right(fs_entry) => {
311 has_ignored_ancestor,
316 has_dirstate_node_or_is_ignored = self.traverse_fs_only(
312 directory_hg_path,
317 has_ignored_ancestor,
313 fs_entry,
318 directory_hg_path,
314 ),
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 fn traverse_fs_and_dirstate(
328 fn traverse_fs_and_dirstate(
@@ -348,7 +355,7 b" impl<'a, 'tree, 'on_disk> StatusCommon<'"
348 }
355 }
349 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
356 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
350 let is_at_repo_root = false;
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 .traverse_fs_directory_and_dirstate(
359 .traverse_fs_directory_and_dirstate(
353 is_ignored,
360 is_ignored,
354 dirstate_node.children(self.dmap.on_disk)?,
361 dirstate_node.children(self.dmap.on_disk)?,
@@ -359,7 +366,7 b" impl<'a, 'tree, 'on_disk> StatusCommon<'"
359 is_at_repo_root,
366 is_at_repo_root,
360 )?;
367 )?;
361 self.maybe_save_directory_mtime(
368 self.maybe_save_directory_mtime(
362 directory_has_any_fs_only_entry,
369 children_all_have_dirstate_node_or_are_ignored,
363 fs_metadata,
370 fs_metadata,
364 dirstate_node,
371 dirstate_node,
365 )?
372 )?
@@ -394,7 +401,10 b" impl<'a, 'tree, 'on_disk> StatusCommon<'"
394 } else {
401 } else {
395 // `node.entry.is_none()` indicates a "directory"
402 // `node.entry.is_none()` indicates a "directory"
396 // node, but the filesystem has a file
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 fn maybe_save_directory_mtime(
419 fn maybe_save_directory_mtime(
410 &self,
420 &self,
411 directory_has_any_fs_only_entry: bool,
421 children_all_have_dirstate_node_or_are_ignored: bool,
412 directory_metadata: &std::fs::Metadata,
422 directory_metadata: &std::fs::Metadata,
413 dirstate_node: NodeRef<'tree, 'on_disk>,
423 dirstate_node: NodeRef<'tree, 'on_disk>,
414 ) -> Result<(), DirstateV2ParseError> {
424 ) -> Result<(), DirstateV2ParseError> {
415 if !directory_has_any_fs_only_entry {
425 if children_all_have_dirstate_node_or_are_ignored {
416 // All filesystem directory entries from `read_dir` have a
426 // All filesystem directory entries from `read_dir` have a
417 // corresponding node in the dirstate, so we can reconstitute the
427 // corresponding node in the dirstate, so we can reconstitute the
418 // names of those entries without calling `read_dir` again.
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 /// Something in the filesystem has no corresponding dirstate node
596 /// Something in the filesystem has no corresponding dirstate node
597 ///
598 /// Returns whether that path is ignored
587 fn traverse_fs_only(
599 fn traverse_fs_only(
588 &self,
600 &self,
589 has_ignored_ancestor: bool,
601 has_ignored_ancestor: bool,
590 directory_hg_path: &HgPath,
602 directory_hg_path: &HgPath,
591 fs_entry: &DirEntry,
603 fs_entry: &DirEntry,
592 ) {
604 ) -> bool {
593 let hg_path = directory_hg_path.join(&fs_entry.base_name);
605 let hg_path = directory_hg_path.join(&fs_entry.base_name);
594 let file_type = fs_entry.metadata.file_type();
606 let file_type = fs_entry.metadata.file_type();
595 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
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 is_ignored,
628 is_ignored,
617 &hg_path,
629 &hg_path,
618 child_fs_entry,
630 child_fs_entry,
619 )
631 );
620 })
632 })
621 }
633 }
622 }
634 }
623 if self.options.collect_traversed_dirs {
635 if self.options.collect_traversed_dirs {
624 self.outcome.lock().unwrap().traversed.push(hg_path.into())
636 self.outcome.lock().unwrap().traversed.push(hg_path.into())
625 }
637 }
626 } else if file_or_symlink && self.matcher.matches(&hg_path) {
638 is_ignored
627 self.mark_unknown_or_ignored(
639 } else {
628 has_ignored_ancestor,
640 if file_or_symlink {
629 &BorrowedPath::InMemory(&hg_path),
641 if self.matcher.matches(&hg_path) {
630 )
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 fn mark_unknown_or_ignored(
663 fn mark_unknown_or_ignored(
635 &self,
664 &self,
636 has_ignored_ancestor: bool,
665 has_ignored_ancestor: bool,
637 hg_path: &BorrowedPath<'_, 'on_disk>,
666 hg_path: &BorrowedPath<'_, 'on_disk>,
638 ) {
667 ) -> bool {
639 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
668 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
640 if is_ignored {
669 if is_ignored {
641 if self.options.list_ignored {
670 if self.options.list_ignored {
@@ -654,6 +683,7 b" impl<'a, 'tree, 'on_disk> StatusCommon<'"
654 .push(hg_path.detach_from_tree())
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