##// END OF EJS Templates
share: make it possible to control the working copy format variant...
share: make it possible to control the working copy format variant A share will use the same format as its source for the store, but there are no reason to not lets it control the working copy variant at creation time. So we make it so. Differential Revision: https://phab.mercurial-scm.org/D11892

File last commit:

r49285:473af5cb default
r49297:bf2738e0 default
Show More
status.rs
482 lines | 15.2 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;
Simon Sapin
rhg: Add support for `rhg status -n`...
r49171 use crate::ui::Ui;
Simon Sapin
rhg: refactor relativize_path into a struct + method...
r49284 use crate::utils::path_utils::RelativizePaths;
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 use clap::{Arg, SubCommand};
Simon Sapin
rhg: Add support for `rhg status -n`...
r49171 use format_bytes::format_bytes;
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 use hg;
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::dirstate::RANGE_MASK_31BIT;
use hg::errors::{HgError, IoResultExt};
use hg::lock::LockError;
Simon Sapin
rhg: Reuse manifest when checking status of multiple ambiguous files...
r48778 use hg::manifest::Manifest;
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 use hg::matchers::AlwaysMatcher;
use hg::repo::Repo;
Simon Sapin
rhg: Fix status desambiguation of symlinks and executable files...
r49168 use hg::utils::files::get_bytes_from_os_string;
Simon Sapin
rhg: Add support for ui.ignore and ui.ignore.* config...
r49282 use hg::utils::files::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};
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 use hg::StatusOptions;
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 use log::{info, warn};
Simon Sapin
rhg: Update the dirstate on disk after status...
r49250 use std::io;
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.
";
pub fn args() -> clap::App<'static, 'static> {
SubCommand::with_name("status")
.alias("st")
.about(HELP_TEXT)
.arg(
Arg::with_name("all")
.help("show status of all files")
.short("-A")
.long("--all"),
)
.arg(
Arg::with_name("modified")
.help("show only modified files")
.short("-m")
.long("--modified"),
)
.arg(
Arg::with_name("added")
.help("show only added files")
.short("-a")
.long("--added"),
)
.arg(
Arg::with_name("removed")
.help("show only removed files")
.short("-r")
.long("--removed"),
)
.arg(
Arg::with_name("clean")
.help("show only clean files")
.short("-c")
.long("--clean"),
)
.arg(
Arg::with_name("deleted")
.help("show only deleted files")
.short("-d")
.long("--deleted"),
)
.arg(
Arg::with_name("unknown")
.help("show only unknown (not tracked) files")
.short("-u")
.long("--unknown"),
)
.arg(
Arg::with_name("ignored")
.help("show only ignored files")
.short("-i")
.long("--ignored"),
)
Simon Sapin
rhg: Add support for `rhg status -n`...
r49171 .arg(
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 Arg::with_name("copies")
.help("show source of copied files (DEFAULT: ui.statuscopies)")
.short("-C")
.long("--copies"),
)
.arg(
Simon Sapin
rhg: Add support for `rhg status -n`...
r49171 Arg::with_name("no-status")
.help("hide status prefix")
.short("-n")
.long("--no-status"),
)
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)
}
}
pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
let status_enabled_default = false;
let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
if !status_enabled.unwrap_or(status_enabled_default) {
return Err(CommandError::unsupported(
"status is experimental in rhg (enable it with 'rhg.status = true' \
or enable fallback with 'rhg.on-unsupported = fallback')"
));
}
Pulkit Goyal
rhg: fallback if tweakdefaults or statuscopies is enabled with status...
r48985 // TODO: lift these limitations
Simon Sapin
rhg: Propagate config errors in `rhg status`...
r49160 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
Pulkit Goyal
rhg: fallback if tweakdefaults or statuscopies is enabled with status...
r48985 return Err(CommandError::unsupported(
"ui.tweakdefaults is not yet supported with rhg status",
));
}
Simon Sapin
rhg: Propagate config errors in `rhg status`...
r49160 if invocation.config.get_bool(b"ui", b"statuscopies")? {
Pulkit Goyal
rhg: fallback if tweakdefaults or statuscopies is enabled with status...
r48985 return Err(CommandError::unsupported(
"ui.statuscopies is not yet supported with rhg status",
));
}
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: Add support for `rhg status --copies`...
r49285 let all = args.is_present("all");
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 {
modified: args.is_present("modified"),
added: args.is_present("added"),
removed: args.is_present("removed"),
clean: args.is_present("clean"),
deleted: args.is_present("deleted"),
unknown: args.is_present("unknown"),
ignored: args.is_present("ignored"),
};
if requested.is_empty() {
DEFAULT_DISPLAY_STATES
} else {
requested
}
};
Simon Sapin
rhg: Add support for `rhg status -n`...
r49171 let no_status = args.is_present("no-status");
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 let list_copies = all
|| args.is_present("copies")
|| 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
if repo.has_sparse() || repo.has_narrow() {
return Err(CommandError::unsupported(
"rhg status is not supported for sparse checkouts or narrow clones yet"
));
}
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
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 let options = StatusOptions {
// we're currently supporting file systems with exec flags only
// anyway
check_exec: true,
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,
};
Simon Sapin
rust: Add Repo::dirstate_map and use it in `rhg status`...
r48768 let (mut ds_status, pattern_warnings) = dmap.status(
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 &AlwaysMatcher,
repo.working_directory_path().to_owned(),
Simon Sapin
rhg: Add support for ui.ignore and ui.ignore.* config...
r49282 ignore_files(repo, config),
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 options,
)?;
if !pattern_warnings.is_empty() {
warn!("Pattern warnings: {:?}", &pattern_warnings);
}
if !ds_status.bad.is_empty() {
warn!("Bad matches {:?}", &(ds_status.bad))
}
Simon Sapin
rust: Move "lookup" a.k.a. "unsure" paths into `DirstateStatus` struct...
r47880 if !ds_status.unsure.is_empty() {
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 info!(
"Files to be rechecked by retrieval from filelog: {:?}",
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 );
}
Simon Sapin
rhg: Update the dirstate on disk after status...
r49250 let mut fixup = Vec::new();
Simon Sapin
rhg: Sort `rhg status` output correctly...
r48112 if !ds_status.unsure.is_empty()
&& (display_states.modified || display_states.clean)
{
Simon Sapin
rhg: Reuse manifest when checking status of multiple ambiguous files...
r48778 let p1 = repo.dirstate_parents()?.p1;
let manifest = repo.manifest_for_node(p1).map_err(|e| {
CommandError::from((e, &*format!("{:x}", p1.short())))
})?;
Simon Sapin
rust: Move "lookup" a.k.a. "unsure" paths into `DirstateStatus` struct...
r47880 for to_check in ds_status.unsure {
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 if unsure_is_modified(repo, &manifest, &to_check.path)? {
Simon Sapin
rhg: Sort `rhg status` output correctly...
r48112 if display_states.modified {
ds_status.modified.push(to_check);
}
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 } else {
Simon Sapin
rhg: Sort `rhg status` output correctly...
r48112 if display_states.clean {
Simon Sapin
rhg: Update the dirstate on disk after status...
r49250 ds_status.clean.push(to_check.clone());
Simon Sapin
rhg: Sort `rhg status` output correctly...
r48112 }
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 fixup.push(to_check.path.into_owned())
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 }
}
Simon Sapin
rhg: Sort `rhg status` output correctly...
r48112 }
Simon Sapin
rhg: refactor display_status_paths with a struct for common arguments...
r49283 let relative_paths = (!ui.plain())
&& config
.get_option(b"commands", b"status.relative")?
.unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
let output = DisplayStatusPaths {
ui,
no_status,
Simon Sapin
rhg: refactor relativize_path into a struct + method...
r49284 relativize: if relative_paths {
Some(RelativizePaths::new(repo)?)
} else {
None
},
Simon Sapin
rhg: refactor display_status_paths with a struct for common arguments...
r49283 };
Simon Sapin
rhg: Sort `rhg status` output correctly...
r48112 if display_states.modified {
Simon Sapin
rhg: refactor display_status_paths with a struct for common arguments...
r49283 output.display(b"M", ds_status.modified)?;
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 }
if display_states.added {
Simon Sapin
rhg: refactor display_status_paths with a struct for common arguments...
r49283 output.display(b"A", ds_status.added)?;
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 }
if display_states.removed {
Simon Sapin
rhg: refactor display_status_paths with a struct for common arguments...
r49283 output.display(b"R", ds_status.removed)?;
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 }
if display_states.deleted {
Simon Sapin
rhg: refactor display_status_paths with a struct for common arguments...
r49283 output.display(b"!", ds_status.deleted)?;
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 }
if display_states.unknown {
Simon Sapin
rhg: refactor display_status_paths with a struct for common arguments...
r49283 output.display(b"?", ds_status.unknown)?;
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 }
if display_states.ignored {
Simon Sapin
rhg: refactor display_status_paths with a struct for common arguments...
r49283 output.display(b"I", ds_status.ignored)?;
Simon Sapin
rhg: Sort `rhg status` output correctly...
r48112 }
if display_states.clean {
Simon Sapin
rhg: refactor display_status_paths with a struct for common arguments...
r49283 output.display(b"C", ds_status.clean)?;
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 }
Simon Sapin
rhg: Update the dirstate on disk after status...
r49250
let mut dirstate_write_needed = ds_status.dirty;
let filesystem_time_at_status_start = ds_status
.filesystem_time_at_status_start
.map(TruncatedTimestamp::from);
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.
let fs_metadata = repo
.working_directory_vfs()
.symlink_metadata(&fs_path)?;
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();
let size = fs_metadata.len() as u32 & RANGE_MASK_31BIT;
let mut entry = dmap
.get(&hg_path)?
.expect("ambiguous file not in dirstate");
entry.set_clean(mode, size, mtime);
dmap.add_file(&hg_path, entry)?;
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.
}
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>,
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: 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
rhg: Add support for `rhg status --copies`...
r49285 for StatusPath { path, copy_source } in paths {
Simon Sapin
rhg: refactor relativize_path into a struct + method...
r49284 let relative;
let path = if let Some(relativize) = &self.relativize {
relative = relativize.relativize(&path);
&*relative
} else {
path.as_bytes()
};
Simon Sapin
rhg: refactor display_status_paths with a struct for common arguments...
r49283 // TODO optim, probably lots of unneeded copies here, especially
// if out stream is buffered
if self.no_status {
Simon Sapin
rhg: refactor relativize_path into a struct + method...
r49284 self.ui.write_stdout(&format_bytes!(b"{}\n", path))?
Simon Sapin
rhg: refactor display_status_paths with a struct for common arguments...
r49283 } else {
self.ui.write_stdout(&format_bytes!(
b"{} {}\n",
status_prefix,
path
Simon Sapin
rhg: refactor relativize_path into a struct + method...
r49284 ))?
Simon Sapin
rhg: refactor display_status_paths with a struct for common arguments...
r49283 }
Simon Sapin
rhg: Add support for `rhg status --copies`...
r49285 if let Some(source) = copy_source {
self.ui.write_stdout(&format_bytes!(
b" {}\n",
source.as_bytes()
))?
}
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 }
}
/// 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(
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 repo: &Repo,
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,
Simon Sapin
rhg: Reuse manifest when checking status of multiple ambiguous files...
r48778 ) -> Result<bool, HgError> {
Simon Sapin
rhg: Fix status desambiguation of symlinks and executable files...
r49168 let vfs = repo.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();
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')
} else if has_exec_bit(&fs_metadata) {
Some(b'x')
} else {
None
};
Simon Sapin
rhg: Also parse flags in the manifest parser...
r49166 let entry = manifest
Simon Sapin
rhg: Reuse manifest when checking status of multiple ambiguous files...
r48778 .find_file(hg_path)?
.expect("ambgious file not in p1");
Simon Sapin
rhg: Fix status desambiguation of symlinks and executable files...
r49168 if entry.flags != fs_flags {
return Ok(true);
}
Simon Sapin
rhg: Reuse manifest when checking status of multiple ambiguous files...
r48778 let filelog = repo.filelog(hg_path)?;
Simon Sapin
rhg: Also parse flags in the manifest parser...
r49166 let filelog_entry =
filelog.data_for_node(entry.node_id()?).map_err(|_| {
HgError::corrupted("filelog missing node from manifest")
})?;
Simon Sapin
rhg: Reuse manifest when checking status of multiple ambiguous files...
r48778 let contents_in_p1 = filelog_entry.data()?;
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)?
};
Simon Sapin
rhg: Update the dirstate on disk after status...
r49250 Ok(contents_in_p1 != &*fs_contents)
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 }