status.rs
248 lines
| 8.4 KiB
| application/rls-services+xml
|
RustLexer
Raphaël Gomès
|
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. | ||||
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, | ||||
)) | ||||
} | ||||