##// END OF EJS Templates
rust-matchers: add `Matcher` trait and implement `AlwaysMatcher`...
rust-matchers: add `Matcher` trait and implement `AlwaysMatcher` In our quest of a faster Mercurial, we have arrived at the point where we need to implement the matchers in Rust. This RFC mainly for the `Matcher` trait to see if the changes proposed feel fine to people with more experience on the matter. While the `AlwaysMatcher` implementation is here as a trivial example, it should be the first step towards matchers use in Rust as it is currently the only supported one. Notable changes: - `exact` is renamed to `exact_match` - enums for `visit*` methods with `Recursive` instead of `'all'`, etc. - a new `roots`, separate from `file_set` - no `bad`, `explicitdir` or `traversedir` functions as they can be passed to the high functions instead of the matchers Thanks to Martin for suggesting the last two (most important) changes and for reaching out to help a few weeks ago. Differential Revision: https://phab.mercurial-scm.org/D7178

File last commit:

r43565:99394e6c default
r43742:a77d4fe3 default
Show More
status.rs
248 lines | 8.4 KiB | application/rls-services+xml | RustLexer
// 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.
use crate::utils::files::HgMetadata;
use crate::utils::hg_path::{hg_path_to_path_buf, HgPath, HgPathBuf};
use crate::{DirstateEntry, DirstateMap, EntryState};
use rayon::prelude::*;
use std::collections::HashMap;
use std::fs::Metadata;
use std::path::Path;
/// Get stat data about the files explicitly specified by match.
/// TODO subrepos
fn walk_explicit(
files: &[impl AsRef<HgPath> + Sync],
dmap: &DirstateMap,
root_dir: impl AsRef<Path> + Sync,
) -> std::io::Result<HashMap<HgPathBuf, Option<HgMetadata>>> {
let mut results = HashMap::new();
// A tuple of the normalized filename and the `Result` of the call to
// `symlink_metadata` for separate handling.
type WalkTuple<'a> = (&'a HgPath, std::io::Result<Metadata>);
let stats_res: std::io::Result<Vec<WalkTuple>> = files
.par_iter()
.map(|filename| {
// TODO normalization
let normalized = filename.as_ref();
let target_filename =
root_dir.as_ref().join(hg_path_to_path_buf(normalized)?);
Ok((normalized, target_filename.symlink_metadata()))
})
.collect();
for res in stats_res? {
match res {
(normalized, Ok(stat)) => {
if stat.is_file() {
results.insert(
normalized.to_owned(),
Some(HgMetadata::from_metadata(stat)),
);
} else {
if dmap.contains_key(normalized) {
results.insert(normalized.to_owned(), None);
}
}
}
(normalized, Err(_)) => {
if dmap.contains_key(normalized) {
results.insert(normalized.to_owned(), None);
}
}
};
}
Ok(results)
}
// Stat all entries in the `DirstateMap` and return their new metadata.
pub fn stat_dmap_entries(
dmap: &DirstateMap,
results: &HashMap<HgPathBuf, Option<HgMetadata>>,
root_dir: impl AsRef<Path> + Sync,
) -> std::io::Result<Vec<(HgPathBuf, Option<HgMetadata>)>> {
dmap.par_iter()
.filter_map(
// Getting file metadata is costly, so we don't do it if the
// file is already present in the results, hence `filter_map`
|(filename, _)| -> Option<
std::io::Result<(HgPathBuf, Option<HgMetadata>)>
> {
if results.contains_key(filename) {
return None;
}
let meta = match hg_path_to_path_buf(filename) {
Ok(p) => root_dir.as_ref().join(p).symlink_metadata(),
Err(e) => return Some(Err(e.into())),
};
Some(match meta {
Ok(ref m)
if !(m.file_type().is_file()
|| m.file_type().is_symlink()) =>
{
Ok((filename.to_owned(), None))
}
Ok(m) => Ok((
filename.to_owned(),
Some(HgMetadata::from_metadata(m)),
)),
Err(ref e)
if e.kind() == std::io::ErrorKind::NotFound
|| 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.to_owned(), None))
}
Err(e) => Err(e),
})
},
)
.collect()
}
pub struct StatusResult {
pub modified: Vec<HgPathBuf>,
pub added: Vec<HgPathBuf>,
pub removed: Vec<HgPathBuf>,
pub deleted: Vec<HgPathBuf>,
pub clean: Vec<HgPathBuf>,
// TODO ignored
// TODO unknown
}
fn build_response(
dmap: &DirstateMap,
list_clean: bool,
last_normal_time: i64,
check_exec: bool,
results: HashMap<HgPathBuf, Option<HgMetadata>>,
) -> (Vec<HgPathBuf>, StatusResult) {
let mut lookup = vec![];
let mut modified = vec![];
let mut added = vec![];
let mut removed = vec![];
let mut deleted = vec![];
let mut clean = vec![];
for (filename, metadata_option) in results.into_iter() {
let DirstateEntry {
state,
mode,
mtime,
size,
} = match dmap.get(&filename) {
None => {
continue;
}
Some(e) => *e,
};
match metadata_option {
None => {
match state {
EntryState::Normal
| EntryState::Merged
| EntryState::Added => deleted.push(filename),
EntryState::Removed => removed.push(filename),
_ => {}
};
}
Some(HgMetadata {
st_mode,
st_size,
st_mtime,
..
}) => {
match state {
EntryState::Normal => {
// Dates and times that are outside the 31-bit signed
// range are compared modulo 2^31. This should prevent
// it from behaving badly with very large files or
// corrupt dates while still having a high probability
// of detecting changes. (issue2608)
let range_mask = 0x7fffffff;
let size_changed = (size != st_size as i32)
&& size != (st_size as i32 & range_mask);
let mode_changed = (mode ^ st_mode as i32) & 0o100
!= 0o000
&& check_exec;
if size >= 0
&& (size_changed || mode_changed)
|| size == -2 // other parent
|| dmap.copy_map.contains_key(&filename)
{
modified.push(filename);
} else if mtime != st_mtime as i32
&& mtime != (st_mtime as i32 & range_mask)
{
lookup.push(filename);
} else if st_mtime == last_normal_time {
// 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.
lookup.push(filename);
} else if list_clean {
clean.push(filename);
}
}
EntryState::Merged => modified.push(filename),
EntryState::Added => added.push(filename),
EntryState::Removed => removed.push(filename),
EntryState::Unknown => {}
}
}
}
}
(
lookup,
StatusResult {
modified,
added,
removed,
deleted,
clean,
},
)
}
pub fn status(
dmap: &DirstateMap,
root_dir: impl AsRef<Path> + Sync + Copy,
files: &[impl AsRef<HgPath> + Sync],
list_clean: bool,
last_normal_time: i64,
check_exec: bool,
) -> std::io::Result<(Vec<HgPathBuf>, StatusResult)> {
let mut results = walk_explicit(files, &dmap, root_dir)?;
results.extend(stat_dmap_entries(&dmap, &results, root_dir)?);
Ok(build_response(
&dmap,
list_clean,
last_normal_time,
check_exec,
results,
))
}