comparison rust/hg-core/src/dirstate_tree/status.rs @ 49568:da48f170d203

rust-status: query fs traversal metadata lazily Currently, any time the status algorithm needs to read a directory from the filesystem (because the stat-only optimization is not available), it also stats each directory entry eagerly. Stat'ing the entries is only needed in a few cases (like when checking the mtime of a directory for caching): this patch creates a wrapper struct `DirEntry` that only stats the directory entry it represents when needed. Excerpt of an `strace` before this change on Mozilla Central: ``` openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3 newfstatat(3, "", {st_mode=S_IFDIR|0755, st_size=3540, ...}, AT_EMPTY_PATH) = 0 getdents64(3, 0x55dc970bd440 /* 139 entries */, 32768) = 5072 statx(3, ".hg", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFDIR|0755, stx_size=772, ...}) = 0 [... 135 other successful `statx` calls] getdents64(3, 0x55dc970bd440 /* 0 entries */, 32768) = 0 close(3) = 0 ``` After this change: ``` openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3 newfstatat(3, "", {st_mode=S_IFDIR|0755, st_size=3540, ...}, AT_EMPTY_PATH) = 0 getdents64(3, 0x561567c10190 /* 139 entries */, 32768) = 5072 getdents64(3, 0x561567c10190 /* 0 entries */, 32768) = 0 close(3) = 0 ```
author Raphaël Gomès <rgomes@octobus.net>
date Wed, 19 Oct 2022 12:38:06 +0200
parents 6b32d39e9a67
children 18282cf18aa2
comparison
equal deleted inserted replaced
49567:6b32d39e9a67 49568:da48f170d203
12 use crate::utils::files::get_bytes_from_os_string; 12 use crate::utils::files::get_bytes_from_os_string;
13 use crate::utils::files::get_path_from_bytes; 13 use crate::utils::files::get_path_from_bytes;
14 use crate::utils::hg_path::HgPath; 14 use crate::utils::hg_path::HgPath;
15 use crate::BadMatch; 15 use crate::BadMatch;
16 use crate::DirstateStatus; 16 use crate::DirstateStatus;
17 use crate::HgPathBuf;
18 use crate::HgPathCow; 17 use crate::HgPathCow;
19 use crate::PatternFileWarning; 18 use crate::PatternFileWarning;
20 use crate::StatusError; 19 use crate::StatusError;
21 use crate::StatusOptions; 20 use crate::StatusOptions;
22 use micro_timer::timed; 21 use micro_timer::timed;
23 use once_cell::sync::OnceCell; 22 use once_cell::sync::OnceCell;
24 use rayon::prelude::*; 23 use rayon::prelude::*;
25 use sha1::{Digest, Sha1}; 24 use sha1::{Digest, Sha1};
26 use std::borrow::Cow; 25 use std::borrow::Cow;
26 use std::convert::TryFrom;
27 use std::convert::TryInto;
27 use std::io; 28 use std::io;
28 use std::path::Path; 29 use std::path::Path;
29 use std::path::PathBuf; 30 use std::path::PathBuf;
30 use std::sync::Mutex; 31 use std::sync::Mutex;
31 use std::time::SystemTime; 32 use std::time::SystemTime;
127 }; 128 };
128 let is_at_repo_root = true; 129 let is_at_repo_root = true;
129 let hg_path = &BorrowedPath::OnDisk(HgPath::new("")); 130 let hg_path = &BorrowedPath::OnDisk(HgPath::new(""));
130 let has_ignored_ancestor = HasIgnoredAncestor::create(None, hg_path); 131 let has_ignored_ancestor = HasIgnoredAncestor::create(None, hg_path);
131 let root_cached_mtime = None; 132 let root_cached_mtime = None;
132 let root_dir_metadata = None;
133 // If the path we have for the repository root is a symlink, do follow it. 133 // If the path we have for the repository root is a symlink, do follow it.
134 // (As opposed to symlinks within the working directory which are not 134 // (As opposed to symlinks within the working directory which are not
135 // followed, using `std::fs::symlink_metadata`.) 135 // followed, using `std::fs::symlink_metadata`.)
136 common.traverse_fs_directory_and_dirstate( 136 common.traverse_fs_directory_and_dirstate(
137 &has_ignored_ancestor, 137 &has_ignored_ancestor,
138 dmap.root.as_ref(), 138 dmap.root.as_ref(),
139 hg_path, 139 hg_path,
140 &root_dir, 140 &DirEntry {
141 root_dir_metadata, 141 hg_path: Cow::Borrowed(HgPath::new(b"")),
142 fs_path: Cow::Borrowed(&root_dir),
143 symlink_metadata: None,
144 file_type: FakeFileType::Directory,
145 },
142 root_cached_mtime, 146 root_cached_mtime,
143 is_at_repo_root, 147 is_at_repo_root,
144 )?; 148 )?;
145 let mut outcome = common.outcome.into_inner().unwrap(); 149 let mut outcome = common.outcome.into_inner().unwrap();
146 let new_cachable = common.new_cachable_directories.into_inner().unwrap(); 150 let new_cachable = common.new_cachable_directories.into_inner().unwrap();
317 /// If this returns true, we can get accurate results by only using 321 /// If this returns true, we can get accurate results by only using
318 /// `symlink_metadata` for child nodes that exist in the dirstate and don’t 322 /// `symlink_metadata` for child nodes that exist in the dirstate and don’t
319 /// need to call `read_dir`. 323 /// need to call `read_dir`.
320 fn can_skip_fs_readdir( 324 fn can_skip_fs_readdir(
321 &self, 325 &self,
322 directory_metadata: Option<&std::fs::Metadata>, 326 directory_entry: &DirEntry,
323 cached_directory_mtime: Option<TruncatedTimestamp>, 327 cached_directory_mtime: Option<TruncatedTimestamp>,
324 ) -> bool { 328 ) -> bool {
325 if !self.options.list_unknown && !self.options.list_ignored { 329 if !self.options.list_unknown && !self.options.list_ignored {
326 // All states that we care about listing have corresponding 330 // All states that we care about listing have corresponding
327 // dirstate entries. 331 // dirstate entries.
333 { 337 {
334 if let Some(cached_mtime) = cached_directory_mtime { 338 if let Some(cached_mtime) = cached_directory_mtime {
335 // The dirstate contains a cached mtime for this directory, set 339 // The dirstate contains a cached mtime for this directory, set
336 // by a previous run of the `status` algorithm which found this 340 // by a previous run of the `status` algorithm which found this
337 // directory eligible for `read_dir` caching. 341 // directory eligible for `read_dir` caching.
338 if let Some(meta) = directory_metadata { 342 if let Ok(meta) = directory_entry.symlink_metadata() {
339 if cached_mtime 343 if cached_mtime
340 .likely_equal_to_mtime_of(meta) 344 .likely_equal_to_mtime_of(&meta)
341 .unwrap_or(false) 345 .unwrap_or(false)
342 { 346 {
343 // The mtime of that directory has not changed 347 // The mtime of that directory has not changed
344 // since then, which means that the results of 348 // since then, which means that the results of
345 // `read_dir` should also be unchanged. 349 // `read_dir` should also be unchanged.
356 fn traverse_fs_directory_and_dirstate<'ancestor>( 360 fn traverse_fs_directory_and_dirstate<'ancestor>(
357 &self, 361 &self,
358 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>, 362 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>,
359 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>, 363 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
360 directory_hg_path: &BorrowedPath<'tree, 'on_disk>, 364 directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
361 directory_fs_path: &Path, 365 directory_entry: &DirEntry,
362 directory_metadata: Option<&std::fs::Metadata>,
363 cached_directory_mtime: Option<TruncatedTimestamp>, 366 cached_directory_mtime: Option<TruncatedTimestamp>,
364 is_at_repo_root: bool, 367 is_at_repo_root: bool,
365 ) -> Result<bool, DirstateV2ParseError> { 368 ) -> Result<bool, DirstateV2ParseError> {
366 if self.can_skip_fs_readdir(directory_metadata, cached_directory_mtime) 369 if self.can_skip_fs_readdir(directory_entry, cached_directory_mtime) {
367 {
368 dirstate_nodes 370 dirstate_nodes
369 .par_iter() 371 .par_iter()
370 .map(|dirstate_node| { 372 .map(|dirstate_node| {
371 let fs_path = directory_fs_path.join(get_path_from_bytes( 373 let fs_path = &directory_entry.fs_path;
374 let fs_path = fs_path.join(get_path_from_bytes(
372 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(), 375 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
373 )); 376 ));
374 match std::fs::symlink_metadata(&fs_path) { 377 match std::fs::symlink_metadata(&fs_path) {
375 Ok(fs_metadata) => self.traverse_fs_and_dirstate( 378 Ok(fs_metadata) => {
376 &fs_path, 379 let file_type =
377 &fs_metadata, 380 match fs_metadata.file_type().try_into() {
378 dirstate_node, 381 Ok(file_type) => file_type,
379 has_ignored_ancestor, 382 Err(_) => return Ok(()),
380 ), 383 };
384 let entry = DirEntry {
385 hg_path: Cow::Borrowed(
386 dirstate_node
387 .full_path(&self.dmap.on_disk)?,
388 ),
389 fs_path: Cow::Borrowed(&fs_path),
390 symlink_metadata: Some(fs_metadata),
391 file_type,
392 };
393 self.traverse_fs_and_dirstate(
394 &entry,
395 dirstate_node,
396 has_ignored_ancestor,
397 )
398 }
381 Err(e) if e.kind() == std::io::ErrorKind::NotFound => { 399 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
382 self.traverse_dirstate_only(dirstate_node) 400 self.traverse_dirstate_only(dirstate_node)
383 } 401 }
384 Err(error) => { 402 Err(error) => {
385 let hg_path = 403 let hg_path =
396 return Ok(children_all_have_dirstate_node_or_are_ignored); 414 return Ok(children_all_have_dirstate_node_or_are_ignored);
397 } 415 }
398 416
399 let mut fs_entries = if let Ok(entries) = self.read_dir( 417 let mut fs_entries = if let Ok(entries) = self.read_dir(
400 directory_hg_path, 418 directory_hg_path,
401 directory_fs_path, 419 &directory_entry.fs_path,
402 is_at_repo_root, 420 is_at_repo_root,
403 ) { 421 ) {
404 entries 422 entries
405 } else { 423 } else {
406 // Treat an unreadable directory (typically because of insufficient 424 // Treat an unreadable directory (typically because of insufficient
438 use itertools::EitherOrBoth::*; 456 use itertools::EitherOrBoth::*;
439 let has_dirstate_node_or_is_ignored; 457 let has_dirstate_node_or_is_ignored;
440 match pair { 458 match pair {
441 Both(dirstate_node, fs_entry) => { 459 Both(dirstate_node, fs_entry) => {
442 self.traverse_fs_and_dirstate( 460 self.traverse_fs_and_dirstate(
443 &fs_entry.fs_path, 461 &fs_entry,
444 &fs_entry.metadata,
445 dirstate_node, 462 dirstate_node,
446 has_ignored_ancestor, 463 has_ignored_ancestor,
447 )?; 464 )?;
448 has_dirstate_node_or_is_ignored = true 465 has_dirstate_node_or_is_ignored = true
449 } 466 }
464 .try_reduce(|| true, |a, b| Ok(a && b)) 481 .try_reduce(|| true, |a, b| Ok(a && b))
465 } 482 }
466 483
467 fn traverse_fs_and_dirstate<'ancestor>( 484 fn traverse_fs_and_dirstate<'ancestor>(
468 &self, 485 &self,
469 fs_path: &Path, 486 fs_entry: &DirEntry,
470 fs_metadata: &std::fs::Metadata,
471 dirstate_node: NodeRef<'tree, 'on_disk>, 487 dirstate_node: NodeRef<'tree, 'on_disk>,
472 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>, 488 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>,
473 ) -> Result<(), DirstateV2ParseError> { 489 ) -> Result<(), DirstateV2ParseError> {
474 self.check_for_outdated_directory_cache(&dirstate_node)?; 490 self.check_for_outdated_directory_cache(&dirstate_node)?;
475 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?; 491 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
476 let file_type = fs_metadata.file_type(); 492 let file_or_symlink = fs_entry.is_file() || fs_entry.is_symlink();
477 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
478 if !file_or_symlink { 493 if !file_or_symlink {
479 // If we previously had a file here, it was removed (with 494 // If we previously had a file here, it was removed (with
480 // `hg rm` or similar) or deleted before it could be 495 // `hg rm` or similar) or deleted before it could be
481 // replaced by a directory or something else. 496 // replaced by a directory or something else.
482 self.mark_removed_or_deleted_if_file(&dirstate_node)?; 497 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
483 } 498 }
484 if file_type.is_dir() { 499 if fs_entry.is_dir() {
485 if self.options.collect_traversed_dirs { 500 if self.options.collect_traversed_dirs {
486 self.outcome 501 self.outcome
487 .lock() 502 .lock()
488 .unwrap() 503 .unwrap()
489 .traversed 504 .traversed
497 let children_all_have_dirstate_node_or_are_ignored = self 512 let children_all_have_dirstate_node_or_are_ignored = self
498 .traverse_fs_directory_and_dirstate( 513 .traverse_fs_directory_and_dirstate(
499 &is_ignored, 514 &is_ignored,
500 dirstate_node.children(self.dmap.on_disk)?, 515 dirstate_node.children(self.dmap.on_disk)?,
501 hg_path, 516 hg_path,
502 fs_path, 517 fs_entry,
503 Some(fs_metadata),
504 dirstate_node.cached_directory_mtime()?, 518 dirstate_node.cached_directory_mtime()?,
505 is_at_repo_root, 519 is_at_repo_root,
506 )?; 520 )?;
507 self.maybe_save_directory_mtime( 521 self.maybe_save_directory_mtime(
508 children_all_have_dirstate_node_or_are_ignored, 522 children_all_have_dirstate_node_or_are_ignored,
509 fs_metadata, 523 fs_entry,
510 dirstate_node, 524 dirstate_node,
511 )? 525 )?
512 } else { 526 } else {
513 if file_or_symlink && self.matcher.matches(&hg_path) { 527 if file_or_symlink && self.matcher.matches(&hg_path) {
514 if let Some(entry) = dirstate_node.entry()? { 528 if let Some(entry) = dirstate_node.entry()? {
525 } else if entry.removed() { 539 } else if entry.removed() {
526 self.push_outcome(Outcome::Removed, &dirstate_node)?; 540 self.push_outcome(Outcome::Removed, &dirstate_node)?;
527 } else if entry.modified() { 541 } else if entry.modified() {
528 self.push_outcome(Outcome::Modified, &dirstate_node)?; 542 self.push_outcome(Outcome::Modified, &dirstate_node)?;
529 } else { 543 } else {
530 self.handle_normal_file(&dirstate_node, fs_metadata)?; 544 self.handle_normal_file(&dirstate_node, fs_entry)?;
531 } 545 }
532 } else { 546 } else {
533 // `node.entry.is_none()` indicates a "directory" 547 // `node.entry.is_none()` indicates a "directory"
534 // node, but the filesystem has a file 548 // node, but the filesystem has a file
535 self.mark_unknown_or_ignored( 549 self.mark_unknown_or_ignored(
548 } 562 }
549 563
550 fn maybe_save_directory_mtime( 564 fn maybe_save_directory_mtime(
551 &self, 565 &self,
552 children_all_have_dirstate_node_or_are_ignored: bool, 566 children_all_have_dirstate_node_or_are_ignored: bool,
553 directory_metadata: &std::fs::Metadata, 567 directory_entry: &DirEntry,
554 dirstate_node: NodeRef<'tree, 'on_disk>, 568 dirstate_node: NodeRef<'tree, 'on_disk>,
555 ) -> Result<(), DirstateV2ParseError> { 569 ) -> Result<(), DirstateV2ParseError> {
556 if !children_all_have_dirstate_node_or_are_ignored { 570 if !children_all_have_dirstate_node_or_are_ignored {
557 return Ok(()); 571 return Ok(());
558 } 572 }
574 // has nanosecond precision, the times reported for a 588 // has nanosecond precision, the times reported for a
575 // directory’s (or file’s) modified time may have lower 589 // directory’s (or file’s) modified time may have lower
576 // resolution based on the filesystem (for example ext3 590 // resolution based on the filesystem (for example ext3
577 // only stores integer seconds), kernel (see 591 // only stores integer seconds), kernel (see
578 // https://stackoverflow.com/a/14393315/1162888), etc. 592 // https://stackoverflow.com/a/14393315/1162888), etc.
593 let metadata = match directory_entry.symlink_metadata() {
594 Ok(meta) => meta,
595 Err(_) => return Ok(()),
596 };
579 let directory_mtime = if let Ok(option) = 597 let directory_mtime = if let Ok(option) =
580 TruncatedTimestamp::for_reliable_mtime_of( 598 TruncatedTimestamp::for_reliable_mtime_of(&metadata, status_start)
581 directory_metadata, 599 {
582 status_start,
583 ) {
584 if let Some(directory_mtime) = option { 600 if let Some(directory_mtime) = option {
585 directory_mtime 601 directory_mtime
586 } else { 602 } else {
587 // The directory was modified too recently, 603 // The directory was modified too recently,
588 // don’t cache its `read_dir` results. 604 // don’t cache its `read_dir` results.
639 655
640 /// A file that is clean in the dirstate was found in the filesystem 656 /// A file that is clean in the dirstate was found in the filesystem
641 fn handle_normal_file( 657 fn handle_normal_file(
642 &self, 658 &self,
643 dirstate_node: &NodeRef<'tree, 'on_disk>, 659 dirstate_node: &NodeRef<'tree, 'on_disk>,
644 fs_metadata: &std::fs::Metadata, 660 fs_entry: &DirEntry,
645 ) -> Result<(), DirstateV2ParseError> { 661 ) -> Result<(), DirstateV2ParseError> {
646 // Keep the low 31 bits 662 // Keep the low 31 bits
647 fn truncate_u64(value: u64) -> i32 { 663 fn truncate_u64(value: u64) -> i32 {
648 (value & 0x7FFF_FFFF) as i32 664 (value & 0x7FFF_FFFF) as i32
649 } 665 }
650 666
667 let fs_metadata = match fs_entry.symlink_metadata() {
668 Ok(meta) => meta,
669 Err(_) => return Ok(()),
670 };
671
651 let entry = dirstate_node 672 let entry = dirstate_node
652 .entry()? 673 .entry()?
653 .expect("handle_normal_file called with entry-less node"); 674 .expect("handle_normal_file called with entry-less node");
654 let mode_changed = 675 let mode_changed =
655 || self.options.check_exec && entry.mode_changed(fs_metadata); 676 || self.options.check_exec && entry.mode_changed(&fs_metadata);
656 let size = entry.size(); 677 let size = entry.size();
657 let size_changed = size != truncate_u64(fs_metadata.len()); 678 let size_changed = size != truncate_u64(fs_metadata.len());
658 if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() { 679 if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() {
659 // issue6456: Size returned may be longer due to encryption 680 // issue6456: Size returned may be longer due to encryption
660 // on EXT-4 fscrypt. TODO maybe only do it on EXT4? 681 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
665 { 686 {
666 self.push_outcome(Outcome::Modified, dirstate_node)? 687 self.push_outcome(Outcome::Modified, dirstate_node)?
667 } else { 688 } else {
668 let mtime_looks_clean; 689 let mtime_looks_clean;
669 if let Some(dirstate_mtime) = entry.truncated_mtime() { 690 if let Some(dirstate_mtime) = entry.truncated_mtime() {
670 let fs_mtime = TruncatedTimestamp::for_mtime_of(fs_metadata) 691 let fs_mtime = TruncatedTimestamp::for_mtime_of(&fs_metadata)
671 .expect("OS/libc does not support mtime?"); 692 .expect("OS/libc does not support mtime?");
672 // There might be a change in the future if for example the 693 // There might be a change in the future if for example the
673 // internal clock become off while process run, but this is a 694 // internal clock become off while process run, but this is a
674 // case where the issues the user would face 695 // case where the issues the user would face
675 // would be a lot worse and there is nothing we 696 // would be a lot worse and there is nothing we
736 has_ignored_ancestor: bool, 757 has_ignored_ancestor: bool,
737 directory_hg_path: &HgPath, 758 directory_hg_path: &HgPath,
738 fs_entry: &DirEntry, 759 fs_entry: &DirEntry,
739 ) -> bool { 760 ) -> bool {
740 let hg_path = directory_hg_path.join(&fs_entry.hg_path); 761 let hg_path = directory_hg_path.join(&fs_entry.hg_path);
741 let file_type = fs_entry.metadata.file_type(); 762 let file_or_symlink = fs_entry.is_file() || fs_entry.is_symlink();
742 let file_or_symlink = file_type.is_file() || file_type.is_symlink(); 763 if fs_entry.is_dir() {
743 if file_type.is_dir() {
744 let is_ignored = 764 let is_ignored =
745 has_ignored_ancestor || (self.ignore_fn)(&hg_path); 765 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
746 let traverse_children = if is_ignored { 766 let traverse_children = if is_ignored {
747 // Descendants of an ignored directory are all ignored 767 // Descendants of an ignored directory are all ignored
748 self.options.list_ignored 768 self.options.list_ignored
751 // ignored 771 // ignored
752 self.options.list_unknown || self.options.list_ignored 772 self.options.list_unknown || self.options.list_ignored
753 }; 773 };
754 if traverse_children { 774 if traverse_children {
755 let is_at_repo_root = false; 775 let is_at_repo_root = false;
756 if let Ok(children_fs_entries) = self.read_dir( 776 if let Ok(children_fs_entries) =
757 &hg_path, 777 self.read_dir(&hg_path, &fs_entry.fs_path, is_at_repo_root)
758 &fs_entry.fs_path, 778 {
759 is_at_repo_root,
760 ) {
761 children_fs_entries.par_iter().for_each(|child_fs_entry| { 779 children_fs_entries.par_iter().for_each(|child_fs_entry| {
762 self.traverse_fs_only( 780 self.traverse_fs_only(
763 is_ignored, 781 is_ignored,
764 &hg_path, 782 &hg_path,
765 child_fs_entry, 783 child_fs_entry,
818 } 836 }
819 is_ignored 837 is_ignored
820 } 838 }
821 } 839 }
822 840
823 struct DirEntry { 841 /// Since [`std::fs::FileType`] cannot be built directly, we emulate what we
824 /// Path as stored in the dirstate 842 /// care about.
825 hg_path: HgPathBuf, 843 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
844 enum FakeFileType {
845 File,
846 Directory,
847 Symlink,
848 }
849
850 impl TryFrom<std::fs::FileType> for FakeFileType {
851 type Error = ();
852
853 fn try_from(f: std::fs::FileType) -> Result<Self, Self::Error> {
854 if f.is_dir() {
855 Ok(Self::Directory)
856 } else if f.is_file() {
857 Ok(Self::File)
858 } else if f.is_symlink() {
859 Ok(Self::Symlink)
860 } else {
861 // Things like FIFO etc.
862 Err(())
863 }
864 }
865 }
866
867 struct DirEntry<'a> {
868 /// Path as stored in the dirstate, or just the filename for optimization.
869 hg_path: HgPathCow<'a>,
826 /// Filesystem path 870 /// Filesystem path
827 fs_path: PathBuf, 871 fs_path: Cow<'a, Path>,
828 metadata: std::fs::Metadata, 872 /// Lazily computed
829 } 873 symlink_metadata: Option<std::fs::Metadata>,
830 874 /// Already computed for ergonomics.
831 impl DirEntry { 875 file_type: FakeFileType,
832 /// Returns **unsorted** entries in the given directory, with name and 876 }
833 /// metadata. 877
878 impl<'a> DirEntry<'a> {
879 /// Returns **unsorted** entries in the given directory, with name,
880 /// metadata and file type.
834 /// 881 ///
835 /// If a `.hg` sub-directory is encountered: 882 /// If a `.hg` sub-directory is encountered:
836 /// 883 ///
837 /// * At the repository root, ignore that sub-directory 884 /// * At the repository root, ignore that sub-directory
838 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty 885 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
842 let at_cwd = path == Path::new(""); 889 let at_cwd = path == Path::new("");
843 let read_dir_path = if at_cwd { Path::new(".") } else { path }; 890 let read_dir_path = if at_cwd { Path::new(".") } else { path };
844 let mut results = Vec::new(); 891 let mut results = Vec::new();
845 for entry in read_dir_path.read_dir()? { 892 for entry in read_dir_path.read_dir()? {
846 let entry = entry?; 893 let entry = entry?;
847 let metadata = match entry.metadata() { 894 let file_type = match entry.file_type() {
848 Ok(v) => v, 895 Ok(v) => v,
849 Err(e) => { 896 Err(e) => {
850 // race with file deletion? 897 // race with file deletion?
851 if e.kind() == std::io::ErrorKind::NotFound { 898 if e.kind() == std::io::ErrorKind::NotFound {
852 continue; 899 continue;
859 // FIXME don't do this when cached 906 // FIXME don't do this when cached
860 if file_name == ".hg" { 907 if file_name == ".hg" {
861 if is_at_repo_root { 908 if is_at_repo_root {
862 // Skip the repo’s own .hg (might be a symlink) 909 // Skip the repo’s own .hg (might be a symlink)
863 continue; 910 continue;
864 } else if metadata.is_dir() { 911 } else if file_type.is_dir() {
865 // A .hg sub-directory at another location means a subrepo, 912 // A .hg sub-directory at another location means a subrepo,
866 // skip it entirely. 913 // skip it entirely.
867 return Ok(Vec::new()); 914 return Ok(Vec::new());
868 } 915 }
869 } 916 }
870 let full_path = if at_cwd { 917 let full_path = if at_cwd {
871 file_name.clone().into() 918 file_name.clone().into()
872 } else { 919 } else {
873 entry.path() 920 entry.path()
874 }; 921 };
875 let base_name = get_bytes_from_os_string(file_name).into(); 922 let filename =
923 Cow::Owned(get_bytes_from_os_string(file_name).into());
924 let file_type = match FakeFileType::try_from(file_type) {
925 Ok(file_type) => file_type,
926 Err(_) => continue,
927 };
876 results.push(DirEntry { 928 results.push(DirEntry {
877 hg_path: base_name, 929 hg_path: filename,
878 fs_path: full_path, 930 fs_path: Cow::Owned(full_path.to_path_buf()),
879 metadata, 931 symlink_metadata: None,
932 file_type,
880 }) 933 })
881 } 934 }
882 Ok(results) 935 Ok(results)
936 }
937
938 fn symlink_metadata(&self) -> Result<std::fs::Metadata, std::io::Error> {
939 match &self.symlink_metadata {
940 Some(meta) => Ok(meta.clone()),
941 None => std::fs::symlink_metadata(&self.fs_path),
942 }
943 }
944
945 fn is_dir(&self) -> bool {
946 self.file_type == FakeFileType::Directory
947 }
948
949 fn is_file(&self) -> bool {
950 self.file_type == FakeFileType::File
951 }
952
953 fn is_symlink(&self) -> bool {
954 self.file_type == FakeFileType::Symlink
883 } 955 }
884 } 956 }
885 957
886 /// Return the `mtime` of a temporary file newly-created in the `.hg` directory 958 /// Return the `mtime` of a temporary file newly-created in the `.hg` directory
887 /// of the give repository. 959 /// of the give repository.