##// END OF EJS Templates
rust-status: only visit parts of the tree requested by the matcher...
rust-status: only visit parts of the tree requested by the matcher This is an optimization that the matcher is designed to support, but we weren't doing it until now. This is primarily relevant for supporting "hg status [FILES]", where this optimization is crucial for getting good performance (without this optimization, that command will still scan the entire tree, and just filter it down after the fact). When this optimization fires we have to return false from traverse_fs_directory_and_dirstate, representing that that part of the tree *might* have new files which we didn't see because we skipped parts of it. This only affects the cached result of the status, and is necessary to make future status operations (which might use a different matcher) work properly.

File last commit:

r51757:76387f79 default
r51757:76387f79 default
Show More
status.rs
1055 lines | 39.7 KiB | application/rls-services+xml | RustLexer
Simon Sapin
dirstate-v2: Truncate directory mtimes to 31 bits of seconds...
r49007 use crate::dirstate::entry::TruncatedTimestamp;
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 use crate::dirstate::status::IgnoreFnType;
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 use crate::dirstate::status::StatusPath;
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 use crate::dirstate_tree::dirstate_map::BorrowedPath;
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 use crate::dirstate_tree::dirstate_map::ChildNodesRef;
Simon Sapin
dirstate-tree: Give to `status()` mutable access to the `DirstateMap`...
r47882 use crate::dirstate_tree::dirstate_map::DirstateMap;
Raphaël Gomès
rust-status: don't trigger dirstate v1 rewrite when only v2 data is changed...
r50232 use crate::dirstate_tree::dirstate_map::DirstateVersion;
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 use crate::dirstate_tree::dirstate_map::NodeRef;
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 use crate::matchers::get_ignore_function;
Spencer Baugh
rust-status: only visit parts of the tree requested by the matcher...
r51757 use crate::matchers::{Matcher, VisitChildrenSet};
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 use crate::utils::files::get_bytes_from_os_string;
Raphaël Gomès
dirstate-v2: hash the source of the ignore patterns as well...
r50453 use crate::utils::files::get_bytes_from_path;
Simon Sapin
dirstate-tree: Skip readdir() in `hg status -mard`...
r48129 use crate::utils::files::get_path_from_bytes;
Spencer Baugh
rust-status: error on non-existent files in file_set...
r51756 use crate::utils::hg_path::hg_path_to_path_buf;
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 use crate::utils::hg_path::HgPath;
Simon Sapin
dirstate-tree: Handle I/O errors in status...
r47885 use crate::BadMatch;
Spencer Baugh
rust-status: explicitly track bad file types...
r51755 use crate::BadType;
Simon Sapin
dirstate-tree: Give to `status()` mutable access to the `DirstateMap`...
r47882 use crate::DirstateStatus;
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 use crate::HgPathCow;
Simon Sapin
dirstate-tree: Give to `status()` mutable access to the `DirstateMap`...
r47882 use crate::PatternFileWarning;
use crate::StatusError;
use crate::StatusOptions;
Arseniy Alekseyev
dirstate-v2: skip evaluation of hgignore regex on cached directories...
r50415 use once_cell::sync::OnceCell;
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 use rayon::prelude::*;
Simon Sapin
dirstate-v2: Store a hash of ignore patterns (.hgignore)...
r48202 use sha1::{Digest, Sha1};
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 use std::borrow::Cow;
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 use std::io;
Spencer Baugh
rust-status: explicitly track bad file types...
r51755 use std::os::unix::prelude::FileTypeExt;
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 use std::path::Path;
Simon Sapin
dirstate-tree: Give to `status()` mutable access to the `DirstateMap`...
r47882 use std::path::PathBuf;
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 use std::sync::Mutex;
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 use std::time::SystemTime;
Simon Sapin
dirstate-tree: Give to `status()` mutable access to the `DirstateMap`...
r47882
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 /// Returns the status of the working directory compared to its parent
/// changeset.
///
/// This algorithm is based on traversing the filesystem tree (`fs` in function
/// and variable names) and dirstate tree at the same time. The core of this
/// traversal is the recursive `traverse_fs_directory_and_dirstate` function
/// and its use of `itertools::merge_join_by`. When reaching a path that only
/// exists in one of the two trees, depending on information requested by
/// `options` we may need to traverse the remaining subtree.
Raphaël Gomès
rust: use `logging_timer` instead of `micro_timer`...
r50808 #[logging_timer::time("trace")]
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 pub fn status<'dirstate>(
dmap: &'dirstate mut DirstateMap,
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 matcher: &(dyn Matcher + Sync),
root_dir: PathBuf,
ignore_files: Vec<PathBuf>,
options: StatusOptions,
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 ) -> Result<(DirstateStatus<'dirstate>, Vec<PatternFileWarning>), StatusError>
{
Raphaël Gomès
rust: fix thread cap (for real this time)...
r51248 // Also cap for a Python caller of this function, but don't complain if
// the global threadpool has already been set since this code path is also
// being used by `rhg`, which calls this early.
let _ = crate::utils::cap_default_rayon_threads();
Raphaël Gomès
rust-status: cap the number of concurrent threads to 16...
r49830
Simon Sapin
dirstate-v2: Store a hash of ignore patterns (.hgignore)...
r48202 let (ignore_fn, warnings, patterns_changed): (IgnoreFnType, _, _) =
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 if options.list_ignored || options.list_unknown {
Raphaël Gomès
rust-status: don't trigger dirstate v1 rewrite when only v2 data is changed...
r50232 let (ignore_fn, warnings, changed) = match dmap.dirstate_version {
DirstateVersion::V1 => {
let (ignore_fn, warnings) = get_ignore_function(
ignore_files,
&root_dir,
Raphaël Gomès
dirstate-v2: hash the source of the ignore patterns as well...
r50453 &mut |_source, _pattern_bytes| {},
Raphaël Gomès
rust-status: don't trigger dirstate v1 rewrite when only v2 data is changed...
r50232 )?;
(ignore_fn, warnings, None)
}
DirstateVersion::V2 => {
let mut hasher = Sha1::new();
let (ignore_fn, warnings) = get_ignore_function(
ignore_files,
&root_dir,
Raphaël Gomès
dirstate-v2: hash the source of the ignore patterns as well...
r50453 &mut |source, pattern_bytes| {
// If inside the repo, use the relative version to
// make it deterministic inside tests.
// The performance hit should be negligible.
let source = source
.strip_prefix(&root_dir)
.unwrap_or(source);
let source = get_bytes_from_path(source);
let mut subhasher = Sha1::new();
subhasher.update(pattern_bytes);
let patterns_hash = subhasher.finalize();
hasher.update(source);
hasher.update(b" ");
hasher.update(patterns_hash);
hasher.update(b"\n");
},
Raphaël Gomès
rust-status: don't trigger dirstate v1 rewrite when only v2 data is changed...
r50232 )?;
let new_hash = *hasher.finalize().as_ref();
let changed = new_hash != dmap.ignore_patterns_hash;
dmap.ignore_patterns_hash = new_hash;
(ignore_fn, warnings, Some(changed))
}
};
(ignore_fn, warnings, changed)
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 } else {
Simon Sapin
dirstate-v2: Store a hash of ignore patterns (.hgignore)...
r48202 (Box::new(|&_| true), vec![], None)
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 };
Simon Sapin
dirstate-v2: Apply SECOND_AMBIGUOUS to directory mtimes too...
r49332 let filesystem_time_at_status_start =
filesystem_now(&root_dir).ok().map(TruncatedTimestamp::from);
Simon Sapin
status: prefer relative paths in Rust code...
r49591
// If the repository is under the current directory, prefer using a
// relative path, so the kernel needs to traverse fewer directory in every
// call to `read_dir` or `symlink_metadata`.
// This is effective in the common case where the current directory is the
// repository root.
// TODO: Better yet would be to use libc functions like `openat` and
// `fstatat` to remove such repeated traversals entirely, but the standard
// library does not provide APIs based on those.
// Maybe with a crate like https://crates.io/crates/openat instead?
let root_dir = if let Some(relative) = std::env::current_dir()
.ok()
.and_then(|cwd| root_dir.strip_prefix(cwd).ok())
{
relative
} else {
&root_dir
};
Simon Sapin
rhg: Update the dirstate on disk after status...
r49250 let outcome = DirstateStatus {
filesystem_time_at_status_start,
..Default::default()
};
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 let common = StatusCommon {
Simon Sapin
dirstate-v2: Make the dirstate bytes buffer available in more places...
r48127 dmap,
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 options,
matcher,
ignore_fn,
Simon Sapin
rhg: Update the dirstate on disk after status...
r49250 outcome: Mutex::new(outcome),
Simon Sapin
dirstate-v2: Store a hash of ignore patterns (.hgignore)...
r48202 ignore_patterns_have_changed: patterns_changed,
Raphaël Gomès
rust-status: fix typos and add docstrings to dircache related fields
r50449 new_cacheable_directories: Default::default(),
outdated_cached_directories: Default::default(),
Simon Sapin
rhg: Update the dirstate on disk after status...
r49250 filesystem_time_at_status_start,
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 };
let is_at_repo_root = true;
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 let hg_path = &BorrowedPath::OnDisk(HgPath::new(""));
Arseniy Alekseyev
dirstate-v2: skip evaluation of hgignore regex on cached directories...
r50415 let has_ignored_ancestor = HasIgnoredAncestor::create(None, hg_path);
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 let root_cached_mtime = None;
// If the path we have for the repository root is a symlink, do follow it.
// (As opposed to symlinks within the working directory which are not
// followed, using `std::fs::symlink_metadata`.)
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 common.traverse_fs_directory_and_dirstate(
Arseniy Alekseyev
dirstate-v2: skip evaluation of hgignore regex on cached directories...
r50415 &has_ignored_ancestor,
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 dmap.root.as_ref(),
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 hg_path,
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 &DirEntry {
hg_path: Cow::Borrowed(HgPath::new(b"")),
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 fs_path: Cow::Borrowed(root_dir),
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 symlink_metadata: None,
file_type: FakeFileType::Directory,
},
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 root_cached_mtime,
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 is_at_repo_root,
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 )?;
Spencer Baugh
rust-status: error on non-existent files in file_set...
r51756 if let Some(file_set) = common.matcher.file_set() {
for file in file_set {
if !file.is_empty() && !dmap.has_node(file)? {
let path = hg_path_to_path_buf(file)?;
if let io::Result::Err(error) =
root_dir.join(path).symlink_metadata()
{
common.io_error(error, file)
}
}
}
}
Simon Sapin
dirstate-v2: Write .hg/dirstate back to disk on directory cache changes...
r48139 let mut outcome = common.outcome.into_inner().unwrap();
Raphaël Gomès
rust-status: fix typos and add docstrings to dircache related fields
r50449 let new_cacheable = common.new_cacheable_directories.into_inner().unwrap();
let outdated = common.outdated_cached_directories.into_inner().unwrap();
Simon Sapin
dirstate-v2: Store a hash of ignore patterns (.hgignore)...
r48202
outcome.dirty = common.ignore_patterns_have_changed == Some(true)
Simon Sapin
dirstate-v2: Drop cached read_dir results after .hgignore changes...
r48268 || !outdated.is_empty()
Raphaël Gomès
rust-status: fix typos and add docstrings to dircache related fields
r50449 || (!new_cacheable.is_empty()
Raphaël Gomès
rust-status: don't trigger dirstate v1 rewrite when only v2 data is changed...
r50232 && dmap.dirstate_version == DirstateVersion::V2);
Simon Sapin
dirstate-v2: Store a hash of ignore patterns (.hgignore)...
r48202
Simon Sapin
dirstate-v2: Drop cached read_dir results after .hgignore changes...
r48268 // Remove outdated mtimes before adding new mtimes, in case a given
// directory is both
for path in &outdated {
Raphaël Gomès
rust-dirstatemap: add `clear_cached_mtime` helper method...
r50018 dmap.clear_cached_mtime(path)?;
Simon Sapin
dirstate-v2: Drop cached read_dir results after .hgignore changes...
r48268 }
Raphaël Gomès
rust-status: fix typos and add docstrings to dircache related fields
r50449 for (path, mtime) in &new_cacheable {
Raphaël Gomès
rust-dirstatemap: add `set_cached_mtime` helper method...
r50019 dmap.set_cached_mtime(path, *mtime)?;
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 }
Simon Sapin
dirstate-v2: Store a hash of ignore patterns (.hgignore)...
r48202
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 Ok((outcome, warnings))
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
/// Bag of random things needed by various parts of the algorithm. Reduces the
/// number of parameters passed to functions.
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 struct StatusCommon<'a, 'tree, 'on_disk: 'tree> {
Simon Sapin
dirstate-v2: Make the dirstate bytes buffer available in more places...
r48127 dmap: &'tree DirstateMap<'on_disk>,
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 options: StatusOptions,
matcher: &'a (dyn Matcher + Sync),
ignore_fn: IgnoreFnType<'a>,
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 outcome: Mutex<DirstateStatus<'on_disk>>,
Raphaël Gomès
rust-status: fix typos and add docstrings to dircache related fields
r50449 /// New timestamps of directories to be used for caching their readdirs
new_cacheable_directories:
Simon Sapin
dirstate-v2: Truncate directory mtimes to 31 bits of seconds...
r49007 Mutex<Vec<(Cow<'on_disk, HgPath>, TruncatedTimestamp)>>,
Raphaël Gomès
rust-status: fix typos and add docstrings to dircache related fields
r50449 /// Used to invalidate the readdir cache of directories
outdated_cached_directories: Mutex<Vec<Cow<'on_disk, HgPath>>>,
Simon Sapin
dirstate-v2: Store a hash of ignore patterns (.hgignore)...
r48202
/// Whether ignore files like `.hgignore` have changed since the previous
/// time a `status()` call wrote their hash to the dirstate. `None` means
/// we don’t know as this run doesn’t list either ignored or uknown files
/// and therefore isn’t reading `.hgignore`.
ignore_patterns_have_changed: Option<bool>,
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138
/// The current time at the start of the `status()` algorithm, as measured
/// and possibly truncated by the filesystem.
Simon Sapin
dirstate-v2: Apply SECOND_AMBIGUOUS to directory mtimes too...
r49332 filesystem_time_at_status_start: Option<TruncatedTimestamp>,
Simon Sapin
dirstate-tree: Give to `status()` mutable access to the `DirstateMap`...
r47882 }
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 enum Outcome {
Modified,
Added,
Removed,
Deleted,
Clean,
Ignored,
Unknown,
Unsure,
Simon Sapin
dirstate-tree: Give to `status()` mutable access to the `DirstateMap`...
r47882 }
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883
Arseniy Alekseyev
dirstate-v2: skip evaluation of hgignore regex on cached directories...
r50415 /// Lazy computation of whether a given path has a hgignored
/// ancestor.
struct HasIgnoredAncestor<'a> {
/// `path` and `parent` constitute the inputs to the computation,
/// `cache` stores the outcome.
path: &'a HgPath,
parent: Option<&'a HasIgnoredAncestor<'a>>,
cache: OnceCell<bool>,
}
impl<'a> HasIgnoredAncestor<'a> {
fn create(
parent: Option<&'a HasIgnoredAncestor<'a>>,
path: &'a HgPath,
) -> HasIgnoredAncestor<'a> {
Self {
path,
parent,
cache: OnceCell::new(),
}
}
fn force<'b>(&self, ignore_fn: &IgnoreFnType<'b>) -> bool {
match self.parent {
None => false,
Some(parent) => {
Arseniy Alekseyev
dirstate: fix the bug in [status] dealing with committed&ignored directories...
r51224 *(self.cache.get_or_init(|| {
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 parent.force(ignore_fn) || ignore_fn(self.path)
Arseniy Alekseyev
dirstate-v2: skip evaluation of hgignore regex on cached directories...
r50415 }))
}
}
}
}
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 fn push_outcome(
&self,
which: Outcome,
dirstate_node: &NodeRef<'tree, 'on_disk>,
) -> Result<(), DirstateV2ParseError> {
let path = dirstate_node
.full_path_borrowed(self.dmap.on_disk)?
.detach_from_tree();
let copy_source = if self.options.list_copies {
dirstate_node
.copy_source_borrowed(self.dmap.on_disk)?
.map(|source| source.detach_from_tree())
} else {
None
};
self.push_outcome_common(which, path, copy_source);
Ok(())
}
fn push_outcome_without_copy_source(
&self,
which: Outcome,
path: &BorrowedPath<'_, 'on_disk>,
) {
self.push_outcome_common(which, path.detach_from_tree(), None)
}
fn push_outcome_common(
&self,
which: Outcome,
path: HgPathCow<'on_disk>,
copy_source: Option<HgPathCow<'on_disk>>,
) {
let mut outcome = self.outcome.lock().unwrap();
let vec = match which {
Outcome::Modified => &mut outcome.modified,
Outcome::Added => &mut outcome.added,
Outcome::Removed => &mut outcome.removed,
Outcome::Deleted => &mut outcome.deleted,
Outcome::Clean => &mut outcome.clean,
Outcome::Ignored => &mut outcome.ignored,
Outcome::Unknown => &mut outcome.unknown,
Outcome::Unsure => &mut outcome.unsure,
};
vec.push(StatusPath { path, copy_source });
}
Simon Sapin
dirstate-tree: Handle I/O errors in status...
r47885 fn read_dir(
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 &self,
Simon Sapin
dirstate-tree: Handle I/O errors in status...
r47885 hg_path: &HgPath,
fs_path: &Path,
is_at_repo_root: bool,
) -> Result<Vec<DirEntry>, ()> {
Simon Sapin
dirstate-tree: Skip readdir() in `hg status -mard`...
r48129 DirEntry::read_dir(fs_path, is_at_repo_root)
.map_err(|error| self.io_error(error, hg_path))
}
fn io_error(&self, error: std::io::Error, hg_path: &HgPath) {
let errno = error.raw_os_error().expect("expected real OS error");
self.outcome
.lock()
.unwrap()
.bad
.push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
Simon Sapin
dirstate-tree: Handle I/O errors in status...
r47885 }
Simon Sapin
dirstate-v2: Drop cached read_dir results after .hgignore changes...
r48268 fn check_for_outdated_directory_cache(
&self,
dirstate_node: &NodeRef<'tree, 'on_disk>,
Raphaël Gomès
rust-status: save new dircache even if just invalidated...
r50450 ) -> Result<bool, DirstateV2ParseError> {
Simon Sapin
dirstate-v2: Drop cached read_dir results after .hgignore changes...
r48268 if self.ignore_patterns_have_changed == Some(true)
Simon Sapin
dirstate-v2: Truncate directory mtimes to 31 bits of seconds...
r49007 && dirstate_node.cached_directory_mtime()?.is_some()
Simon Sapin
dirstate-v2: Drop cached read_dir results after .hgignore changes...
r48268 {
Raphaël Gomès
rust-status: fix typos and add docstrings to dircache related fields
r50449 self.outdated_cached_directories.lock().unwrap().push(
Simon Sapin
dirstate-v2: Drop cached read_dir results after .hgignore changes...
r48268 dirstate_node
.full_path_borrowed(self.dmap.on_disk)?
.detach_from_tree(),
Raphaël Gomès
rust-status: save new dircache even if just invalidated...
r50450 );
return Ok(true);
Simon Sapin
dirstate-v2: Drop cached read_dir results after .hgignore changes...
r48268 }
Raphaël Gomès
rust-status: save new dircache even if just invalidated...
r50450 Ok(false)
Simon Sapin
dirstate-v2: Drop cached read_dir results after .hgignore changes...
r48268 }
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 /// 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`.
fn can_skip_fs_readdir(
&self,
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 directory_entry: &DirEntry,
Simon Sapin
dirstate-v2: Truncate directory mtimes to 31 bits of seconds...
r49007 cached_directory_mtime: Option<TruncatedTimestamp>,
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 ) -> bool {
if !self.options.list_unknown && !self.options.list_ignored {
// All states that we care about listing have corresponding
// dirstate entries.
// This happens for example with `hg status -mard`.
return true;
}
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 if !self.options.list_ignored
&& self.ignore_patterns_have_changed == Some(false)
{
if let Some(cached_mtime) = cached_directory_mtime {
// The dirstate contains a cached mtime for this directory, set
// by a previous run of the `status` algorithm which found this
// directory eligible for `read_dir` caching.
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 if let Ok(meta) = directory_entry.symlink_metadata() {
Simon Sapin
status: Extract TruncatedTimestamp from fs::Metadata without SystemTime...
r49032 if cached_mtime
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 .likely_equal_to_mtime_of(&meta)
Simon Sapin
status: Extract TruncatedTimestamp from fs::Metadata without SystemTime...
r49032 .unwrap_or(false)
{
// The mtime of that directory has not changed
// since then, which means that the results of
// `read_dir` should also be unchanged.
return true;
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 }
}
}
}
false
}
Spencer Baugh
rust-status: only visit parts of the tree requested by the matcher...
r51757 fn should_visit(set: &VisitChildrenSet, basename: &HgPath) -> bool {
match set {
VisitChildrenSet::This | VisitChildrenSet::Recursive => true,
VisitChildrenSet::Empty => false,
VisitChildrenSet::Set(children_to_visit) => {
children_to_visit.contains(basename)
}
}
}
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 /// Returns whether all child entries of the filesystem directory have a
/// corresponding dirstate node or are ignored.
Arseniy Alekseyev
dirstate-v2: skip evaluation of hgignore regex on cached directories...
r50415 fn traverse_fs_directory_and_dirstate<'ancestor>(
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 &self,
Arseniy Alekseyev
dirstate-v2: skip evaluation of hgignore regex on cached directories...
r50415 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>,
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 directory_entry: &DirEntry,
Simon Sapin
dirstate-v2: Truncate directory mtimes to 31 bits of seconds...
r49007 cached_directory_mtime: Option<TruncatedTimestamp>,
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 is_at_repo_root: bool,
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 ) -> Result<bool, DirstateV2ParseError> {
Spencer Baugh
rust-status: only visit parts of the tree requested by the matcher...
r51757 let children_set = self.matcher.visit_children_set(directory_hg_path);
if let VisitChildrenSet::Empty = children_set {
return Ok(false);
}
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 if self.can_skip_fs_readdir(directory_entry, cached_directory_mtime) {
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 dirstate_nodes
Simon Sapin
dirstate-tree: Skip readdir() in `hg status -mard`...
r48129 .par_iter()
.map(|dirstate_node| {
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 let fs_path = &directory_entry.fs_path;
Spencer Baugh
rust-status: only visit parts of the tree requested by the matcher...
r51757 let basename =
dirstate_node.base_name(self.dmap.on_disk)?.as_bytes();
let fs_path = fs_path.join(get_path_from_bytes(basename));
if !Self::should_visit(
&children_set,
HgPath::new(basename),
) {
return Ok(());
}
Simon Sapin
dirstate-tree: Skip readdir() in `hg status -mard`...
r48129 match std::fs::symlink_metadata(&fs_path) {
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 Ok(fs_metadata) => {
Spencer Baugh
rust-status: explicitly track bad file types...
r51755 let file_type = fs_metadata.file_type().into();
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 let entry = DirEntry {
hg_path: Cow::Borrowed(
dirstate_node
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 .full_path(self.dmap.on_disk)?,
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 ),
fs_path: Cow::Borrowed(&fs_path),
symlink_metadata: Some(fs_metadata),
file_type,
};
self.traverse_fs_and_dirstate(
&entry,
dirstate_node,
has_ignored_ancestor,
)
}
Simon Sapin
dirstate-tree: Skip readdir() in `hg status -mard`...
r48129 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
self.traverse_dirstate_only(dirstate_node)
}
Err(error) => {
let hg_path =
dirstate_node.full_path(self.dmap.on_disk)?;
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 self.io_error(error, hg_path);
Ok(())
Simon Sapin
dirstate-tree: Skip readdir() in `hg status -mard`...
r48129 }
}
})
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 .collect::<Result<_, _>>()?;
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 // We don’t know, so conservatively say this isn’t the case
let children_all_have_dirstate_node_or_are_ignored = false;
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 return Ok(children_all_have_dirstate_node_or_are_ignored);
Simon Sapin
dirstate-tree: Skip readdir() in `hg status -mard`...
r48129 }
Arseniy Alekseyev
dirstate-v2: fix an incorrect handling of readdir errors...
r51217 let readdir_succeeded;
Simon Sapin
dirstate-tree: Handle I/O errors in status...
r47885 let mut fs_entries = if let Ok(entries) = self.read_dir(
directory_hg_path,
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 &directory_entry.fs_path,
Simon Sapin
dirstate-tree: Handle I/O errors in status...
r47885 is_at_repo_root,
) {
Arseniy Alekseyev
dirstate-v2: fix an incorrect handling of readdir errors...
r51217 readdir_succeeded = true;
Simon Sapin
dirstate-tree: Handle I/O errors in status...
r47885 entries
} else {
Simon Sapin
dirstate-tree: Fix status algorithm with unreadable directory...
r48135 // Treat an unreadable directory (typically because of insufficient
// permissions) like an empty directory. `self.read_dir` has
// already called `self.io_error` so a warning will be emitted.
Arseniy Alekseyev
dirstate-v2: fix an incorrect handling of readdir errors...
r51217 // We still need to remember that there was an error so that we
// know not to cache this result.
readdir_succeeded = false;
Simon Sapin
dirstate-tree: Fix status algorithm with unreadable directory...
r48135 Vec::new()
Simon Sapin
dirstate-tree: Handle I/O errors in status...
r47885 };
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883
// `merge_join_by` requires both its input iterators to be sorted:
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 let dirstate_nodes = dirstate_nodes.sorted();
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
// https://github.com/rust-lang/rust/issues/34162
Raphaël Gomès
rust-status: make `DirEntry` attributes clearer
r50458 fs_entries.sort_unstable_by(|e1, e2| e1.hg_path.cmp(&e2.hg_path));
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 // Propagate here any error that would happen inside the comparison
// callback below
for dirstate_node in &dirstate_nodes {
Simon Sapin
dirstate-v2: Make the dirstate bytes buffer available in more places...
r48127 dirstate_node.base_name(self.dmap.on_disk)?;
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 }
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 itertools::merge_join_by(
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 dirstate_nodes,
&fs_entries,
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 |dirstate_node, fs_entry| {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 // This `unwrap` never panics because we already propagated
// those errors above
Simon Sapin
dirstate-v2: Make the dirstate bytes buffer available in more places...
r48127 dirstate_node
.base_name(self.dmap.on_disk)
.unwrap()
Raphaël Gomès
rust-status: make `DirEntry` attributes clearer
r50458 .cmp(&fs_entry.hg_path)
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 },
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 )
.par_bridge()
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 .map(|pair| {
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 use itertools::EitherOrBoth::*;
Spencer Baugh
rust-status: only visit parts of the tree requested by the matcher...
r51757 let basename = match &pair {
Left(dirstate_node) | Both(dirstate_node, _) => HgPath::new(
dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
),
Right(fs_entry) => &fs_entry.hg_path,
};
if !Self::should_visit(&children_set, basename) {
return Ok(false);
}
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 let has_dirstate_node_or_is_ignored = match pair {
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 Both(dirstate_node, fs_entry) => {
self.traverse_fs_and_dirstate(
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 fs_entry,
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 dirstate_node,
has_ignored_ancestor,
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 )?;
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 true
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 }
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 Left(dirstate_node) => {
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 self.traverse_dirstate_only(dirstate_node)?;
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 true
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 }
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 Right(fs_entry) => self.traverse_fs_only(
has_ignored_ancestor.force(&self.ignore_fn),
directory_hg_path,
fs_entry,
),
};
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 Ok(has_dirstate_node_or_is_ignored)
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 })
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 .try_reduce(|| true, |a, b| Ok(a && b))
Arseniy Alekseyev
dirstate-v2: fix an incorrect handling of readdir errors...
r51217 .map(|res| res && readdir_succeeded)
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
Arseniy Alekseyev
dirstate-v2: skip evaluation of hgignore regex on cached directories...
r50415 fn traverse_fs_and_dirstate<'ancestor>(
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 &self,
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 fs_entry: &DirEntry,
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 dirstate_node: NodeRef<'tree, 'on_disk>,
Arseniy Alekseyev
dirstate-v2: skip evaluation of hgignore regex on cached directories...
r50415 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>,
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 ) -> Result<(), DirstateV2ParseError> {
Raphaël Gomès
rust-status: save new dircache even if just invalidated...
r50450 let outdated_dircache =
self.check_for_outdated_directory_cache(&dirstate_node)?;
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 let file_or_symlink = fs_entry.is_file() || fs_entry.is_symlink();
Simon Sapin
dirstate-tree: Ignore FIFOs etc. in the status algorithm...
r47884 if !file_or_symlink {
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 // If we previously had a file here, it was removed (with
// `hg rm` or similar) or deleted before it could be
Simon Sapin
dirstate-tree: Ignore FIFOs etc. in the status algorithm...
r47884 // replaced by a directory or something else.
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
Simon Sapin
dirstate-tree: Ignore FIFOs etc. in the status algorithm...
r47884 }
Spencer Baugh
rust-status: explicitly track bad file types...
r51755 if let Some(bad_type) = fs_entry.is_bad() {
if self.matcher.exact_match(hg_path) {
let path = dirstate_node.full_path(self.dmap.on_disk)?;
self.outcome.lock().unwrap().bad.push((
path.to_owned().into(),
BadMatch::BadType(bad_type),
))
}
}
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 if fs_entry.is_dir() {
Simon Sapin
dirstate-tree: Ignore FIFOs etc. in the status algorithm...
r47884 if self.options.collect_traversed_dirs {
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 self.outcome
.lock()
.unwrap()
.traversed
.push(hg_path.detach_from_tree())
Simon Sapin
dirstate-tree: Ignore FIFOs etc. in the status algorithm...
r47884 }
Arseniy Alekseyev
dirstate-v2: skip evaluation of hgignore regex on cached directories...
r50415 let is_ignored = HasIgnoredAncestor::create(
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 Some(has_ignored_ancestor),
Arseniy Alekseyev
dirstate-v2: skip evaluation of hgignore regex on cached directories...
r50415 hg_path,
);
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 let is_at_repo_root = false;
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 let children_all_have_dirstate_node_or_are_ignored = self
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 .traverse_fs_directory_and_dirstate(
Arseniy Alekseyev
dirstate-v2: skip evaluation of hgignore regex on cached directories...
r50415 &is_ignored,
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 dirstate_node.children(self.dmap.on_disk)?,
hg_path,
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 fs_entry,
Simon Sapin
dirstate-v2: Truncate directory mtimes to 31 bits of seconds...
r49007 dirstate_node.cached_directory_mtime()?,
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 is_at_repo_root,
)?;
self.maybe_save_directory_mtime(
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 children_all_have_dirstate_node_or_are_ignored,
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 fs_entry,
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 dirstate_node,
Raphaël Gomès
rust-status: save new dircache even if just invalidated...
r50450 outdated_dircache,
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 )?
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 } else {
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 if file_or_symlink && self.matcher.matches(hg_path) {
Raphaël Gomès
rust-status: stop using `state()` in the dispatch logic...
r50033 if let Some(entry) = dirstate_node.entry()? {
if !entry.any_tracked() {
// Forward-compat if we start tracking unknown/ignored
// files for caching reasons
self.mark_unknown_or_ignored(
Arseniy Alekseyev
dirstate-v2: skip evaluation of hgignore regex on cached directories...
r50415 has_ignored_ancestor.force(&self.ignore_fn),
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 hg_path,
Raphaël Gomès
rust-status: stop using `state()` in the dispatch logic...
r50033 );
}
if entry.added() {
self.push_outcome(Outcome::Added, &dirstate_node)?;
} else if entry.removed() {
self.push_outcome(Outcome::Removed, &dirstate_node)?;
} else if entry.modified() {
self.push_outcome(Outcome::Modified, &dirstate_node)?;
} else {
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 self.handle_normal_file(&dirstate_node, fs_entry)?;
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
} else {
// `node.entry.is_none()` indicates a "directory"
// node, but the filesystem has a file
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 self.mark_unknown_or_ignored(
Arseniy Alekseyev
dirstate-v2: skip evaluation of hgignore regex on cached directories...
r50415 has_ignored_ancestor.force(&self.ignore_fn),
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 hg_path,
);
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
}
Simon Sapin
dirstate-v2: Make the dirstate bytes buffer available in more places...
r48127 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
{
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 self.traverse_dirstate_only(child_node)?
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
}
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 Ok(())
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
Raphaël Gomès
rust-status: save new dircache even if just invalidated...
r50450 /// Save directory mtime if applicable.
///
/// `outdated_directory_cache` is `true` if we've just invalidated the
/// cache for this directory in `check_for_outdated_directory_cache`,
/// which forces the update.
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 fn maybe_save_directory_mtime(
&self,
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 children_all_have_dirstate_node_or_are_ignored: bool,
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 directory_entry: &DirEntry,
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 dirstate_node: NodeRef<'tree, 'on_disk>,
Raphaël Gomès
rust-status: save new dircache even if just invalidated...
r50450 outdated_directory_cache: bool,
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 ) -> Result<(), DirstateV2ParseError> {
Simon Sapin
dirstate-v2: Apply SECOND_AMBIGUOUS to directory mtimes too...
r49332 if !children_all_have_dirstate_node_or_are_ignored {
return Ok(());
}
// All filesystem directory entries from `read_dir` have a
// corresponding node in the dirstate, so we can reconstitute the
// names of those entries without calling `read_dir` again.
// TODO: use let-else here and below when available:
// https://github.com/rust-lang/rust/issues/87335
let status_start = if let Some(status_start) =
&self.filesystem_time_at_status_start
{
status_start
} else {
return Ok(());
};
// Although the Rust standard library’s `SystemTime` type
// has nanosecond precision, the times reported for a
// directory’s (or file’s) modified time may have lower
// resolution based on the filesystem (for example ext3
// only stores integer seconds), kernel (see
// https://stackoverflow.com/a/14393315/1162888), etc.
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 let metadata = match directory_entry.symlink_metadata() {
Ok(meta) => meta,
Err(_) => return Ok(()),
};
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825
let directory_mtime = match TruncatedTimestamp::for_reliable_mtime_of(
&metadata,
status_start,
) {
Ok(Some(directory_mtime)) => directory_mtime,
Ok(None) => {
Simon Sapin
dirstate-v2: Apply SECOND_AMBIGUOUS to directory mtimes too...
r49332 // The directory was modified too recently,
// don’t cache its `read_dir` results.
//
// 1. A change to this directory (direct child was
// added or removed) cause its mtime to be set
// (possibly truncated) to `directory_mtime`
// 2. This `status` algorithm calls `read_dir`
// 3. An other change is made to the same directory is
// made so that calling `read_dir` agin would give
// different results, but soon enough after 1. that
// the mtime stays the same
//
// On a system where the time resolution poor, this
// scenario is not unlikely if all three steps are caused
// by the same script.
return Ok(());
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 }
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 Err(_) => {
// OS/libc does not support mtime?
return Ok(());
}
Simon Sapin
dirstate-v2: Apply SECOND_AMBIGUOUS to directory mtimes too...
r49332 };
// We’ve observed (through `status_start`) that time has
// “progressed” since `directory_mtime`, so any further
// change to this directory is extremely likely to cause a
// different mtime.
//
// Having the same mtime again is not entirely impossible
// since the system clock is not monotonous. It could jump
// backward to some point before `directory_mtime`, then a
// directory change could potentially happen during exactly
// the wrong tick.
//
// We deem this scenario (unlike the previous one) to be
// unlikely enough in practice.
Raphaël Gomès
rust-status: save new dircache even if just invalidated...
r50450 let is_up_to_date = if let Some(cached) =
dirstate_node.cached_directory_mtime()?
{
!outdated_directory_cache && cached.likely_equal(directory_mtime)
} else {
false
};
Simon Sapin
dirstate-v2: Apply SECOND_AMBIGUOUS to directory mtimes too...
r49332 if !is_up_to_date {
let hg_path = dirstate_node
.full_path_borrowed(self.dmap.on_disk)?
.detach_from_tree();
Raphaël Gomès
rust-status: fix typos and add docstrings to dircache related fields
r50449 self.new_cacheable_directories
Simon Sapin
dirstate-v2: Apply SECOND_AMBIGUOUS to directory mtimes too...
r49332 .lock()
.unwrap()
.push((hg_path, directory_mtime))
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 }
Ok(())
}
Raphaël Gomès
rust-status: stop using `state()` in the dispatch logic...
r50033 /// A file that is clean in the dirstate was found in the filesystem
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 fn handle_normal_file(
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 &self,
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 dirstate_node: &NodeRef<'tree, 'on_disk>,
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 fs_entry: &DirEntry,
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 ) -> Result<(), DirstateV2ParseError> {
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 // Keep the low 31 bits
fn truncate_u64(value: u64) -> i32 {
(value & 0x7FFF_FFFF) as i32
}
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 let fs_metadata = match fs_entry.symlink_metadata() {
Ok(meta) => meta,
Err(_) => return Ok(()),
};
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 let entry = dirstate_node
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 .entry()?
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 .expect("handle_normal_file called with entry-less node");
Simon Sapin
dirstate-tree: Skip readdir() in `hg status -mard`...
r48129 let mode_changed =
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 || self.options.check_exec && entry.mode_changed(&fs_metadata);
Simon Sapin
rust: Make the fields of DirstateEntry private...
r48834 let size = entry.size();
let size_changed = size != truncate_u64(fs_metadata.len());
if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() {
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 // issue6456: Size returned may be longer due to encryption
// on EXT-4 fscrypt. TODO maybe only do it on EXT4?
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 self.push_outcome(Outcome::Unsure, dirstate_node)?
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 } else if dirstate_node.has_copy_source()
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 || entry.is_from_other_parent()
Simon Sapin
rust: Make the fields of DirstateEntry private...
r48834 || (size >= 0 && (size_changed || mode_changed()))
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 {
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 self.push_outcome(Outcome::Modified, dirstate_node)?
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 } else {
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 let mtime_looks_clean = if let Some(dirstate_mtime) =
entry.truncated_mtime()
{
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 let fs_mtime = TruncatedTimestamp::for_mtime_of(&fs_metadata)
Simon Sapin
dirstate-v2: actually use sub-second mtime precision...
r49082 .expect("OS/libc does not support mtime?");
dirstate: remove `lastnormaltime` mechanism...
r49220 // There might be a change in the future if for example the
// internal clock become off while process run, but this is a
// case where the issues the user would face
// would be a lot worse and there is nothing we
// can really do.
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 fs_mtime.likely_equal(dirstate_mtime)
Simon Sapin
dirstate: store mtimes with nanosecond precision in memory...
r49079 } else {
// No mtime in the dirstate entry
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 false
Simon Sapin
dirstate: store mtimes with nanosecond precision in memory...
r49079 };
if !mtime_looks_clean {
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 self.push_outcome(Outcome::Unsure, dirstate_node)?
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 } else if self.options.list_clean {
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 self.push_outcome(Outcome::Clean, dirstate_node)?
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
}
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 Ok(())
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
/// A node in the dirstate tree has no corresponding filesystem entry
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 fn traverse_dirstate_only(
&self,
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 dirstate_node: NodeRef<'tree, 'on_disk>,
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 ) -> Result<(), DirstateV2ParseError> {
Simon Sapin
dirstate-v2: Drop cached read_dir results after .hgignore changes...
r48268 self.check_for_outdated_directory_cache(&dirstate_node)?;
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 dirstate_node
Simon Sapin
dirstate-v2: Make the dirstate bytes buffer available in more places...
r48127 .children(self.dmap.on_disk)?
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 .par_iter()
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 .map(|child_node| self.traverse_dirstate_only(child_node))
.collect()
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
/// A node in the dirstate tree has no corresponding *file* on the
/// filesystem
///
/// Does nothing on a "directory" node
fn mark_removed_or_deleted_if_file(
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 &self,
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 dirstate_node: &NodeRef<'tree, 'on_disk>,
) -> Result<(), DirstateV2ParseError> {
Raphaël Gomès
rust-status: stop using `state()` in `handle_normal_file`...
r50032 if let Some(entry) = dirstate_node.entry()? {
if !entry.any_tracked() {
// Future-compat for when we start storing ignored and unknown
// files for caching reasons
return Ok(());
}
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 let path = dirstate_node.full_path(self.dmap.on_disk)?;
if self.matcher.matches(path) {
Raphaël Gomès
rust-status: stop using `state()` in `handle_normal_file`...
r50032 if entry.removed() {
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 self.push_outcome(Outcome::Removed, dirstate_node)?
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 } else {
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 self.push_outcome(Outcome::Deleted, dirstate_node)?
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
}
}
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 Ok(())
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
/// Something in the filesystem has no corresponding dirstate node
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 ///
/// Returns whether that path is ignored
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 fn traverse_fs_only(
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 &self,
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 has_ignored_ancestor: bool,
directory_hg_path: &HgPath,
fs_entry: &DirEntry,
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 ) -> bool {
Raphaël Gomès
rust-status: make `DirEntry` attributes clearer
r50458 let hg_path = directory_hg_path.join(&fs_entry.hg_path);
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 let file_or_symlink = fs_entry.is_file() || fs_entry.is_symlink();
if fs_entry.is_dir() {
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 let is_ignored =
has_ignored_ancestor || (self.ignore_fn)(&hg_path);
let traverse_children = if is_ignored {
// Descendants of an ignored directory are all ignored
self.options.list_ignored
} else {
// Descendants of an unknown directory may be either unknown or
// ignored
self.options.list_unknown || self.options.list_ignored
};
if traverse_children {
let is_at_repo_root = false;
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 if let Ok(children_fs_entries) =
self.read_dir(&hg_path, &fs_entry.fs_path, is_at_repo_root)
{
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 children_fs_entries.par_iter().for_each(|child_fs_entry| {
Simon Sapin
dirstate-tree: Handle I/O errors in status...
r47885 self.traverse_fs_only(
is_ignored,
&hg_path,
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 child_fs_entry,
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 );
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 })
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
Raphaël Gomès
rust-status: ignored directories are now correctly only listed if opted into...
r50316 if self.options.collect_traversed_dirs {
self.outcome.lock().unwrap().traversed.push(hg_path.into())
}
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 is_ignored
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 } else if file_or_symlink {
if self.matcher.matches(&hg_path) {
self.mark_unknown_or_ignored(
has_ignored_ancestor,
&BorrowedPath::InMemory(&hg_path),
)
} else {
// We haven’t computed whether this path is ignored. It
// might not be, and a future run of status might have a
// different matcher that matches it. So treat it as not
// ignored. That is, inhibit readdir caching of the parent
// directory.
false
}
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 } else {
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 // This is neither a directory, a plain file, or a symlink.
// Treat it like an ignored file.
true
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
}
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 /// Returns whether that path is ignored
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 fn mark_unknown_or_ignored(
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 &self,
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 has_ignored_ancestor: bool,
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 hg_path: &BorrowedPath<'_, 'on_disk>,
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 ) -> bool {
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 if is_ignored {
if self.options.list_ignored {
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 self.push_outcome_without_copy_source(
Outcome::Ignored,
hg_path,
)
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
Raphaël Gomès
rust-clippy: fix most warnings in `hg-core`...
r50825 } else if self.options.list_unknown {
self.push_outcome_without_copy_source(Outcome::Unknown, hg_path)
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
Simon Sapin
status: Extend read_dir caching to directories with ignored files...
r48269 is_ignored
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
}
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 /// Since [`std::fs::FileType`] cannot be built directly, we emulate what we
/// care about.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum FakeFileType {
File,
Directory,
Symlink,
Spencer Baugh
rust-status: explicitly track bad file types...
r51755 BadType(BadType),
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
Spencer Baugh
rust-status: explicitly track bad file types...
r51755 impl From<std::fs::FileType> for FakeFileType {
fn from(f: std::fs::FileType) -> Self {
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 if f.is_dir() {
Spencer Baugh
rust-status: explicitly track bad file types...
r51755 Self::Directory
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 } else if f.is_file() {
Spencer Baugh
rust-status: explicitly track bad file types...
r51755 Self::File
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 } else if f.is_symlink() {
Spencer Baugh
rust-status: explicitly track bad file types...
r51755 Self::Symlink
} else if f.is_fifo() {
Self::BadType(BadType::FIFO)
} else if f.is_block_device() {
Self::BadType(BadType::BlockDevice)
} else if f.is_char_device() {
Self::BadType(BadType::CharacterDevice)
} else if f.is_socket() {
Self::BadType(BadType::Socket)
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 } else {
Spencer Baugh
rust-status: explicitly track bad file types...
r51755 Self::BadType(BadType::Unknown)
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 }
}
}
struct DirEntry<'a> {
/// Path as stored in the dirstate, or just the filename for optimization.
hg_path: HgPathCow<'a>,
/// Filesystem path
fs_path: Cow<'a, Path>,
/// Lazily computed
symlink_metadata: Option<std::fs::Metadata>,
/// Already computed for ergonomics.
file_type: FakeFileType,
}
impl<'a> DirEntry<'a> {
/// Returns **unsorted** entries in the given directory, with name,
/// metadata and file type.
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 ///
/// If a `.hg` sub-directory is encountered:
///
/// * At the repository root, ignore that sub-directory
/// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
/// list instead.
fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
Simon Sapin
status: prefer relative paths in Rust code...
r49591 // `read_dir` returns a "not found" error for the empty path
let at_cwd = path == Path::new("");
let read_dir_path = if at_cwd { Path::new(".") } else { path };
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 let mut results = Vec::new();
Simon Sapin
status: prefer relative paths in Rust code...
r49591 for entry in read_dir_path.read_dir()? {
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 let entry = entry?;
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 let file_type = match entry.file_type() {
Arseniy Alekseyev
status: fix hg status race against file deletion...
r49645 Ok(v) => v,
Err(e) => {
// race with file deletion?
if e.kind() == std::io::ErrorKind::NotFound {
continue;
} else {
return Err(e);
}
}
};
Simon Sapin
status: prefer relative paths in Rust code...
r49591 let file_name = entry.file_name();
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 // FIXME don't do this when cached
Simon Sapin
status: prefer relative paths in Rust code...
r49591 if file_name == ".hg" {
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 if is_at_repo_root {
// Skip the repo’s own .hg (might be a symlink)
continue;
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 } else if file_type.is_dir() {
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 // A .hg sub-directory at another location means a subrepo,
// skip it entirely.
return Ok(Vec::new());
}
}
Simon Sapin
status: prefer relative paths in Rust code...
r49591 let full_path = if at_cwd {
file_name.clone().into()
} else {
entry.path()
};
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 let filename =
Cow::Owned(get_bytes_from_os_string(file_name).into());
Spencer Baugh
rust-status: explicitly track bad file types...
r51755 let file_type = FakeFileType::from(file_type);
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 results.push(DirEntry {
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459 hg_path: filename,
fs_path: Cow::Owned(full_path.to_path_buf()),
symlink_metadata: None,
file_type,
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 })
}
Ok(results)
}
Raphaël Gomès
rust-status: query fs traversal metadata lazily...
r50459
fn symlink_metadata(&self) -> Result<std::fs::Metadata, std::io::Error> {
match &self.symlink_metadata {
Some(meta) => Ok(meta.clone()),
None => std::fs::symlink_metadata(&self.fs_path),
}
}
fn is_dir(&self) -> bool {
self.file_type == FakeFileType::Directory
}
fn is_file(&self) -> bool {
self.file_type == FakeFileType::File
}
fn is_symlink(&self) -> bool {
self.file_type == FakeFileType::Symlink
}
Spencer Baugh
rust-status: explicitly track bad file types...
r51755
fn is_bad(&self) -> Option<BadType> {
match self.file_type {
FakeFileType::BadType(ty) => Some(ty),
_ => None,
}
}
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138
/// Return the `mtime` of a temporary file newly-created in the `.hg` directory
/// of the give repository.
///
/// This is similar to `SystemTime::now()`, with the result truncated to the
/// same time resolution as other files’ modification times. Using `.hg`
/// instead of the system’s default temporary directory (such as `/tmp`) makes
/// it more likely the temporary file is in the same disk partition as contents
/// of the working directory, which can matter since different filesystems may
/// store timestamps with different resolutions.
///
/// This may fail, typically if we lack write permissions. In that case we
/// should continue the `status()` algoritm anyway and consider the current
/// date/time to be unknown.
fn filesystem_now(repo_root: &Path) -> Result<SystemTime, io::Error> {
tempfile::tempfile_in(repo_root.join(".hg"))?
.metadata()?
.modified()
}