Mercurial > public > mercurial-scm > hg
comparison rust/hg-core/src/dirstate_tree/status.rs @ 47475:94e38822d395
status: Extend read_dir caching to directories with ignored files
See code comments
Differential Revision: https://phab.mercurial-scm.org/D10909
author | Simon Sapin <simon.sapin@octobus.net> |
---|---|
date | Thu, 24 Jun 2021 21:54:14 +0200 |
parents | c657beacdf2e |
children | f2a9db29cb2d |
comparison
equal
deleted
inserted
replaced
47474:c657beacdf2e | 47475:94e38822d395 |
---|---|
188 // All states that we care about listing have corresponding | 188 // All states that we care about listing have corresponding |
189 // dirstate entries. | 189 // dirstate entries. |
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 } |
208 } | 211 } |
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, |
217 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>, | 220 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>, |
218 directory_hg_path: &BorrowedPath<'tree, 'on_disk>, | 221 directory_hg_path: &BorrowedPath<'tree, 'on_disk>, |
246 } | 249 } |
247 } | 250 } |
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; | 256 |
254 | 257 return Ok(children_all_have_dirstate_node_or_are_ignored); |
255 return Ok(directory_has_any_fs_only_entry); | |
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( |
259 directory_hg_path, | 261 directory_hg_path, |
260 directory_fs_path, | 262 directory_fs_path, |
293 }, | 295 }, |
294 ) | 296 ) |
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)?; |
309 } | 313 has_dirstate_node_or_is_ignored = true; |
310 Right(fs_entry) => self.traverse_fs_only( | 314 } |
311 has_ignored_ancestor, | 315 Right(fs_entry) => { |
312 directory_hg_path, | 316 has_dirstate_node_or_is_ignored = self.traverse_fs_only( |
313 fs_entry, | 317 has_ignored_ancestor, |
314 ), | 318 directory_hg_path, |
315 } | 319 fs_entry, |
316 Ok(is_fs_only) | 320 ) |
321 } | |
322 } | |
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( |
322 &self, | 329 &self, |
323 fs_path: &Path, | 330 fs_path: &Path, |
346 .traversed | 353 .traversed |
347 .push(hg_path.detach_from_tree()) | 354 .push(hg_path.detach_from_tree()) |
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)?, |
355 hg_path, | 362 hg_path, |
356 fs_path, | 363 fs_path, |
357 Some(fs_metadata), | 364 Some(fs_metadata), |
358 dirstate_node.cached_directory_mtime(), | 365 dirstate_node.cached_directory_mtime(), |
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 )? |
366 } else { | 373 } else { |
367 if file_or_symlink && self.matcher.matches(hg_path) { | 374 if file_or_symlink && self.matcher.matches(hg_path) { |
392 EntryState::Unknown => unreachable!(), | 399 EntryState::Unknown => unreachable!(), |
393 } | 400 } |
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 |
401 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter() | 411 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter() |
402 { | 412 { |
406 Ok(()) | 416 Ok(()) |
407 } | 417 } |
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. |
419 if let (Some(status_start), Ok(directory_mtime)) = ( | 429 if let (Some(status_start), Ok(directory_mtime)) = ( |
420 &self.filesystem_time_at_status_start, | 430 &self.filesystem_time_at_status_start, |
582 } | 592 } |
583 } | 593 } |
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(); |
596 if file_type.is_dir() { | 608 if file_type.is_dir() { |
597 let is_ignored = | 609 let is_ignored = |
614 children_fs_entries.par_iter().for_each(|child_fs_entry| { | 626 children_fs_entries.par_iter().for_each(|child_fs_entry| { |
615 self.traverse_fs_only( | 627 self.traverse_fs_only( |
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( |
631 } | 643 has_ignored_ancestor, |
632 } | 644 &BorrowedPath::InMemory(&hg_path), |
633 | 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 } | |
659 } | |
660 } | |
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 { |
642 self.outcome | 671 self.outcome |
643 .lock() | 672 .lock() |
652 .unwrap() | 681 .unwrap() |
653 .unknown | 682 .unknown |
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 |
660 #[cfg(unix)] // TODO | 690 #[cfg(unix)] // TODO |
661 fn mtime_seconds(metadata: &std::fs::Metadata) -> i64 { | 691 fn mtime_seconds(metadata: &std::fs::Metadata) -> i64 { |