##// END OF EJS Templates
rust: add a utility function to merge ordered fallible iterators...
rust: add a utility function to merge ordered fallible iterators Adding a function merge_join_results_by, a version of itertools::merge_join_by that works on "fallible" iterators (iterators that can produce errors)

File last commit:

r52046:976403c9 default
r52047:aba622c7 default
Show More
status.rs
728 lines | 23.9 KiB | application/rls-services+xml | RustLexer
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 // status.rs
//
// Copyright 2020, Georges Racinet <georges.racinets@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.
use crate::error::CommandError;
Raphaël Gomès
rust-ui: refactor ui code for printing narrow/sparse warnings...
r50876 use crate::ui::{
Raphaël Gomès
rust-config: add support for default config items...
r51656 format_pattern_file_warning, print_narrow_sparse_warnings, relative_paths,
RelativePaths, Ui,
Raphaël Gomès
rust-ui: refactor ui code for printing narrow/sparse warnings...
r50876 };
Simon Sapin
rhg: refactor relativize_path into a struct + method...
r49284 use crate::utils::path_utils::RelativizePaths;
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 use clap::Arg;
Simon Sapin
rhg: Add support for `rhg status -n`...
r49171 use format_bytes::format_bytes;
Pulkit Goyal
rhg: add relative paths support in `rhg status`...
r48989 use hg::config::Config;
dirstate: remove `lastnormaltime` mechanism...
r49220 use hg::dirstate::has_exec_bit;
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 use hg::dirstate::status::StatusPath;
Simon Sapin
rhg: Update the dirstate on disk after status...
r49250 use hg::dirstate::TruncatedTimestamp;
use hg::errors::{HgError, IoResultExt};
Spencer Baugh
rhg: support "status FILE"...
r51759 use hg::filepatterns::parse_pattern_args;
Simon Sapin
rhg: Update the dirstate on disk after status...
r49250 use hg::lock::LockError;
Simon Sapin
rhg: Reuse manifest when checking status of multiple ambiguous files...
r48778 use hg::manifest::Manifest;
Raphaël Gomès
rhg-status: add support for narrow clones
r50383 use hg::matchers::{AlwaysMatcher, IntersectionMatcher};
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 use hg::repo::Repo;
Raphaël Gomès , Pierre-Yves David pierre-yves.david@octobus.net
dirstate: add a way to test races happening during status...
r51110 use hg::utils::debug::debug_wait_for_file;
Spencer Baugh
rhg: support "status FILE"...
r51759 use hg::utils::files::{
get_bytes_from_os_str, get_bytes_from_os_string, get_path_from_bytes,
};
Simon Sapin
rhg: Update the dirstate on disk after status...
r49250 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 use hg::DirstateStatus;
use hg::PatternFileWarning;
use hg::StatusError;
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 use hg::StatusOptions;
Raphaël Gomès
rhg-status: add support for narrow clones
r50383 use hg::{self, narrow, sparse};
Simon Sapin
rhg: Properly format warnings related to ignore patterns...
r49342 use log::info;
Arseniy Alekseyev
rhg: parallellize computation of [unsure_is_modified]...
r50412 use rayon::prelude::*;
Simon Sapin
rhg: Update the dirstate on disk after status...
r49250 use std::io;
Arseniy Alekseyev
rhg: refactor hg status, make the display code usable for non-dirstate status
r52046 use std::mem::take;
Simon Sapin
rhg: Add support for ui.ignore and ui.ignore.* config...
r49282 use std::path::PathBuf;
Georges Racinet
rhg: Initial support for the 'status' command...
r47578
pub const HELP_TEXT: &str = "
Show changed files in the working directory
This is a pure Rust version of `hg status`.
Some options might be missing, check the list below.
";
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 pub fn args() -> clap::Command {
clap::command!("status")
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 .alias("st")
.about(HELP_TEXT)
.arg(
Spencer Baugh
rhg: support "status FILE"...
r51759 Arg::new("file")
.value_parser(clap::value_parser!(std::ffi::OsString))
.help("show only these files")
.action(clap::ArgAction::Append),
)
.arg(
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 Arg::new("all")
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 .help("show status of all files")
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 .short('A')
.action(clap::ArgAction::SetTrue)
.long("all"),
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 )
.arg(
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 Arg::new("modified")
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 .help("show only modified files")
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 .short('m')
.action(clap::ArgAction::SetTrue)
.long("modified"),
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 )
.arg(
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 Arg::new("added")
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 .help("show only added files")
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 .short('a')
.action(clap::ArgAction::SetTrue)
.long("added"),
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 )
.arg(
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 Arg::new("removed")
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 .help("show only removed files")
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 .short('r')
.action(clap::ArgAction::SetTrue)
.long("removed"),
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 )
.arg(
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 Arg::new("clean")
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 .help("show only clean files")
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 .short('c')
.action(clap::ArgAction::SetTrue)
.long("clean"),
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 )
.arg(
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 Arg::new("deleted")
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 .help("show only deleted files")
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 .short('d')
.action(clap::ArgAction::SetTrue)
.long("deleted"),
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 )
.arg(
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 Arg::new("unknown")
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 .help("show only unknown (not tracked) files")
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 .short('u')
.action(clap::ArgAction::SetTrue)
.long("unknown"),
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 )
.arg(
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 Arg::new("ignored")
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 .help("show only ignored files")
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 .short('i')
.action(clap::ArgAction::SetTrue)
.long("ignored"),
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 )
Simon Sapin
rhg: Add support for `rhg status -n`...
r49171 .arg(
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 Arg::new("copies")
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 .help("show source of copied files (DEFAULT: ui.statuscopies)")
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 .short('C')
.action(clap::ArgAction::SetTrue)
.long("copies"),
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 )
.arg(
Arseniy Alekseyev
rhg: support `status --print0`...
r51289 Arg::new("print0")
.help("end filenames with NUL, for use with xargs")
.short('0')
.action(clap::ArgAction::SetTrue)
.long("print0"),
)
.arg(
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 Arg::new("no-status")
Simon Sapin
rhg: Add support for `rhg status -n`...
r49171 .help("hide status prefix")
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 .short('n')
.action(clap::ArgAction::SetTrue)
.long("no-status"),
Simon Sapin
rhg: Add support for `rhg status -n`...
r49171 )
Arseniy Alekseyev
rhg: make [rhg status -v] work when it needs no extra output...
r50334 .arg(
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 Arg::new("verbose")
Arseniy Alekseyev
rhg: make [rhg status -v] work when it needs no extra output...
r50334 .help("enable additional output")
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 .short('v')
.action(clap::ArgAction::SetTrue)
.long("verbose"),
Arseniy Alekseyev
rhg: make [rhg status -v] work when it needs no extra output...
r50334 )
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 }
/// Pure data type allowing the caller to specify file states to display
#[derive(Copy, Clone, Debug)]
pub struct DisplayStates {
pub modified: bool,
pub added: bool,
pub removed: bool,
pub clean: bool,
pub deleted: bool,
pub unknown: bool,
pub ignored: bool,
}
pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
modified: true,
added: true,
removed: true,
clean: false,
deleted: true,
unknown: true,
ignored: false,
};
pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
modified: true,
added: true,
removed: true,
clean: true,
deleted: true,
unknown: true,
ignored: true,
};
impl DisplayStates {
pub fn is_empty(&self) -> bool {
!(self.modified
|| self.added
|| self.removed
|| self.clean
|| self.deleted
|| self.unknown
|| self.ignored)
}
}
Arseniy Alekseyev
rhg: make [rhg status -v] work when it needs no extra output...
r50334 fn has_unfinished_merge(repo: &Repo) -> Result<bool, CommandError> {
Raphaël Gomès
rust: run `cargo clippy`...
r50809 Ok(repo.dirstate_parents()?.is_merge())
Arseniy Alekseyev
rhg: make [rhg status -v] work when it needs no extra output...
r50334 }
fn has_unfinished_state(repo: &Repo) -> Result<bool, CommandError> {
// These are all the known values for the [fname] argument of
// [addunfinished] function in [state.py]
let known_state_files: &[&str] = &[
"bisect.state",
"graftstate",
"histedit-state",
"rebasestate",
"shelvedstate",
"transplant/journal",
"updatestate",
];
if has_unfinished_merge(repo)? {
return Ok(true);
};
for f in known_state_files {
if repo.hg_vfs().join(f).exists() {
return Ok(true);
}
}
Raphaël Gomès
rust: run `cargo clippy`...
r50809 Ok(false)
Arseniy Alekseyev
rhg: make [rhg status -v] work when it needs no extra output...
r50334 }
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
Pulkit Goyal
rhg: fallback if tweakdefaults or statuscopies is enabled with status...
r48985 // TODO: lift these limitations
Simon Sapin
rhg: Config commands.status.terse is not supported...
r49161 if invocation
.config
.get(b"commands", b"status.terse")
.is_some()
{
return Err(CommandError::unsupported(
"status.terse is not yet supported with rhg status",
));
}
Pulkit Goyal
rhg: fallback if tweakdefaults or statuscopies is enabled with status...
r48985
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 let ui = invocation.ui;
Pulkit Goyal
rhg: add relative paths support in `rhg status`...
r48989 let config = invocation.config;
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 let args = invocation.subcommand_args;
Simon Sapin
rhg: Fall back to Python if verbose status is requested by config...
r49344
Arseniy Alekseyev
rhg: support `status --print0`...
r51289 let print0 = args.get_flag("print0");
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 let verbose = args.get_flag("verbose")
|| config.get_bool(b"ui", b"verbose")?
|| config.get_bool(b"commands", b"status.verbose")?;
Arseniy Alekseyev
rhg: support `status --print0`...
r51289 let verbose = verbose && !print0;
Simon Sapin
rhg: Fall back to Python if verbose status is requested by config...
r49344
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 let all = args.get_flag("all");
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 let display_states = if all {
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 // TODO when implementing `--quiet`: it excludes clean files
// from `--all`
ALL_DISPLAY_STATES
} else {
let requested = DisplayStates {
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 modified: args.get_flag("modified"),
added: args.get_flag("added"),
removed: args.get_flag("removed"),
clean: args.get_flag("clean"),
deleted: args.get_flag("deleted"),
unknown: args.get_flag("unknown"),
ignored: args.get_flag("ignored"),
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 };
if requested.is_empty() {
DEFAULT_DISPLAY_STATES
} else {
requested
}
};
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 let no_status = args.get_flag("no-status");
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 let list_copies = all
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 || args.get_flag("copies")
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 || config.get_bool(b"ui", b"statuscopies")?;
Georges Racinet
rhg: Initial support for the 'status' command...
r47578
let repo = invocation.repo?;
Arseniy Alekseyev
rhg: add support for narrow clones and sparse checkouts...
r49238
Raphaël Gomès
rust: run `cargo clippy`...
r50809 if verbose && has_unfinished_state(repo)? {
return Err(CommandError::unsupported(
"verbose status output is not supported by rhg (and is needed because we're in an unfinished operation)",
));
Arseniy Alekseyev
rhg: make [rhg status -v] work when it needs no extra output...
r50334 }
Simon Sapin
rust: Add Repo::dirstate_map and use it in `rhg status`...
r48768 let mut dmap = repo.dirstate_map_mut()?;
Simon Sapin
dirstate-v2: Introduce a docket file...
r48474
Arseniy Alekseyev
rhg: implement checkexec to support weird filesystems...
r50789 let check_exec = hg::checkexec::check_exec(repo.working_directory_path());
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 let options = StatusOptions {
Arseniy Alekseyev
rhg: implement checkexec to support weird filesystems...
r50789 check_exec,
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 list_clean: display_states.clean,
list_unknown: display_states.unknown,
list_ignored: display_states.ignored,
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 list_copies,
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 collect_traversed_dirs: false,
};
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864
type StatusResult<'a> =
Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
Arseniy Alekseyev
rhg: refactor hg status, make the display code usable for non-dirstate status
r52046 let relative_status = config
.get_option(b"commands", b"status.relative")?
.expect("commands.status.relative should have a default value");
let relativize_paths = relative_status || {
// See in Python code with `getuipathfn` usage in `commands.py`.
let legacy_relative_behavior = args.contains_id("file");
match relative_paths(invocation.config)? {
RelativePaths::Legacy => legacy_relative_behavior,
RelativePaths::Bool(v) => v,
}
};
let mut output = DisplayStatusPaths {
ui,
no_status,
relativize: if relativize_paths {
Some(RelativizePaths::new(repo)?)
} else {
None
},
print0,
};
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 let after_status = |res: StatusResult| -> Result<_, CommandError> {
let (mut ds_status, pattern_warnings) = res?;
for warning in pattern_warnings {
Raphaël Gomès
rust-ui: refactor ui code for printing narrow/sparse warnings...
r50876 ui.write_stderr(&format_pattern_file_warning(&warning, repo))?;
Simon Sapin
rhg: Properly format warnings related to ignore patterns...
r49342 }
Georges Racinet
rhg: Initial support for the 'status' command...
r47578
Arseniy Alekseyev
rhg: refactor hg status, make the display code usable for non-dirstate status
r52046 for (path, error) in take(&mut ds_status.bad) {
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 let error = match error {
hg::BadMatch::OsError(code) => {
std::io::Error::from_raw_os_error(code).to_string()
}
hg::BadMatch::BadType(ty) => {
format!("unsupported file type (type is {})", ty)
Simon Sapin
rhg: Sort `rhg status` output correctly...
r48112 }
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 };
ui.write_stderr(&format_bytes!(
b"{}: {}\n",
path.as_bytes(),
error.as_bytes()
))?
}
if !ds_status.unsure.is_empty() {
info!(
"Files to be rechecked by retrieval from filelog: {:?}",
ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
);
}
let mut fixup = Vec::new();
if !ds_status.unsure.is_empty()
&& (display_states.modified || display_states.clean)
{
let p1 = repo.dirstate_parents()?.p1;
let manifest = repo.manifest_for_node(p1).map_err(|e| {
CommandError::from((e, &*format!("{:x}", p1.short())))
})?;
Arseniy Alekseyev
rhg: parallellize computation of [unsure_is_modified]...
r50412 let working_directory_vfs = repo.working_directory_vfs();
let store_vfs = repo.store_vfs();
Arseniy Alekseyev
rhg: refactor hg status, make the display code usable for non-dirstate status
r52046 let res: Vec<_> = take(&mut ds_status.unsure)
Arseniy Alekseyev
rhg: parallellize computation of [unsure_is_modified]...
r50412 .into_par_iter()
.map(|to_check| {
Raphaël Gomès
rhg: fix race when an ambiguous file is deleted on disk...
r51121 // The compiler seems to get a bit confused with complex
// inference when using a parallel iterator + map
// + map_err + collect, so let's just inline some of the
// logic.
match unsure_is_modified(
Arseniy Alekseyev
rhg: parallellize computation of [unsure_is_modified]...
r50412 working_directory_vfs,
store_vfs,
Arseniy Alekseyev
rhg: implement checkexec to support weird filesystems...
r50789 check_exec,
Arseniy Alekseyev
rhg: parallellize computation of [unsure_is_modified]...
r50412 &manifest,
&to_check.path,
Raphaël Gomès
rhg: fix race when an ambiguous file is deleted on disk...
r51121 ) {
Err(HgError::IoError { .. }) => {
// IO errors most likely stem from the file being
// deleted even though we know it's in the
// dirstate.
Ok((to_check, UnsureOutcome::Deleted))
}
Ok(outcome) => Ok((to_check, outcome)),
Err(e) => Err(e),
}
Arseniy Alekseyev
rhg: parallellize computation of [unsure_is_modified]...
r50412 })
.collect::<Result<_, _>>()?;
Raphaël Gomès
rhg: fix race when an ambiguous file is deleted on disk...
r51121 for (status_path, outcome) in res.into_iter() {
match outcome {
UnsureOutcome::Clean => {
if display_states.clean {
ds_status.clean.push(status_path.clone());
}
fixup.push(status_path.path.into_owned())
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 }
Raphaël Gomès
rhg: fix race when an ambiguous file is deleted on disk...
r51121 UnsureOutcome::Modified => {
if display_states.modified {
ds_status.modified.push(status_path);
}
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 }
Raphaël Gomès
rhg: fix race when an ambiguous file is deleted on disk...
r51121 UnsureOutcome::Deleted => {
if display_states.deleted {
ds_status.deleted.push(status_path);
}
}
Simon Sapin
rhg: Sort `rhg status` output correctly...
r48112 }
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 }
}
Raphaël Gomès
rust-config: add support for default config items...
r51656
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 let dirstate_write_needed = ds_status.dirty;
let filesystem_time_at_status_start =
ds_status.filesystem_time_at_status_start;
Arseniy Alekseyev
rhg: refactor hg status, make the display code usable for non-dirstate status
r52046 output.output(display_states, ds_status)?;
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 Ok((
fixup,
dirstate_write_needed,
filesystem_time_at_status_start,
))
Simon Sapin
rhg: refactor display_status_paths with a struct for common arguments...
r49283 };
Raphaël Gomès
rhg-status: add support for narrow clones
r50383 let (narrow_matcher, narrow_warnings) = narrow::matcher(repo)?;
let (sparse_matcher, sparse_warnings) = sparse::matcher(repo)?;
let matcher = match (repo.has_narrow(), repo.has_sparse()) {
(true, true) => {
Box::new(IntersectionMatcher::new(narrow_matcher, sparse_matcher))
}
(true, false) => narrow_matcher,
(false, true) => sparse_matcher,
(false, false) => Box::new(AlwaysMatcher),
};
Spencer Baugh
rhg: support "status FILE"...
r51759 let matcher = match args.get_many::<std::ffi::OsString>("file") {
None => matcher,
Some(files) => {
let patterns: Vec<Vec<u8>> = files
.filter(|s| !s.is_empty())
.map(get_bytes_from_os_str)
.collect();
for file in &patterns {
if file.starts_with(b"set:") {
return Err(CommandError::unsupported("fileset"));
}
}
let cwd = hg::utils::current_dir()?;
let root = repo.working_directory_path();
let ignore_patterns = parse_pattern_args(patterns, &cwd, root)?;
let files_matcher =
hg::matchers::PatternMatcher::new(ignore_patterns)?;
Box::new(IntersectionMatcher::new(
Box::new(files_matcher),
matcher,
))
}
};
Raphaël Gomès
rhg: add sparse support
r50380
Raphaël Gomès
rust-ui: refactor ui code for printing narrow/sparse warnings...
r50876 print_narrow_sparse_warnings(
&narrow_warnings,
&sparse_warnings,
ui,
repo,
)?;
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 let (fixup, mut dirstate_write_needed, filesystem_time_at_status_start) =
dmap.with_status(
Raphaël Gomès
rhg: add sparse support
r50380 matcher.as_ref(),
Raphaël Gomès
rust: fix unsound `OwningDirstateMap`...
r49864 repo.working_directory_path().to_owned(),
ignore_files(repo, config),
options,
after_status,
)?;
Simon Sapin
rhg: Update the dirstate on disk after status...
r49250
Raphaël Gomès , Pierre-Yves David pierre-yves.david@octobus.net
dirstate: add a way to test races happening during status...
r51110 // Development config option to test write races
if let Err(e) =
branching: merge stable into default...
r51147 debug_wait_for_file(config, "status.pre-dirstate-write-file")
Raphaël Gomès , Pierre-Yves David pierre-yves.david@octobus.net
dirstate: add a way to test races happening during status...
r51110 {
ui.write_stderr(e.as_bytes()).ok();
}
Simon Sapin
rhg: Update the dirstate on disk after status...
r49250 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
&& !dirstate_write_needed
{
// Nothing to update
return Ok(());
}
// Update the dirstate on disk if we can
let with_lock_result =
repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
if let Some(mtime_boundary) = filesystem_time_at_status_start {
for hg_path in fixup {
use std::os::unix::fs::MetadataExt;
let fs_path = hg_path_to_path_buf(&hg_path)
.expect("HgPath conversion");
// Specifically do not reuse `fs_metadata` from
// `unsure_is_clean` which was needed before reading
// contents. Here we access metadata again after reading
// content, in case it changed in the meantime.
Raphaël Gomès
rhg: fix race when a fixup file is deleted on disk...
r51120 let metadata_res = repo
Simon Sapin
rhg: Update the dirstate on disk after status...
r49250 .working_directory_vfs()
Raphaël Gomès
rhg: fix race when a fixup file is deleted on disk...
r51120 .symlink_metadata(&fs_path);
let fs_metadata = match metadata_res {
Ok(meta) => meta,
Err(err) => match err {
HgError::IoError { .. } => {
// The file has probably been deleted. In any
// case, it was in the dirstate before, so
// let's ignore the error.
continue;
}
_ => return Err(err.into()),
},
};
Simon Sapin
rhg: Set second_ambiguous as needed in post-status fixup...
r49272 if let Some(mtime) =
TruncatedTimestamp::for_reliable_mtime_of(
&fs_metadata,
&mtime_boundary,
)
.when_reading_file(&fs_path)?
{
Simon Sapin
rhg: Update the dirstate on disk after status...
r49250 let mode = fs_metadata.mode();
Raphaël Gomès
rhg: use the new `set_clean` API...
r50001 let size = fs_metadata.len();
dmap.set_clean(&hg_path, mode, size as u32, mtime)?;
Simon Sapin
rhg: Update the dirstate on disk after status...
r49250 dirstate_write_needed = true
}
}
}
drop(dmap); // Avoid "already mutably borrowed" RefCell panics
if dirstate_write_needed {
repo.write_dirstate()?
}
Ok(())
});
match with_lock_result {
Ok(closure_result) => closure_result?,
Err(LockError::AlreadyHeld) => {
// Not updating the dirstate is not ideal but not critical:
// don’t keep our caller waiting until some other Mercurial
// process releases the lock.
Raphaël Gomès
rust: add debug log about skipping dirstate update
r51073 log::info!("not writing dirstate from `status`: lock is held")
Simon Sapin
rhg: Update the dirstate on disk after status...
r49250 }
Err(LockError::Other(HgError::IoError { error, .. }))
if error.kind() == io::ErrorKind::PermissionDenied =>
{
// `hg status` on a read-only repository is fine
}
Err(LockError::Other(error)) => {
// Report other I/O errors
Err(error)?
}
}
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 Ok(())
}
Simon Sapin
rhg: Add support for ui.ignore and ui.ignore.* config...
r49282 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
let mut ignore_files = Vec::new();
let repo_ignore = repo.working_directory_vfs().join(".hgignore");
if repo_ignore.exists() {
ignore_files.push(repo_ignore)
}
for (key, value) in config.iter_section(b"ui") {
if key == b"ignore" || key.starts_with(b"ignore.") {
let path = get_path_from_bytes(value);
// TODO: expand "~/" and environment variable here, like Python
// does with `os.path.expanduser` and `os.path.expandvars`
let joined = repo.working_directory_path().join(path);
ignore_files.push(joined);
}
}
ignore_files
}
Simon Sapin
rhg: refactor display_status_paths with a struct for common arguments...
r49283 struct DisplayStatusPaths<'a> {
ui: &'a Ui,
Simon Sapin
rhg: Add support for `rhg status -n`...
r49171 no_status: bool,
Simon Sapin
rhg: refactor relativize_path into a struct + method...
r49284 relativize: Option<RelativizePaths>,
Arseniy Alekseyev
rhg: support `status --print0`...
r51289 print0: bool,
Simon Sapin
rhg: refactor display_status_paths with a struct for common arguments...
r49283 }
impl DisplayStatusPaths<'_> {
// Probably more elegant to use a Deref or Borrow trait rather than
// harcode HgPathBuf, but probably not really useful at this point
fn display(
&self,
status_prefix: &[u8],
Simon Sapin
rhg: Colorize `rhg status` output when appropriate...
r49585 label: &'static str,
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 mut paths: Vec<StatusPath<'_>>,
Simon Sapin
rhg: refactor display_status_paths with a struct for common arguments...
r49283 ) -> Result<(), CommandError> {
paths.sort_unstable();
Simon Sapin
rust: fix code formatting...
r49589 // TODO: get the stdout lock once for the whole loop
// instead of in each write
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 for StatusPath { path, copy_source } in paths {
Arun Kulshreshtha
rhg: correctly relativize copy source path
r51338 let relative_path;
let relative_source;
let (path, copy_source) = if let Some(relativize) =
&self.relativize
{
relative_path = relativize.relativize(&path);
relative_source =
copy_source.as_ref().map(|s| relativize.relativize(s));
(&*relative_path, relative_source.as_deref())
Simon Sapin
rhg: refactor relativize_path into a struct + method...
r49284 } else {
Arun Kulshreshtha
rhg: correctly relativize copy source path
r51338 (path.as_bytes(), copy_source.as_ref().map(|s| s.as_bytes()))
Simon Sapin
rhg: refactor relativize_path into a struct + method...
r49284 };
Simon Sapin
rhg: Colorize `rhg status` output when appropriate...
r49585 // TODO: Add a way to use `write_bytes!` instead of `format_bytes!`
// in order to stream to stdout instead of allocating an
// itermediate `Vec<u8>`.
if !self.no_status {
self.ui.write_stdout_labelled(status_prefix, label)?
Simon Sapin
rhg: refactor display_status_paths with a struct for common arguments...
r49283 }
Arseniy Alekseyev
rhg: support `status --print0`...
r51289 let linebreak = if self.print0 { b"\x00" } else { b"\n" };
self.ui.write_stdout_labelled(
&format_bytes!(b"{}{}", path, linebreak),
label,
)?;
Arun Kulshreshtha
rhg: don't print copy source when --no-status is passed
r51337 if let Some(source) = copy_source.filter(|_| !self.no_status) {
Simon Sapin
rhg: Colorize `rhg status` output when appropriate...
r49585 let label = "status.copied";
self.ui.write_stdout_labelled(
Raphaël Gomès
branching: merge stable into default
r51356 &format_bytes!(b" {}{}", source, linebreak),
Simon Sapin
rhg: Colorize `rhg status` output when appropriate...
r49585 label,
)?
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 }
Simon Sapin
rhg: Add support for `rhg status -n`...
r49171 }
Simon Sapin
rhg: refactor display_status_paths with a struct for common arguments...
r49283 Ok(())
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 }
Arseniy Alekseyev
rhg: refactor hg status, make the display code usable for non-dirstate status
r52046
fn output(
&mut self,
display_states: DisplayStates,
ds_status: DirstateStatus,
) -> Result<(), CommandError> {
if display_states.modified {
self.display(b"M ", "status.modified", ds_status.modified)?;
}
if display_states.added {
self.display(b"A ", "status.added", ds_status.added)?;
}
if display_states.removed {
self.display(b"R ", "status.removed", ds_status.removed)?;
}
if display_states.deleted {
self.display(b"! ", "status.deleted", ds_status.deleted)?;
}
if display_states.unknown {
self.display(b"? ", "status.unknown", ds_status.unknown)?;
}
if display_states.ignored {
self.display(b"I ", "status.ignored", ds_status.ignored)?;
}
if display_states.clean {
self.display(b"C ", "status.clean", ds_status.clean)?;
}
Ok(())
}
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 }
Raphaël Gomès
rhg: fix race when an ambiguous file is deleted on disk...
r51121 /// Outcome of the additional check for an ambiguous tracked file
enum UnsureOutcome {
/// The file is actually clean
Clean,
/// The file has been modified
Modified,
/// The file was deleted on disk (or became another type of fs entry)
Deleted,
}
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 /// Check if a file is modified by comparing actual repo store and file system.
///
/// This meant to be used for those that the dirstate cannot resolve, due
/// to time resolution limits.
Simon Sapin
rhg: Rename cat_file_is_modified...
r49167 fn unsure_is_modified(
Arseniy Alekseyev
rhg: parallellize computation of [unsure_is_modified]...
r50412 working_directory_vfs: hg::vfs::Vfs,
store_vfs: hg::vfs::Vfs,
Arseniy Alekseyev
rhg: implement checkexec to support weird filesystems...
r50789 check_exec: bool,
Simon Sapin
rhg: Reuse manifest when checking status of multiple ambiguous files...
r48778 manifest: &Manifest,
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 hg_path: &HgPath,
Raphaël Gomès
rhg: fix race when an ambiguous file is deleted on disk...
r51121 ) -> Result<UnsureOutcome, HgError> {
Arseniy Alekseyev
rhg: parallellize computation of [unsure_is_modified]...
r50412 let vfs = working_directory_vfs;
Simon Sapin
rhg: Update the dirstate on disk after status...
r49250 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
Simon Sapin
rhg: Fix status desambiguation of symlinks and executable files...
r49168 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
let is_symlink = fs_metadata.file_type().is_symlink();
Arseniy Alekseyev
rhg: implement checkexec to support weird filesystems...
r50789
let entry = manifest
.find_by_path(hg_path)?
.expect("ambgious file not in p1");
dirstate: remove `lastnormaltime` mechanism...
r49220 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
// dirstate
Simon Sapin
rhg: Fix status desambiguation of symlinks and executable files...
r49168 let fs_flags = if is_symlink {
Some(b'l')
Arseniy Alekseyev
rhg: implement checkexec to support weird filesystems...
r50789 } else if check_exec && has_exec_bit(&fs_metadata) {
Simon Sapin
rhg: Fix status desambiguation of symlinks and executable files...
r49168 Some(b'x')
} else {
None
};
Arseniy Alekseyev
rhg: implement checkexec to support weird filesystems...
r50789 let entry_flags = if check_exec {
entry.flags
Raphaël Gomès
rust-clippy: fix warning about nested ifs
r50847 } else if entry.flags == Some(b'x') {
None
Arseniy Alekseyev
rhg: implement checkexec to support weird filesystems...
r50789 } else {
Raphaël Gomès
rust-clippy: fix warning about nested ifs
r50847 entry.flags
Arseniy Alekseyev
rhg: implement checkexec to support weird filesystems...
r50789 };
if entry_flags != fs_flags {
Raphaël Gomès
rhg: fix race when an ambiguous file is deleted on disk...
r51121 return Ok(UnsureOutcome::Modified);
Simon Sapin
rhg: Fix status desambiguation of symlinks and executable files...
r49168 }
Arseniy Alekseyev
rhg: parallellize computation of [unsure_is_modified]...
r50412 let filelog = hg::filelog::Filelog::open_vfs(&store_vfs, hg_path)?;
Simon Sapin
rhg: Skip reading the contents of ambiguous files in some cases...
r49302 let fs_len = fs_metadata.len();
Raphaël Gomès
rust: don't swallow valuable error information...
r50269 let file_node = entry.node_id()?;
let filelog_entry = filelog.entry_for_node(file_node).map_err(|_| {
HgError::corrupted(format!(
Arseniy Alekseyev
rhg: nicer error message
r50989 "filelog {:?} missing node {:?} from manifest",
hg_path, file_node
Raphaël Gomès
rust: don't swallow valuable error information...
r50269 ))
})?;
Simon Sapin
rhg: desambiguate status without decompressing filelog if possible...
r49378 if filelog_entry.file_data_len_not_equal_to(fs_len) {
// No need to read file contents:
// it cannot be equal if it has a different length.
Raphaël Gomès
rhg: fix race when an ambiguous file is deleted on disk...
r51121 return Ok(UnsureOutcome::Modified);
Simon Sapin
rhg: desambiguate status without decompressing filelog if possible...
r49378 }
let p1_filelog_data = filelog_entry.data()?;
let p1_contents = p1_filelog_data.file_data()?;
if p1_contents.len() as u64 != fs_len {
// No need to read file contents:
Simon Sapin
rhg: Skip reading the contents of ambiguous files in some cases...
r49302 // it cannot be equal if it has a different length.
Raphaël Gomès
rhg: fix race when an ambiguous file is deleted on disk...
r51121 return Ok(UnsureOutcome::Modified);
Simon Sapin
rhg: Skip reading the contents of ambiguous files in some cases...
r49302 }
Georges Racinet
rhg: Initial support for the 'status' command...
r47578
Simon Sapin
rhg: Fix status desambiguation of symlinks and executable files...
r49168 let fs_contents = if is_symlink {
get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
} else {
vfs.read(fs_path)?
};
Raphaël Gomès
rhg: fix race when an ambiguous file is deleted on disk...
r51121
Ok(if p1_contents != &*fs_contents {
UnsureOutcome::Modified
} else {
UnsureOutcome::Clean
})
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 }