##// END OF EJS Templates
rust-status: update rust-cpython bridge to account for the changes in core...
rust-status: update rust-cpython bridge to account for the changes in core Differential Revision: https://phab.mercurial-scm.org/D7930

File last commit:

r45016:f96b28aa default
r45016:f96b28aa default
Show More
status.rs
770 lines | 25.9 KiB | application/rls-services+xml | RustLexer
Raphaël Gomès
rust-dirstate-status: add first Rust implementation of `dirstate.status`...
r43565 // status.rs
//
// Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
//
// This software may be used and distributed according to the terms of the
// GNU General Public License version 2 or any later version.
//! Rust implementation of dirstate.status (dirstate.py).
//! It is currently missing a lot of functionality compared to the Python one
//! and will only be triggered in narrow cases.
Raphaël Gomès
rust: introduce SIZE_FROM_OTHER_PARENT constant...
r44003 use crate::{
dirstate::SIZE_FROM_OTHER_PARENT,
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 filepatterns::PatternFileWarning,
matchers::{get_ignore_function, Matcher, VisitChildrenSet},
Raphaël Gomès
rust: introduce SIZE_FROM_OTHER_PARENT constant...
r44003 utils::{
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 files::{find_dirs, HgMetadata},
Raphaël Gomès
rust-status: add util for listing a directory...
r45010 hg_path::{
hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 HgPathError,
Raphaël Gomès
rust-status: add util for listing a directory...
r45010 },
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 path_auditor::PathAuditor,
Raphaël Gomès
rust: introduce SIZE_FROM_OTHER_PARENT constant...
r44003 },
Raphaël Gomès
rust-status: add function for sequential traversal of the working directory...
r45014 CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap,
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 PatternError,
Raphaël Gomès
rust: introduce SIZE_FROM_OTHER_PARENT constant...
r44003 };
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 use lazy_static::lazy_static;
Raphaël Gomès
rust-dirstate-status: add first Rust implementation of `dirstate.status`...
r43565 use rayon::prelude::*;
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 use std::collections::VecDeque;
use std::{
borrow::Cow,
collections::HashSet,
fs::{read_dir, DirEntry},
io::ErrorKind,
ops::Deref,
path::Path,
};
Raphaël Gomès
rust-dirstate-status: add first Rust implementation of `dirstate.status`...
r43565
Raphaël Gomès
rust-status: add missing variants to `Dispatch` enum...
r45013 /// Wrong type of file from a `BadMatch`
/// Note: a lot of those don't exist on all platforms.
#[derive(Debug)]
pub enum BadType {
CharacterDevice,
BlockDevice,
FIFO,
Socket,
Directory,
Unknown,
}
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 impl ToString for BadType {
fn to_string(&self) -> String {
match self {
BadType::CharacterDevice => "character device",
BadType::BlockDevice => "block device",
BadType::FIFO => "fifo",
BadType::Socket => "socket",
BadType::Directory => "directory",
BadType::Unknown => "unknown",
}
.to_string()
}
}
Raphaël Gomès
rust-status: add missing variants to `Dispatch` enum...
r45013 /// Was explicitly matched but cannot be found/accessed
#[derive(Debug)]
pub enum BadMatch {
OsError(i32),
BadType(BadType),
}
Raphaël Gomès
rust-status: improve status performance...
r44000 /// Marker enum used to dispatch new status entries into the right collections.
/// Is similar to `crate::EntryState`, but represents the transient state of
/// entries during the lifetime of a command.
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 #[derive(Debug)]
Raphaël Gomès
rust-status: improve status performance...
r44000 enum Dispatch {
Unsure,
Modified,
Added,
Removed,
Deleted,
Clean,
Unknown,
Raphaël Gomès
rust-status: add missing variants to `Dispatch` enum...
r45013 Ignored,
/// Empty dispatch, the file is not worth listing
None,
/// Was explicitly matched but cannot be found/accessed
Bad(BadMatch),
Directory {
/// True if the directory used to be a file in the dmap so we can say
/// that it's been removed.
was_file: bool,
},
Raphaël Gomès
rust-status: improve status performance...
r44000 }
Raphaël Gomès
rust-dirstate-status: add `walk_explicit` implementation, use `Matcher` trait...
r44367 type IoResult<T> = std::io::Result<T>;
Raphaël Gomès
rust-status: refactor dispatch case for normal files...
r44002 /// Dates and times that are outside the 31-bit signed range are compared
/// modulo 2^31. This should prevent hg from behaving badly with very large
/// files or corrupt dates while still having a high probability of detecting
/// changes. (issue2608)
/// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
/// is not defined for `i32`, and there is no `As` trait. This forces the
/// caller to cast `b` as `i32`.
fn mod_compare(a: i32, b: i32) -> bool {
a & i32::max_value() != b & i32::max_value()
}
Raphaël Gomès
rust-status: add util for listing a directory...
r45010 /// Return a sorted list containing information about the entries
/// in the directory.
///
/// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
fn list_directory(
path: impl AsRef<Path>,
skip_dot_hg: bool,
) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
let mut results = vec![];
let entries = read_dir(path.as_ref())?;
for entry in entries {
let entry = entry?;
let filename = os_string_to_hg_path_buf(entry.file_name())?;
let file_type = entry.file_type()?;
if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
return Ok(vec![]);
} else {
results.push((HgPathBuf::from(filename), entry))
}
}
results.sort_unstable_by_key(|e| e.0.clone());
Ok(results)
}
Raphaël Gomès
rust-status: improve status performance...
r44000 /// The file corresponding to the dirstate entry was found on the filesystem.
fn dispatch_found(
filename: impl AsRef<HgPath>,
entry: DirstateEntry,
metadata: HgMetadata,
copy_map: &CopyMap,
Raphaël Gomès
rust-status: refactor options into a `StatusOptions` struct...
r45011 options: StatusOptions,
Raphaël Gomès
rust-status: improve status performance...
r44000 ) -> Dispatch {
let DirstateEntry {
state,
mode,
mtime,
size,
} = entry;
let HgMetadata {
st_mode,
st_size,
st_mtime,
..
} = metadata;
match state {
EntryState::Normal => {
Raphaël Gomès
rust-status: refactor dispatch case for normal files...
r44002 let size_changed = mod_compare(size, st_size as i32);
Raphaël Gomès
rust-status: improve status performance...
r44000 let mode_changed =
Raphaël Gomès
rust-status: refactor options into a `StatusOptions` struct...
r45011 (mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
Raphaël Gomès
rust-status: refactor dispatch case for normal files...
r44002 let metadata_changed = size >= 0 && (size_changed || mode_changed);
Raphaël Gomès
rust: introduce SIZE_FROM_OTHER_PARENT constant...
r44003 let other_parent = size == SIZE_FROM_OTHER_PARENT;
Raphaël Gomès
rust-status: refactor dispatch case for normal files...
r44002 if metadata_changed
|| other_parent
|| copy_map.contains_key(filename.as_ref())
Raphaël Gomès
rust-status: improve status performance...
r44000 {
Dispatch::Modified
Raphaël Gomès
rust-status: refactor dispatch case for normal files...
r44002 } else if mod_compare(mtime, st_mtime as i32) {
Raphaël Gomès
rust-status: improve status performance...
r44000 Dispatch::Unsure
Raphaël Gomès
rust-status: refactor options into a `StatusOptions` struct...
r45011 } else if st_mtime == options.last_normal_time {
Raphaël Gomès
rust-status: improve status performance...
r44000 // the file may have just been marked as normal and
// it may have changed in the same second without
// changing its size. This can happen if we quickly
// do multiple commits. Force lookup, so we don't
// miss such a racy file change.
Dispatch::Unsure
Raphaël Gomès
rust-status: refactor options into a `StatusOptions` struct...
r45011 } else if options.list_clean {
Raphaël Gomès
rust-status: improve status performance...
r44000 Dispatch::Clean
} else {
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 Dispatch::None
Raphaël Gomès
rust-status: improve status performance...
r44000 }
}
EntryState::Merged => Dispatch::Modified,
EntryState::Added => Dispatch::Added,
EntryState::Removed => Dispatch::Removed,
EntryState::Unknown => Dispatch::Unknown,
}
}
/// The file corresponding to this Dirstate entry is missing.
fn dispatch_missing(state: EntryState) -> Dispatch {
match state {
// File was removed from the filesystem during commands
EntryState::Normal | EntryState::Merged | EntryState::Added => {
Dispatch::Deleted
}
// File was removed, everything is normal
EntryState::Removed => Dispatch::Removed,
// File is unknown to Mercurial, everything is normal
EntryState::Unknown => Dispatch::Unknown,
}
}
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 lazy_static! {
static ref DEFAULT_WORK: HashSet<&'static HgPath> = {
let mut h = HashSet::new();
h.insert(HgPath::new(b""));
h
};
}
Raphaël Gomès
rust-dirstate-status: add `walk_explicit` implementation, use `Matcher` trait...
r44367 /// Get stat data about the files explicitly specified by match.
/// TODO subrepos
fn walk_explicit<'a>(
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 files: Option<&'a HashSet<&HgPath>>,
Raphaël Gomès
rust-dirstate-status: add `walk_explicit` implementation, use `Matcher` trait...
r44367 dmap: &'a DirstateMap,
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 root_dir: impl AsRef<Path> + Sync + Send + 'a,
Raphaël Gomès
rust-status: refactor options into a `StatusOptions` struct...
r45011 options: StatusOptions,
Raphaël Gomès
rust-dirstate-status: add `walk_explicit` implementation, use `Matcher` trait...
r44367 ) -> impl ParallelIterator<Item = IoResult<(&'a HgPath, Dispatch)>> {
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 files
.unwrap_or(&DEFAULT_WORK)
.par_iter()
.map(move |filename| {
// TODO normalization
let normalized = filename.as_ref();
Raphaël Gomès
rust-dirstate-status: add `walk_explicit` implementation, use `Matcher` trait...
r44367
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 let buf = match hg_path_to_path_buf(normalized) {
Ok(x) => x,
Err(e) => return Some(Err(e.into())),
};
let target = root_dir.as_ref().join(buf);
let st = target.symlink_metadata();
let in_dmap = dmap.get(normalized);
match st {
Ok(meta) => {
let file_type = meta.file_type();
return if file_type.is_file() || file_type.is_symlink() {
if let Some(entry) = in_dmap {
return Some(Ok((
normalized,
dispatch_found(
&normalized,
*entry,
HgMetadata::from_metadata(meta),
&dmap.copy_map,
options,
),
)));
}
Some(Ok((normalized, Dispatch::Unknown)))
} else {
if file_type.is_dir() {
Some(Ok((
normalized,
Dispatch::Directory {
was_file: in_dmap.is_some(),
},
)))
} else {
Some(Ok((
normalized,
Dispatch::Bad(BadMatch::BadType(
// TODO do more than unknown
// Support for all `BadType` variant
// varies greatly between platforms.
// So far, no tests check the type and
// this should be good enough for most
// users.
BadType::Unknown,
)),
)))
}
};
}
Err(_) => {
if let Some(entry) = in_dmap {
Raphaël Gomès
rust-dirstate-status: add `walk_explicit` implementation, use `Matcher` trait...
r44367 return Some(Ok((
normalized,
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 dispatch_missing(entry.state),
Raphaël Gomès
rust-dirstate-status: add `walk_explicit` implementation, use `Matcher` trait...
r44367 )));
}
}
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 };
None
})
.flatten()
Raphaël Gomès
rust-dirstate-status: add `walk_explicit` implementation, use `Matcher` trait...
r44367 }
Raphaël Gomès
rust-status: refactor options into a `StatusOptions` struct...
r45011 #[derive(Debug, Copy, Clone)]
pub struct StatusOptions {
/// Remember the most recent modification timeslot for status, to make
/// sure we won't miss future size-preserving file content modifications
/// that happen within the same timeslot.
pub last_normal_time: i64,
/// Whether we are on a filesystem with UNIX-like exec flags
pub check_exec: bool,
pub list_clean: bool,
Raphaël Gomès
rust-status: add function for sequential traversal of the working directory...
r45014 pub list_unknown: bool,
pub list_ignored: bool,
}
/// Dispatch a single file found during `traverse`.
/// If `file` is a folder that needs to be traversed, it will be pushed into
/// `work`.
fn traverse_worker<'a>(
work: &mut VecDeque<HgPathBuf>,
matcher: &impl Matcher,
dmap: &DirstateMap,
filename: impl AsRef<HgPath>,
dir_entry: &DirEntry,
ignore_fn: &impl for<'r> Fn(&'r HgPath) -> bool,
dir_ignore_fn: &impl for<'r> Fn(&'r HgPath) -> bool,
options: StatusOptions,
) -> Option<IoResult<(Cow<'a, HgPath>, Dispatch)>> {
let file_type = match dir_entry.file_type() {
Ok(x) => x,
Err(e) => return Some(Err(e.into())),
};
let filename = filename.as_ref();
let entry_option = dmap.get(filename);
if file_type.is_dir() {
// Do we need to traverse it?
if !ignore_fn(&filename) || options.list_ignored {
work.push_front(filename.to_owned());
}
// Nested `if` until `rust-lang/rust#53668` is stable
if let Some(entry) = entry_option {
// Used to be a file, is now a folder
if matcher.matches_everything() || matcher.matches(&filename) {
return Some(Ok((
Cow::Owned(filename.to_owned()),
dispatch_missing(entry.state),
)));
}
}
} else if file_type.is_file() || file_type.is_symlink() {
if let Some(entry) = entry_option {
if matcher.matches_everything() || matcher.matches(&filename) {
let metadata = match dir_entry.metadata() {
Ok(x) => x,
Err(e) => return Some(Err(e.into())),
};
return Some(Ok((
Cow::Owned(filename.to_owned()),
dispatch_found(
&filename,
*entry,
HgMetadata::from_metadata(metadata),
&dmap.copy_map,
options,
),
)));
}
} else if (matcher.matches_everything() || matcher.matches(&filename))
&& !ignore_fn(&filename)
{
if (options.list_ignored || matcher.exact_match(&filename))
&& dir_ignore_fn(&filename)
{
if options.list_ignored {
return Some(Ok((
Cow::Owned(filename.to_owned()),
Dispatch::Ignored,
)));
}
} else {
return Some(Ok((
Cow::Owned(filename.to_owned()),
Dispatch::Unknown,
)));
}
}
} else if let Some(entry) = entry_option {
// Used to be a file or a folder, now something else.
if matcher.matches_everything() || matcher.matches(&filename) {
return Some(Ok((
Cow::Owned(filename.to_owned()),
dispatch_missing(entry.state),
)));
}
}
None
}
/// Walk the working directory recursively to look for changes compared to the
/// current `DirstateMap`.
fn traverse<'a>(
matcher: &(impl Matcher + Sync),
root_dir: impl AsRef<Path>,
dmap: &DirstateMap,
path: impl AsRef<HgPath>,
old_results: FastHashMap<Cow<'a, HgPath>, Dispatch>,
ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
dir_ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
options: StatusOptions,
) -> IoResult<FastHashMap<Cow<'a, HgPath>, Dispatch>> {
let root_dir = root_dir.as_ref();
let mut new_results = FastHashMap::default();
let mut work = VecDeque::new();
work.push_front(path.as_ref().to_owned());
while let Some(ref directory) = work.pop_front() {
if directory.as_bytes() == b".hg" {
continue;
}
let visit_entries = match matcher.visit_children_set(directory) {
VisitChildrenSet::Empty => continue,
VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
VisitChildrenSet::Set(set) => Some(set),
};
let buf = hg_path_to_path_buf(directory)?;
let dir_path = root_dir.join(buf);
let skip_dot_hg = !directory.as_bytes().is_empty();
let entries = match list_directory(dir_path, skip_dot_hg) {
Err(e) => match e.kind() {
ErrorKind::NotFound | ErrorKind::PermissionDenied => {
new_results.insert(
Cow::Owned(directory.to_owned()),
Dispatch::Bad(BadMatch::OsError(
// Unwrapping here is OK because the error always
// is a real os error
e.raw_os_error().unwrap(),
)),
);
continue;
}
_ => return Err(e),
},
Ok(entries) => entries,
};
for (filename, dir_entry) in entries {
if let Some(ref set) = visit_entries {
if !set.contains(filename.deref()) {
continue;
}
}
// TODO normalize
let filename = if directory.is_empty() {
filename.to_owned()
} else {
directory.join(&filename)
};
if !old_results.contains_key(filename.deref()) {
if let Some((res, dispatch)) = traverse_worker(
&mut work,
matcher,
&dmap,
&filename,
&dir_entry,
&ignore_fn,
&dir_ignore_fn,
options,
)
.transpose()?
{
new_results.insert(res, dispatch);
}
}
}
}
new_results.extend(old_results.into_iter());
Ok(new_results)
Raphaël Gomès
rust-status: refactor options into a `StatusOptions` struct...
r45011 }
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 /// Stat all entries in the `DirstateMap` and mark them for dispatch.
Raphaël Gomès
rust-status: improve status performance...
r44000 fn stat_dmap_entries(
Raphaël Gomès
rust-dirstate-status: add first Rust implementation of `dirstate.status`...
r43565 dmap: &DirstateMap,
Raphaël Gomès
rust-status: return a ParallelIterator instead of a Vec from stat_dmap_entries...
r44001 root_dir: impl AsRef<Path> + Sync + Send,
Raphaël Gomès
rust-status: refactor options into a `StatusOptions` struct...
r45011 options: StatusOptions,
Raphaël Gomès
rust-dirstate-status: add `walk_explicit` implementation, use `Matcher` trait...
r44367 ) -> impl ParallelIterator<Item = IoResult<(&HgPath, Dispatch)>> {
Raphaël Gomès
rust-status: return a ParallelIterator instead of a Vec from stat_dmap_entries...
r44001 dmap.par_iter().map(move |(filename, entry)| {
let filename: &HgPath = filename;
let filename_as_path = hg_path_to_path_buf(filename)?;
let meta = root_dir.as_ref().join(filename_as_path).symlink_metadata();
Raphaël Gomès
rust-dirstate-status: add first Rust implementation of `dirstate.status`...
r43565
Raphaël Gomès
rust-status: return a ParallelIterator instead of a Vec from stat_dmap_entries...
r44001 match meta {
Ok(ref m)
if !(m.file_type().is_file()
|| m.file_type().is_symlink()) =>
{
Ok((filename, dispatch_missing(entry.state)))
}
Ok(m) => Ok((
filename,
dispatch_found(
filename,
*entry,
HgMetadata::from_metadata(m),
&dmap.copy_map,
Raphaël Gomès
rust-status: refactor options into a `StatusOptions` struct...
r45011 options,
Raphaël Gomès
rust-status: return a ParallelIterator instead of a Vec from stat_dmap_entries...
r44001 ),
)),
Err(ref e)
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 if e.kind() == ErrorKind::NotFound
Raphaël Gomès
rust-status: return a ParallelIterator instead of a Vec from stat_dmap_entries...
r44001 || e.raw_os_error() == Some(20) =>
{
// Rust does not yet have an `ErrorKind` for
// `NotADirectory` (errno 20)
// It happens if the dirstate contains `foo/bar` and
// foo is not a directory
Ok((filename, dispatch_missing(entry.state)))
}
Err(e) => Err(e),
}
})
Raphaël Gomès
rust-dirstate-status: add first Rust implementation of `dirstate.status`...
r43565 }
Raphaël Gomès
rust-status: rename `StatusResult` to `DirstateStatus`...
r45012 pub struct DirstateStatus<'a> {
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 pub modified: Vec<Cow<'a, HgPath>>,
pub added: Vec<Cow<'a, HgPath>>,
pub removed: Vec<Cow<'a, HgPath>>,
pub deleted: Vec<Cow<'a, HgPath>>,
pub clean: Vec<Cow<'a, HgPath>>,
pub ignored: Vec<Cow<'a, HgPath>>,
pub unknown: Vec<Cow<'a, HgPath>>,
pub bad: Vec<(Cow<'a, HgPath>, BadMatch)>,
Raphaël Gomès
rust-dirstate-status: add first Rust implementation of `dirstate.status`...
r43565 }
Raphaël Gomès
rust-dirstate-status: add `walk_explicit` implementation, use `Matcher` trait...
r44367 fn build_response<'a>(
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 results: impl IntoIterator<Item = (Cow<'a, HgPath>, Dispatch)>,
) -> (Vec<Cow<'a, HgPath>>, DirstateStatus<'a>) {
Raphaël Gomès
rust-dirstate-status: add first Rust implementation of `dirstate.status`...
r43565 let mut lookup = vec![];
let mut modified = vec![];
let mut added = vec![];
let mut removed = vec![];
let mut deleted = vec![];
let mut clean = vec![];
Raphaël Gomès
rust-status: add missing variants to `Dispatch` enum...
r45013 let mut ignored = vec![];
let mut unknown = vec![];
let mut bad = vec![];
Raphaël Gomès
rust-dirstate-status: add first Rust implementation of `dirstate.status`...
r43565
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 for (filename, dispatch) in results.into_iter() {
Raphaël Gomès
rust-status: improve status performance...
r44000 match dispatch {
Raphaël Gomès
rust-status: add missing variants to `Dispatch` enum...
r45013 Dispatch::Unknown => unknown.push(filename),
Raphaël Gomès
rust-status: improve status performance...
r44000 Dispatch::Unsure => lookup.push(filename),
Dispatch::Modified => modified.push(filename),
Dispatch::Added => added.push(filename),
Dispatch::Removed => removed.push(filename),
Dispatch::Deleted => deleted.push(filename),
Dispatch::Clean => clean.push(filename),
Raphaël Gomès
rust-status: add missing variants to `Dispatch` enum...
r45013 Dispatch::Ignored => ignored.push(filename),
Dispatch::None => {}
Dispatch::Bad(reason) => bad.push((filename, reason)),
Dispatch::Directory { .. } => {}
Raphaël Gomès
rust-dirstate-status: add first Rust implementation of `dirstate.status`...
r43565 }
}
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 (
Raphaël Gomès
rust-dirstate-status: add first Rust implementation of `dirstate.status`...
r43565 lookup,
Raphaël Gomès
rust-status: rename `StatusResult` to `DirstateStatus`...
r45012 DirstateStatus {
Raphaël Gomès
rust-dirstate-status: add first Rust implementation of `dirstate.status`...
r43565 modified,
added,
removed,
deleted,
clean,
Raphaël Gomès
rust-status: add missing variants to `Dispatch` enum...
r45013 ignored,
unknown,
bad,
Raphaël Gomès
rust-dirstate-status: add first Rust implementation of `dirstate.status`...
r43565 },
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 )
}
pub enum StatusError {
IO(std::io::Error),
Path(HgPathError),
Pattern(PatternError),
Raphaël Gomès
rust-dirstate-status: add first Rust implementation of `dirstate.status`...
r43565 }
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 pub type StatusResult<T> = Result<T, StatusError>;
impl From<PatternError> for StatusError {
fn from(e: PatternError) -> Self {
StatusError::Pattern(e)
}
}
impl From<HgPathError> for StatusError {
fn from(e: HgPathError) -> Self {
StatusError::Path(e)
}
}
impl From<std::io::Error> for StatusError {
fn from(e: std::io::Error) -> Self {
StatusError::IO(e)
}
}
impl ToString for StatusError {
fn to_string(&self) -> String {
match self {
StatusError::IO(e) => e.to_string(),
StatusError::Path(e) => e.to_string(),
StatusError::Pattern(e) => e.to_string(),
}
}
}
/// Get the status of files in the working directory.
///
/// This is the current entry-point for `hg-core` and is realistically unusable
/// outside of a Python context because its arguments need to provide a lot of
/// information that will not be necessary in the future.
Raphaël Gomès
rust-dirstate-status: add `walk_explicit` implementation, use `Matcher` trait...
r44367 pub fn status<'a: 'c, 'b: 'c, 'c>(
dmap: &'a DirstateMap,
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 matcher: &'b (impl Matcher + Sync),
root_dir: impl AsRef<Path> + Sync + Send + Copy + 'c,
ignore_files: &[impl AsRef<Path> + 'c],
Raphaël Gomès
rust-status: refactor options into a `StatusOptions` struct...
r45011 options: StatusOptions,
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 ) -> StatusResult<(
(Vec<Cow<'c, HgPath>>, DirstateStatus<'c>),
Vec<PatternFileWarning>,
)> {
let (ignore_fn, warnings) = get_ignore_function(&ignore_files, root_dir)?;
// Is the path or one of its ancestors ignored?
let dir_ignore_fn = |dir: &_| {
if ignore_fn(dir) {
true
} else {
for p in find_dirs(dir) {
if ignore_fn(p) {
return true;
}
}
false
}
};
Raphaël Gomès
rust-dirstate-status: add `walk_explicit` implementation, use `Matcher` trait...
r44367 let files = matcher.file_set();
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015
// Step 1: check the files explicitly mentioned by the user
let explicit = walk_explicit(files, &dmap, root_dir, options);
let (work, mut results): (Vec<_>, FastHashMap<_, _>) = explicit
.filter_map(Result::ok)
.map(|(filename, dispatch)| (Cow::Borrowed(filename), dispatch))
.partition(|(_, dispatch)| match dispatch {
Dispatch::Directory { .. } => true,
_ => false,
});
// Step 2: recursively check the working directory for changes if needed
for (dir, dispatch) in work {
match dispatch {
Dispatch::Directory { was_file } => {
if was_file {
results.insert(dir.to_owned(), Dispatch::Removed);
}
if options.list_ignored
|| options.list_unknown && !dir_ignore_fn(&dir)
{
results = traverse(
matcher,
root_dir,
&dmap,
&dir,
results,
&ignore_fn,
&dir_ignore_fn,
options,
)?;
}
}
_ => unreachable!("There can only be directories in `work`"),
}
Raphaël Gomès
rust-dirstate-status: add `walk_explicit` implementation, use `Matcher` trait...
r44367 }
Raphaël Gomès
rust-status: improve status performance...
r44000
Raphaël Gomès
rust-dirstate-status: add `walk_explicit` implementation, use `Matcher` trait...
r44367 if !matcher.is_exact() {
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 // Step 3: Check the remaining files from the dmap.
// If a dmap file is not in results yet, it was either
// a) not matched b) ignored, c) missing, or d) under a
// symlink directory.
if options.list_unknown {
let to_visit: Box<dyn Iterator<Item = (&HgPath, &DirstateEntry)>> =
if results.is_empty() && matcher.matches_everything() {
Box::new(dmap.iter().map(|(f, e)| (f.deref(), e)))
} else {
Box::new(dmap.iter().filter_map(|(f, e)| {
if !results.contains_key(f.deref())
&& matcher.matches(f)
{
Some((f.deref(), e))
} else {
None
}
}))
};
let mut to_visit: Vec<_> = to_visit.collect();
to_visit.sort_by(|a, b| a.0.cmp(&b.0));
// We walked all dirs under the roots that weren't ignored, and
// everything that matched was stat'ed and is already in results.
// The rest must thus be ignored or under a symlink.
let mut path_auditor = PathAuditor::new(root_dir);
for (ref filename, entry) in to_visit {
// Report ignored items in the dmap as long as they are not
// under a symlink directory.
if path_auditor.check(filename) {
// TODO normalize for case-insensitive filesystems
let buf = hg_path_to_path_buf(filename)?;
results.insert(
Cow::Borrowed(filename),
match root_dir.as_ref().join(&buf).symlink_metadata() {
// File was just ignored, no links, and exists
Ok(meta) => {
let metadata = HgMetadata::from_metadata(meta);
dispatch_found(
filename,
*entry,
metadata,
&dmap.copy_map,
options,
)
}
// File doesn't exist
Err(_) => dispatch_missing(entry.state),
},
);
} else {
// It's either missing or under a symlink directory which
// we, in this case, report as missing.
results.insert(
Cow::Borrowed(filename),
dispatch_missing(entry.state),
);
}
}
} else {
// We may not have walked the full directory tree above, so stat
// and check everything we missed.
let stat_results = stat_dmap_entries(&dmap, root_dir, options);
results.par_extend(stat_results.flatten().map(
|(filename, dispatch)| (Cow::Borrowed(filename), dispatch),
));
}
Raphaël Gomès
rust-dirstate-status: add `walk_explicit` implementation, use `Matcher` trait...
r44367 }
Raphaël Gomès
rust-status: add bare `hg status` support in hg-core...
r45015 let results = results.into_iter().filter_map(|(filename, dispatch)| {
match dispatch {
Dispatch::Bad(_) => return Some((filename, dispatch)),
_ => {}
};
// TODO do this in //, not at the end
if !dmap.contains_key(filename.deref()) {
if (options.list_ignored || matcher.exact_match(&filename))
&& dir_ignore_fn(&filename)
{
if options.list_ignored {
return Some((filename.to_owned(), Dispatch::Ignored));
}
} else {
if !ignore_fn(&filename) {
return Some((filename.to_owned(), Dispatch::Unknown));
}
}
return None;
}
Some((filename, dispatch))
});
Ok((build_response(results), warnings))
Raphaël Gomès
rust-dirstate-status: add first Rust implementation of `dirstate.status`...
r43565 }