##// END OF EJS Templates
rhg: read [paths] for `--repository` value...
rhg: read [paths] for `--repository` value hg parses `-R` and `--repository` CLI arguments "early" in order to know which local repository to load config from. (Config can then affect whether or how to fall back.) The value of of those arguments can be not only a filesystem path, but also an alias configured in the `[paths]` section. This part was missing in rhg and this patch implements that. The current patch still lacks functionality to read config of current repository if we are not at root of repo. That will be fixed in upcoming patches. A new crate `home` is added to get path of home directory. Differential Revision: https://phab.mercurial-scm.org/D10296

File last commit:

r48139:04d1f17f default
r48196:ebdef628 default
Show More
status.rs
685 lines | 26.3 KiB | application/rls-services+xml | RustLexer
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 use crate::dirstate::status::IgnoreFnType;
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;
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 use crate::dirstate_tree::dirstate_map::NodeData;
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-v2: Skip readdir in status based on directory mtime...
r48138 use crate::dirstate_tree::on_disk::Timestamp;
use crate::dirstate_tree::path_with_basename::WithBasename;
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 use crate::matchers::get_ignore_function;
Simon Sapin
dirstate-tree: Give to `status()` mutable access to the `DirstateMap`...
r47882 use crate::matchers::Matcher;
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 use crate::utils::files::get_bytes_from_os_string;
Simon Sapin
dirstate-tree: Skip readdir() in `hg status -mard`...
r48129 use crate::utils::files::get_path_from_bytes;
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;
Simon Sapin
dirstate-tree: Give to `status()` mutable access to the `DirstateMap`...
r47882 use crate::DirstateStatus;
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 use crate::EntryState;
use crate::HgPathBuf;
Simon Sapin
dirstate-tree: Give to `status()` mutable access to the `DirstateMap`...
r47882 use crate::PatternFileWarning;
use crate::StatusError;
use crate::StatusOptions;
Simon Sapin
dirstate-tree: Add #[timed] attribute to `status` and `DirstateMap::read`...
r47888 use micro_timer::timed;
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 use rayon::prelude::*;
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;
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.
Simon Sapin
dirstate-tree: Add #[timed] attribute to `status` and `DirstateMap::read`...
r47888 #[timed]
Simon Sapin
dirstate-v2: Make the dirstate bytes buffer available in more places...
r48127 pub fn status<'tree, 'on_disk: 'tree>(
dmap: &'tree mut DirstateMap<'on_disk>,
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 matcher: &(dyn Matcher + Sync),
root_dir: PathBuf,
ignore_files: Vec<PathBuf>,
options: StatusOptions,
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 ) -> Result<(DirstateStatus<'on_disk>, Vec<PatternFileWarning>), StatusError> {
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 let (ignore_fn, warnings): (IgnoreFnType, _) =
if options.list_ignored || options.list_unknown {
get_ignore_function(ignore_files, &root_dir)?
} else {
(Box::new(|&_| true), vec![])
};
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
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 outcome: Default::default(),
cached_directory_mtimes_to_add: Default::default(),
filesystem_time_at_status_start: filesystem_now(&root_dir).ok(),
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(""));
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 let has_ignored_ancestor = false;
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 let root_cached_mtime = None;
let root_dir_metadata = 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(
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,
&root_dir,
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 root_dir_metadata,
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 )?;
Simon Sapin
dirstate-v2: Write .hg/dirstate back to disk on directory cache changes...
r48139 let mut outcome = common.outcome.into_inner().unwrap();
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 let to_add = common.cached_directory_mtimes_to_add.into_inner().unwrap();
Simon Sapin
dirstate-v2: Write .hg/dirstate back to disk on directory cache changes...
r48139 outcome.dirty = !to_add.is_empty();
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 for (path, mtime) in &to_add {
let node = DirstateMap::get_or_insert_node(
dmap.on_disk,
&mut dmap.root,
path,
WithBasename::to_cow_owned,
|_| {},
)?;
match &node.data {
NodeData::Entry(_) => {} // Don’t overwrite an entry
NodeData::CachedDirectory { .. } | NodeData::None => {
node.data = NodeData::CachedDirectory { mtime: *mtime }
}
}
}
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>>,
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 cached_directory_mtimes_to_add:
Mutex<Vec<(Cow<'on_disk, HgPath>, Timestamp)>>,
/// The current time at the start of the `status()` algorithm, as measured
/// and possibly truncated by the filesystem.
filesystem_time_at_status_start: Option<SystemTime>,
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
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
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: 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,
directory_metadata: Option<&std::fs::Metadata>,
cached_directory_mtime: Option<&Timestamp>,
) -> 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;
}
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.
if let Some(meta) = directory_metadata {
if let Ok(current_mtime) = meta.modified() {
if current_mtime == cached_mtime.into() {
// The mtime of that directory has not changed since
// then, which means that the
// results of `read_dir` should also
// be unchanged.
return true;
}
}
}
}
false
}
/// Returns whether the filesystem directory was found to have any entry
/// that does not have a corresponding dirstate tree node.
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 fn traverse_fs_directory_and_dirstate(
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 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
Simon Sapin
dirstate-tree: Handle I/O errors in status...
r47885 directory_fs_path: &Path,
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 directory_metadata: Option<&std::fs::Metadata>,
cached_directory_mtime: Option<&Timestamp>,
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> {
if self.can_skip_fs_readdir(directory_metadata, cached_directory_mtime)
{
dirstate_nodes
Simon Sapin
dirstate-tree: Skip readdir() in `hg status -mard`...
r48129 .par_iter()
.map(|dirstate_node| {
let fs_path = directory_fs_path.join(get_path_from_bytes(
dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
));
match std::fs::symlink_metadata(&fs_path) {
Ok(fs_metadata) => self.traverse_fs_and_dirstate(
&fs_path,
&fs_metadata,
dirstate_node,
has_ignored_ancestor,
),
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)?;
Ok(self.io_error(error, hg_path))
}
}
})
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 .collect::<Result<_, _>>()?;
// Conservatively don’t let the caller assume that there aren’t
// any, since we don’t know.
let directory_has_any_fs_only_entry = true;
return Ok(directory_has_any_fs_only_entry);
Simon Sapin
dirstate-tree: Skip readdir() in `hg status -mard`...
r48129 }
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,
directory_fs_path,
is_at_repo_root,
) {
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.
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
fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name));
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()
.cmp(&fs_entry.base_name)
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::*;
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 let is_fs_only = pair.is_right();
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 match pair {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 Both(dirstate_node, fs_entry) => self
.traverse_fs_and_dirstate(
Simon Sapin
dirstate-tree: Skip readdir() in `hg status -mard`...
r48129 &fs_entry.full_path,
&fs_entry.metadata,
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 dirstate_node,
has_ignored_ancestor,
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 )?,
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 Left(dirstate_node) => {
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 self.traverse_dirstate_only(dirstate_node)?
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 }
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 Right(fs_entry) => self.traverse_fs_only(
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 has_ignored_ancestor,
directory_hg_path,
fs_entry,
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 ),
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 Ok(is_fs_only)
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 })
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 .try_reduce(|| false, |a, b| Ok(a || b))
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
fn traverse_fs_and_dirstate(
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 &self,
Simon Sapin
dirstate-tree: Skip readdir() in `hg status -mard`...
r48129 fs_path: &Path,
fs_metadata: &std::fs::Metadata,
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 dirstate_node: NodeRef<'tree, 'on_disk>,
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 has_ignored_ancestor: bool,
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 ) -> Result<(), DirstateV2ParseError> {
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
Simon Sapin
dirstate-tree: Skip readdir() in `hg status -mard`...
r48129 let file_type = fs_metadata.file_type();
Simon Sapin
dirstate-tree: Ignore FIFOs etc. in the status algorithm...
r47884 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
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
dirstate-tree: Add the new `status()` algorithm...
r47883 self.mark_removed_or_deleted_if_file(
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 &hg_path,
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 dirstate_node.state()?,
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 );
Simon Sapin
dirstate-tree: Ignore FIFOs etc. in the status algorithm...
r47884 }
if file_type.is_dir() {
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 }
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
let is_at_repo_root = false;
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 let directory_has_any_fs_only_entry = self
.traverse_fs_directory_and_dirstate(
is_ignored,
dirstate_node.children(self.dmap.on_disk)?,
hg_path,
fs_path,
Some(fs_metadata),
dirstate_node.cached_directory_mtime(),
is_at_repo_root,
)?;
self.maybe_save_directory_mtime(
directory_has_any_fs_only_entry,
fs_metadata,
dirstate_node,
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 )?
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 } else {
Simon Sapin
dirstate-tree: Ignore FIFOs etc. in the status algorithm...
r47884 if file_or_symlink && self.matcher.matches(hg_path) {
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 if let Some(state) = dirstate_node.state()? {
Simon Sapin
dirstate-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 match state {
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 EntryState::Added => self
.outcome
.lock()
.unwrap()
.added
.push(hg_path.detach_from_tree()),
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 EntryState::Removed => self
.outcome
.lock()
.unwrap()
.removed
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 .push(hg_path.detach_from_tree()),
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 EntryState::Merged => self
.outcome
.lock()
.unwrap()
.modified
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 .push(hg_path.detach_from_tree()),
Simon Sapin
dirstate-tree: Skip readdir() in `hg status -mard`...
r48129 EntryState::Normal => self
.handle_normal_file(&dirstate_node, fs_metadata)?,
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 // This variant is not used in DirstateMap
// nodes
EntryState::Unknown => unreachable!(),
}
} else {
// `node.entry.is_none()` indicates a "directory"
// node, but the filesystem has a file
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 self.mark_unknown_or_ignored(has_ignored_ancestor, 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 }
Simon Sapin
dirstate-v2: Skip readdir in status based on directory mtime...
r48138 fn maybe_save_directory_mtime(
&self,
directory_has_any_fs_only_entry: bool,
directory_metadata: &std::fs::Metadata,
dirstate_node: NodeRef<'tree, 'on_disk>,
) -> Result<(), DirstateV2ParseError> {
if !directory_has_any_fs_only_entry {
// 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.
if let (Some(status_start), Ok(directory_mtime)) = (
&self.filesystem_time_at_status_start,
directory_metadata.modified(),
) {
// 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.
if &directory_mtime >= status_start {
// The directory was modified too recently, don’t cache its
// `read_dir` results.
//
// A timeline like this is possible:
//
// 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.
} else {
// 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.
let timestamp = directory_mtime.into();
let cached = dirstate_node.cached_directory_mtime();
if cached != Some(&timestamp) {
let hg_path = dirstate_node
.full_path_borrowed(self.dmap.on_disk)?
.detach_from_tree();
self.cached_directory_mtimes_to_add
.lock()
.unwrap()
.push((hg_path, timestamp))
}
}
}
}
Ok(())
}
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 /// A file with `EntryState::Normal` in the dirstate was found in the
/// filesystem
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>,
Simon Sapin
dirstate-tree: Skip readdir() in `hg status -mard`...
r48129 fs_metadata: &std::fs::Metadata,
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
}
fn truncate_i64(value: i64) -> i32 {
(value & 0x7FFF_FFFF) as i32
}
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: Change status() results to not borrow DirstateMap...
r48136 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
Simon Sapin
dirstate-tree: Skip readdir() in `hg status -mard`...
r48129 let mode_changed =
|| self.options.check_exec && entry.mode_changed(fs_metadata);
let size_changed = entry.size != truncate_u64(fs_metadata.len());
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 if entry.size >= 0
&& size_changed
Simon Sapin
dirstate-tree: Skip readdir() in `hg status -mard`...
r48129 && 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
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 self.outcome
.lock()
.unwrap()
.unsure
.push(hg_path.detach_from_tree())
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()
|| (entry.size >= 0 && (size_changed || mode_changed()))
{
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 self.outcome
.lock()
.unwrap()
.modified
.push(hg_path.detach_from_tree())
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 } else {
Simon Sapin
dirstate-tree: Skip readdir() in `hg status -mard`...
r48129 let mtime = mtime_seconds(fs_metadata);
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 if truncate_i64(mtime) != entry.mtime
|| mtime == self.options.last_normal_time
{
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 self.outcome
.lock()
.unwrap()
.unsure
.push(hg_path.detach_from_tree())
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 } else if self.options.list_clean {
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 self.outcome
.lock()
.unwrap()
.clean
.push(hg_path.detach_from_tree())
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-tree: Add `NodeRef` and `ChildNodesRef` enums...
r48124 self.mark_removed_or_deleted_if_file(
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 &dirstate_node.full_path_borrowed(self.dmap.on_disk)?,
Simon Sapin
dirstate-v2: Make more APIs fallible, returning Result...
r48126 dirstate_node.state()?,
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
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 hg_path: &BorrowedPath<'tree, 'on_disk>,
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 dirstate_node_state: Option<EntryState>,
) {
if let Some(state) = dirstate_node_state {
if self.matcher.matches(hg_path) {
if let EntryState::Removed = state {
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 self.outcome
.lock()
.unwrap()
.removed
.push(hg_path.detach_from_tree())
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 } else {
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 self.outcome
.lock()
.unwrap()
.deleted
.push(hg_path.detach_from_tree())
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
}
}
}
/// Something in the filesystem has no corresponding dirstate node
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,
) {
let hg_path = directory_hg_path.join(&fs_entry.base_name);
Simon Sapin
dirstate-tree: Ignore FIFOs etc. in the status algorithm...
r47884 let file_type = fs_entry.metadata.file_type();
let file_or_symlink = file_type.is_file() || file_type.is_symlink();
if file_type.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;
Simon Sapin
dirstate-tree: Handle I/O errors in status...
r47885 if let Ok(children_fs_entries) = self.read_dir(
&hg_path,
&fs_entry.full_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
dirstate-tree: Handle I/O errors in status...
r47885 )
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 })
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
}
if self.options.collect_traversed_dirs {
Simon Sapin
dirstate-tree: Paralellize the status algorithm with Rayon...
r47887 self.outcome.lock().unwrap().traversed.push(hg_path.into())
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
Simon Sapin
dirstate-tree: Ignore FIFOs etc. in the status algorithm...
r47884 } else if file_or_symlink && self.matcher.matches(&hg_path) {
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 self.mark_unknown_or_ignored(
has_ignored_ancestor,
&BorrowedPath::InMemory(&hg_path),
)
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
dirstate-tree: Add the new `status()` algorithm...
r47883 ) {
let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
if is_ignored {
if self.options.list_ignored {
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 self.outcome
.lock()
.unwrap()
.ignored
.push(hg_path.detach_from_tree())
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
} else {
if self.options.list_unknown {
Simon Sapin
dirstate-tree: Change status() results to not borrow DirstateMap...
r48136 self.outcome
.lock()
.unwrap()
.unknown
.push(hg_path.detach_from_tree())
Simon Sapin
dirstate-tree: Add the new `status()` algorithm...
r47883 }
}
}
}
#[cfg(unix)] // TODO
fn mtime_seconds(metadata: &std::fs::Metadata) -> i64 {
// Going through `Metadata::modified()` would be portable, but would take
// care to construct a `SystemTime` value with sub-second precision just
// for us to throw that away here.
use std::os::unix::fs::MetadataExt;
metadata.mtime()
}
struct DirEntry {
base_name: HgPathBuf,
full_path: PathBuf,
metadata: std::fs::Metadata,
}
impl DirEntry {
/// Returns **unsorted** entries in the given directory, with name and
/// metadata.
///
/// 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>> {
let mut results = Vec::new();
for entry in path.read_dir()? {
let entry = entry?;
let metadata = entry.metadata()?;
let name = get_bytes_from_os_string(entry.file_name());
// FIXME don't do this when cached
if name == b".hg" {
if is_at_repo_root {
// Skip the repo’s own .hg (might be a symlink)
continue;
} else if metadata.is_dir() {
// A .hg sub-directory at another location means a subrepo,
// skip it entirely.
return Ok(Vec::new());
}
}
results.push(DirEntry {
base_name: name.into(),
full_path: entry.path(),
metadata,
})
}
Ok(results)
}
}
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()
}