##// END OF EJS Templates
rust: blanket implementation of Graph for Graph references...
rust: blanket implementation of Graph for Graph references The need comes from the fact that `AncestorsIterator` and many Graph-related algorithms take ownership of the `Graph` they work with. This, in turn is due to them needing to accept the `Index` instances that are provided by the Python layers (that neither rhg nor `RHGitaly` use, of course): the fact that nowadays the Python layer holds an object that is itself implemented in Rust does not change the core problem that they cannot be tracked by the borrow checker. Even though it looks like cloning `Changelog` would be cheap, it seems hard to guarantee that on the long run. The object is already too rich for us to be comfortable with it, when using references is the most natural and guaranteed way of proceeding. The added test seems a bit superfleous, but it will act as a reminder that this feature is really useful until something in the Mercurial code base actually uses it.

File last commit:

r52048:ac3859a8 default
r52512:b08c5fbe stable
Show More
main.rs
849 lines | 28.0 KiB | application/rls-services+xml | RustLexer
Antoine Cezar
rhg: Add debug timing...
r46101 extern crate log;
Simon Sapin
rhg: $HG_PENDING is not supported...
r49159 use crate::error::CommandError;
Raphaël Gomès
rhg: signal when falling back in logs...
r49622 use crate::ui::{local_to_utf8, Ui};
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 use clap::{command, Arg, ArgMatches};
Simon Sapin
rhg: Fall back to Python if unsupported extensions are enabled...
r47467 use format_bytes::{format_bytes, join};
Arseniy Alekseyev
rhg: central treatment of PLAIN and PLAINEXCEPT
r50407 use hg::config::{Config, ConfigSource, PlainInfo};
Simon Sapin
rhg: Move `Repo` object creation into `main()`...
r47335 use hg::repo::{Repo, RepoError};
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes};
use hg::utils::SliceExt;
auto-upgrade: introduce a way to auto-upgrade to/from share-safe...
r50087 use hg::{exit_codes, requirements};
Arseniy Alekseyev
rhg: central treatment of PLAIN and PLAINEXCEPT
r50407 use std::borrow::Cow;
Raphaël Gomès
rhg: support the new extension suboptions syntax...
r49270 use std::collections::HashSet;
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 use std::ffi::OsString;
Raphaël Gomès
rhg: use `Command::exec` instead of `Command::status`...
r50043 use std::os::unix::prelude::CommandExt;
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 use std::path::PathBuf;
Simon Sapin
rhg: Add support for automatic fallback to Python...
r47425 use std::process::Command;
Antoine Cezar
rhg: add a limited `rhg root` subcommand...
r45593
Simon Sapin
rhg: Add support for the blackbox extension...
r47343 mod blackbox;
Simon Sapin
rhg: Add support for colored output...
r49584 mod color;
Antoine Cezar
rhg: add Command trait for subcommands implemented by rhg...
r45515 mod error;
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592 mod ui;
Pulkit Goyal
rhg: refactor function to relativize paths in utils...
r48988 pub mod utils {
pub mod path_utils;
}
Antoine Cezar
rhg: add rhg crate...
r45503
Simon Sapin
rhg: Add support for the blackbox extension...
r47343 fn main_with_result(
Arseniy Alekseyev
rhg: refactor to pass argv down, instead of caling args_os()...
r49961 argv: Vec<OsString>,
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 process_start_time: &blackbox::ProcessStartTime,
Simon Sapin
rhg: Add support for the blackbox extension...
r47343 ui: &ui::Ui,
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 repo: Result<&Repo, &NoRepoInCwdError>,
config: &Config,
Simon Sapin
rhg: Add support for the blackbox extension...
r47343 ) -> Result<(), CommandError> {
Simon Sapin
rhg: Colorize `rhg status` output when appropriate...
r49585 check_unsupported(config, repo)?;
Simon Sapin
rhg: Fall back to Python if unsupported extensions are enabled...
r47467
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 let app = command!()
.subcommand_required(true)
Simon Sapin
rhg: Use clap’s support for global CLI arguments...
r47351 .arg(
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 Arg::new("repository")
Simon Sapin
rhg: Use clap’s support for global CLI arguments...
r47351 .help("repository root directory")
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 .short('R')
Simon Sapin
rhg: Use clap’s support for global CLI arguments...
r47351 .value_name("REPO")
// Both ok: `hg -R ./foo log` or `hg log -R ./foo`
.global(true),
)
.arg(
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 Arg::new("config")
Simon Sapin
rhg: Use clap’s support for global CLI arguments...
r47351 .help("set/override config option (use 'section.name=value')")
.value_name("CONFIG")
.global(true)
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 .long("config")
Simon Sapin
rhg: Use clap’s support for global CLI arguments...
r47351 // Ok: `--config section.key1=val --config section.key2=val2`
// Not ok: `--config section.key1=val section.key2=val2`
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 .action(clap::ArgAction::Append),
Simon Sapin
rhg: Use clap’s support for global CLI arguments...
r47351 )
Simon Sapin
rhg: Add support for --cwd...
r47470 .arg(
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 Arg::new("cwd")
Simon Sapin
rhg: Add support for --cwd...
r47470 .help("change working directory")
.value_name("DIR")
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 .long("cwd")
Simon Sapin
rhg: Add support for --cwd...
r47470 .global(true),
)
Simon Sapin
rhg: Add parsing for the --color global CLI argument...
r49583 .arg(
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 Arg::new("color")
Simon Sapin
rhg: Add parsing for the --color global CLI argument...
r49583 .help("when to colorize (boolean, always, auto, never, or debug)")
.value_name("TYPE")
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 .long("color")
Simon Sapin
rhg: Add parsing for the --color global CLI argument...
r49583 .global(true),
)
Simon Sapin
rhg: Replace subcommand boilerplate with a macro...
r47252 .version("0.0.1");
let app = add_subcommand_args(app);
Antoine Cezar
rhg: add a limited `rhg root` subcommand...
r45593
Raphaël Gomès
rust: run `cargo clippy`...
r50809 let matches = app.try_get_matches_from(argv.iter())?;
Simon Sapin
rhg: Add support for -R and --repository command-line arguments...
r47253
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 let (subcommand_name, subcommand_args) =
matches.subcommand().expect("subcommand required");
Raphaël Gomès
rhg: fallback if the current command has any generic hook defined...
r48889
Raphaël Gomès
rhg: fallback if `defaults` config is set for the current command...
r48890 // Mercurial allows users to define "defaults" for commands, fallback
// if a default is detected for the current command
Spencer Baugh
rhg: allow setting defaults.cmd to an empty string...
r51779 let defaults = config.get_str(b"defaults", subcommand_name.as_bytes())?;
match defaults {
// Programmatic usage might set defaults to an empty string to unset
// it; allow that
None | Some("") => {}
Some(_) => {
let msg = "`defaults` config set";
return Err(CommandError::unsupported(msg));
}
Raphaël Gomès
rhg: fallback if `defaults` config is set for the current command...
r48890 }
Raphaël Gomès
rhg: fallback if the current command has any generic hook defined...
r48889 for prefix in ["pre", "post", "fail"].iter() {
// Mercurial allows users to define generic hooks for commands,
// fallback if any are detected
let item = format!("{}-{}", prefix, subcommand_name);
Raphaël Gomès
rust-config: add config getters that don't fall back to defaults...
r51657 let hook_for_command =
config.get_str_no_default(b"hooks", item.as_bytes())?;
Raphaël Gomès
rhg: fallback if the current command has any generic hook defined...
r48889 if hook_for_command.is_some() {
let msg = format!("{}-{} hook defined", prefix, subcommand_name);
return Err(CommandError::unsupported(msg));
}
}
Simon Sapin
rhg: Replace subcommand boilerplate with a macro...
r47252 let run = subcommand_run_fn(subcommand_name)
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 .expect("unknown subcommand name from clap despite Command::subcommand_required");
Antoine Cezar
rhg: add a limited `rhg root` subcommand...
r45593
Simon Sapin
rhg: Add support for the blackbox extension...
r47343 let invocation = CliInvocation {
Simon Sapin
rhg: Group values passed to every sub-command into a struct...
r47334 ui,
subcommand_args,
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 config,
repo,
Simon Sapin
rhg: Add support for the blackbox extension...
r47343 };
Raphaël Gomès
rhg: fall back if subrepos are detected...
r48891
if let Ok(repo) = repo {
// We don't support subrepos, fallback if the subrepos file is present
if repo.working_directory_vfs().join(".hgsub").exists() {
let msg = "subrepos (.hgsub is present)";
return Err(CommandError::unsupported(msg));
}
}
Raphaël Gomès
rhg: don't run `blackbox` if not activated...
r49243 if config.is_extension_enabled(b"blackbox") {
let blackbox =
blackbox::Blackbox::new(&invocation, process_start_time)?;
Arseniy Alekseyev
rhg: refactor to pass argv down, instead of caling args_os()...
r49961 blackbox.log_command_start(argv.iter());
Raphaël Gomès
rhg: don't run `blackbox` if not activated...
r49243 let result = run(&invocation);
Arseniy Alekseyev
rhg: refactor to pass argv down, instead of caling args_os()...
r49961 blackbox.log_command_end(
argv.iter(),
exit_code(
&result,
// TODO: show a warning or combine with original error if
// `get_bool` returns an error
config
.get_bool(b"ui", b"detailed-exit-code")
.unwrap_or(false),
),
);
Raphaël Gomès
rhg: don't run `blackbox` if not activated...
r49243 result
} else {
run(&invocation)
}
Simon Sapin
rhg: Remove error message on unsupported CLI arguments...
r47333 }
Simon Sapin
rhg: Replace subcommand boilerplate with a macro...
r47252
Arseniy Alekseyev
rhg: refactor to pass argv down, instead of caling args_os()...
r49961 fn rhg_main(argv: Vec<OsString>) -> ! {
Simon Sapin
rhg: Add support for the blackbox extension...
r47343 // Run this first, before we find out if the blackbox extension is even
// enabled, in order to include everything in-between in the duration
// measurements. Reading config files can be slow if they’re on NFS.
let process_start_time = blackbox::ProcessStartTime::now();
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 env_logger::init();
Simon Sapin
rhg: Remove error message on unsupported CLI arguments...
r47333
Raphaël Gomès
rust: fix thread cap (for real this time)...
r51248 // Make sure nothing in a future version of `rhg` sets the global
// threadpool before we can cap default threads. (This is also called
// in core because Python uses the same code path, we're adding a
// redundant check.)
hg::utils::cap_default_rayon_threads()
.expect("Rayon threadpool already initialized");
Arseniy Alekseyev
rhg: refactor to pass argv down, instead of caling args_os()...
r49961 let early_args = EarlyArgs::parse(&argv);
Simon Sapin
rhg: Add support for --cwd...
r47470
let initial_current_dir = early_args.cwd.map(|cwd| {
let cwd = get_path_from_bytes(&cwd);
std::env::current_dir()
.and_then(|initial| {
std::env::set_current_dir(cwd)?;
Ok(initial)
})
.unwrap_or_else(|error| {
exit(
Arseniy Alekseyev
rhg: refactor to pass argv down, instead of caling args_os()...
r49961 &argv,
Simon Sapin
rhg: Add support for --cwd...
r47470 &None,
Simon Sapin
rhg: Make Ui::new falliable, add Ui::new_infallible...
r49582 &Ui::new_infallible(&Config::empty()),
Simon Sapin
rhg: Add support for --cwd...
r47470 OnUnsupported::Abort,
Err(CommandError::abort(format!(
"abort: {}: '{}'",
error,
cwd.display()
))),
Pulkit Goyal
rhg: add support for detailed exit code for ConfigParseError...
r47576 false,
Simon Sapin
rhg: Add support for --cwd...
r47470 )
})
});
Pulkit Goyal
rhg: split non_repo_config and `--config` loading in different functions...
r48198 let mut non_repo_config =
Config::load_non_repo().unwrap_or_else(|error| {
Simon Sapin
rhg: Add a `rhg.on-unsupported` configuration key...
r47424 // Normally this is decided based on config, but we don’t have that
// available. As of this writing config loading never returns an
// "unsupported" error but that is not enforced by the type system.
let on_unsupported = OnUnsupported::Abort;
Pulkit Goyal
rhg: add support for detailed exit code for ConfigParseError...
r47576 exit(
Arseniy Alekseyev
rhg: refactor to pass argv down, instead of caling args_os()...
r49961 &argv,
Pulkit Goyal
rhg: add support for detailed exit code for ConfigParseError...
r47576 &initial_current_dir,
Simon Sapin
rhg: Make Ui::new falliable, add Ui::new_infallible...
r49582 &Ui::new_infallible(&Config::empty()),
Pulkit Goyal
rhg: add support for detailed exit code for ConfigParseError...
r47576 on_unsupported,
Err(error.into()),
false,
)
Simon Sapin
rhg: Add a `rhg.on-unsupported` configuration key...
r47424 });
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423
Pulkit Goyal
rhg: split non_repo_config and `--config` loading in different functions...
r48198 non_repo_config
Simon Sapin
rhg: Add parsing for the --color global CLI argument...
r49583 .load_cli_args(early_args.config, early_args.color)
Pulkit Goyal
rhg: split non_repo_config and `--config` loading in different functions...
r48198 .unwrap_or_else(|error| {
exit(
Arseniy Alekseyev
rhg: refactor to pass argv down, instead of caling args_os()...
r49961 &argv,
Pulkit Goyal
rhg: split non_repo_config and `--config` loading in different functions...
r48198 &initial_current_dir,
Simon Sapin
rhg: Make Ui::new falliable, add Ui::new_infallible...
r49582 &Ui::new_infallible(&non_repo_config),
Arseniy Alekseyev
rhg: only complain about poorly configured fallback when falling back...
r49176 OnUnsupported::from_config(&non_repo_config),
Pulkit Goyal
rhg: split non_repo_config and `--config` loading in different functions...
r48198 Err(error.into()),
non_repo_config
.get_bool(b"ui", b"detailed-exit-code")
.unwrap_or(false),
)
});
Simon Sapin
rhg: Fall back to Python on --repository with an URL...
r47463 if let Some(repo_path_bytes) = &early_args.repo {
lazy_static::lazy_static! {
static ref SCHEME_RE: regex::bytes::Regex =
// Same as `_matchscheme` in `mercurial/util.py`
regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
}
Raphaël Gomès
rust: run `cargo clippy`...
r50809 if SCHEME_RE.is_match(repo_path_bytes) {
Simon Sapin
rhg: Fall back to Python on --repository with an URL...
r47463 exit(
Arseniy Alekseyev
rhg: refactor to pass argv down, instead of caling args_os()...
r49961 &argv,
Simon Sapin
rhg: Add support for --cwd...
r47470 &initial_current_dir,
Simon Sapin
rhg: Make Ui::new falliable, add Ui::new_infallible...
r49582 &Ui::new_infallible(&non_repo_config),
Arseniy Alekseyev
rhg: only complain about poorly configured fallback when falling back...
r49176 OnUnsupported::from_config(&non_repo_config),
Simon Sapin
rhg: Fall back to Python on --repository with an URL...
r47463 Err(CommandError::UnsupportedFeature {
message: format_bytes!(
b"URL-like --repository {}",
repo_path_bytes
),
}),
Pulkit Goyal
rhg: add support for detailed exit code for ConfigParseError...
r47576 // TODO: show a warning or combine with original error if
// `get_bool` returns an error
non_repo_config
.get_bool(b"ui", b"detailed-exit-code")
.unwrap_or(false),
Simon Sapin
rhg: Fall back to Python on --repository with an URL...
r47463 )
}
}
Raphaël Gomès
rust: run `cargo clippy`...
r50809 let repo_arg = early_args.repo.unwrap_or_default();
Pulkit Goyal
rhg: read [paths] for `--repository` value...
r48196 let repo_path: Option<PathBuf> = {
if repo_arg.is_empty() {
None
} else {
let local_config = {
if std::env::var_os("HGRCSKIPREPO").is_none() {
Pulkit Goyal
rhg: look for repository in ancestors also instead of cwd only...
r48197 // TODO: handle errors from find_repo_root
if let Ok(current_dir_path) = Repo::find_repo_root() {
Pulkit Goyal
rhg: read [paths] for `--repository` value...
r48196 let config_files = vec![
ConfigSource::AbsPath(
current_dir_path.join(".hg/hgrc"),
),
ConfigSource::AbsPath(
current_dir_path.join(".hg/hgrc-not-shared"),
),
];
// TODO: handle errors from
// `load_from_explicit_sources`
Config::load_from_explicit_sources(config_files).ok()
} else {
None
}
} else {
None
}
};
let non_repo_config_val = {
let non_repo_val = non_repo_config.get(b"paths", &repo_arg);
match &non_repo_val {
Raphaël Gomès
rust: run `cargo clippy`...
r50809 Some(val) if !val.is_empty() => home::home_dir()
Pulkit Goyal
rhg: read [paths] for `--repository` value...
r48196 .unwrap_or_else(|| PathBuf::from("~"))
.join(get_path_from_bytes(val))
.canonicalize()
// TODO: handle error and make it similar to python
// implementation maybe?
.ok(),
_ => None,
}
};
let config_val = match &local_config {
None => non_repo_config_val,
Some(val) => {
let local_config_val = val.get(b"paths", &repo_arg);
match &local_config_val {
Raphaël Gomès
rust: run `cargo clippy`...
r50809 Some(val) if !val.is_empty() => {
Pulkit Goyal
rhg: read [paths] for `--repository` value...
r48196 // presence of a local_config assures that
// current_dir
// wont result in an Error
let canpath = hg::utils::current_dir()
.unwrap()
.join(get_path_from_bytes(val))
.canonicalize();
canpath.ok().or(non_repo_config_val)
}
_ => non_repo_config_val,
}
}
};
Raphaël Gomès
rust-clippy: fix remaining warnings in `rhg`...
r50826 config_val
.or_else(|| Some(get_path_from_bytes(&repo_arg).to_path_buf()))
Pulkit Goyal
rhg: read [paths] for `--repository` value...
r48196 }
};
Raphaël Gomès
rhg: stop shadowing `exit` function...
r50463 let simple_exit =
Arseniy Alekseyev
rhg: share some code
r50410 |ui: &Ui, config: &Config, result: Result<(), CommandError>| -> ! {
exit(
&argv,
&initial_current_dir,
ui,
OnUnsupported::from_config(config),
result,
// TODO: show a warning or combine with original error if
// `get_bool` returns an error
non_repo_config
.get_bool(b"ui", b"detailed-exit-code")
.unwrap_or(false),
)
};
let early_exit = |config: &Config, error: CommandError| -> ! {
Raphaël Gomès
rust: run `cargo clippy`...
r50809 simple_exit(&Ui::new_infallible(config), config, Err(error))
Arseniy Alekseyev
rhg: share some code
r50410 };
Pulkit Goyal
rhg: read [paths] for `--repository` value...
r48196 let repo_result = match Repo::find(&non_repo_config, repo_path.to_owned())
{
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 Ok(repo) => Ok(repo),
Err(RepoError::NotFound { at }) if repo_path.is_none() => {
// Not finding a repo is not fatal yet, if `-R` was not given
Err(NoRepoInCwdError { cwd: at })
Simon Sapin
rhg: Add support for the blackbox extension...
r47343 }
Arseniy Alekseyev
rhg: share some code
r50410 Err(error) => early_exit(&non_repo_config, error.into()),
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 };
let config = if let Ok(repo) = &repo_result {
repo.config()
} else {
&non_repo_config
};
Arseniy Alekseyev
rhg: central treatment of PLAIN and PLAINEXCEPT
r50407
let mut config_cow = Cow::Borrowed(config);
Arseniy Alekseyev
rhg: centralize PlainInfo
r50408 config_cow.to_mut().apply_plain(PlainInfo::from_env());
Arseniy Alekseyev
rhg: support tweakdefaults
r50409 if !ui::plain(Some("tweakdefaults"))
&& config_cow
.as_ref()
.get_bool(b"ui", b"tweakdefaults")
Raphaël Gomès
rust: run `cargo clippy`...
r50809 .unwrap_or_else(|error| early_exit(config, error.into()))
Arseniy Alekseyev
rhg: support tweakdefaults
r50409 {
config_cow.to_mut().tweakdefaults()
};
Arseniy Alekseyev
rhg: central treatment of PLAIN and PLAINEXCEPT
r50407 let config = config_cow.as_ref();
Raphaël Gomès
rust: run `cargo clippy`...
r50809 let ui = Ui::new(config)
.unwrap_or_else(|error| early_exit(config, error.into()));
Raphaël Gomès
rhg: add a config option to fall back immediately...
r50464
if let Ok(true) = config.get_bool(b"rhg", b"fallback-immediately") {
exit(
&argv,
&initial_current_dir,
&ui,
Arseniy Alekseyev
rhg: tweak rhg fallback code structure...
r51702 OnUnsupported::fallback(config),
Raphaël Gomès
rhg: add a config option to fall back immediately...
r50464 Err(CommandError::unsupported(
"`rhg.fallback-immediately is true`",
)),
false,
)
}
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 let result = main_with_result(
Arseniy Alekseyev
rhg: refactor to pass argv down, instead of caling args_os()...
r49961 argv.iter().map(|s| s.to_owned()).collect(),
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 &process_start_time,
&ui,
repo_result.as_ref(),
config,
);
Raphaël Gomès
rust: run `cargo clippy`...
r50809 simple_exit(&ui, config, result)
Simon Sapin
rhg: Add support for the blackbox extension...
r47343 }
Arseniy Alekseyev
rhg: refactor to pass argv down, instead of caling args_os()...
r49961 fn main() -> ! {
rhg_main(std::env::args_os().collect())
}
Pulkit Goyal
rhg: add support for detailed exit code for ConfigParseError...
r47576 fn exit_code(
result: &Result<(), CommandError>,
use_detailed_exit_code: bool,
) -> i32 {
Simon Sapin
rhg: Add support for the blackbox extension...
r47343 match result {
Pulkit Goyal
rhg: add exit code to HgError::Abort()...
r48199 Ok(()) => exit_codes::OK,
Pulkit Goyal
rhg: add support for detailed exit code for ConfigParseError...
r47576 Err(CommandError::Abort {
Raphaël Gomès
rust: add support for hints in error messages...
r50382 detailed_exit_code, ..
Pulkit Goyal
rhg: add support for detailed exit code for ConfigParseError...
r47576 }) => {
if use_detailed_exit_code {
*detailed_exit_code
} else {
Pulkit Goyal
rhg: add exit code to HgError::Abort()...
r48199 exit_codes::ABORT
Pulkit Goyal
rhg: add support for detailed exit code for ConfigParseError...
r47576 }
}
Pulkit Goyal
rhg: add exit code to HgError::Abort()...
r48199 Err(CommandError::Unsuccessful) => exit_codes::UNSUCCESSFUL,
Simon Sapin
rhg: Simplify CommandError based on its use...
r47174 // Exit with a specific code and no error message to let a potential
// wrapper script fallback to Python-based Mercurial.
Simon Sapin
rhg: Add a `rhg.on-unsupported` configuration key...
r47424 Err(CommandError::UnsupportedFeature { .. }) => {
Pulkit Goyal
rhg: add exit code to HgError::Abort()...
r48199 exit_codes::UNIMPLEMENTED
Simon Sapin
rhg: Add a `rhg.on-unsupported` configuration key...
r47424 }
Raphaël Gomès
rhg: use `Command::exec` instead of `Command::status`...
r50043 Err(CommandError::InvalidFallback { .. }) => {
exit_codes::INVALID_FALLBACK
}
Simon Sapin
rhg: Add support for the blackbox extension...
r47343 }
Antoine Cezar
rhg: add rhg crate...
r45503 }
Antoine Cezar
rhg: add a limited `rhg debugdata` subcommand...
r46100
Raphaël Gomès
rust: run a clippy pass with the latest stable version...
r52013 fn exit(
original_args: &[OsString],
Simon Sapin
rhg: Add support for --cwd...
r47470 initial_current_dir: &Option<PathBuf>,
Simon Sapin
rhg: Add a `rhg.on-unsupported` configuration key...
r47424 ui: &Ui,
Simon Sapin
rhg: Add support for automatic fallback to Python...
r47425 mut on_unsupported: OnUnsupported,
Simon Sapin
rhg: Add a `rhg.on-unsupported` configuration key...
r47424 result: Result<(), CommandError>,
Pulkit Goyal
rhg: add support for detailed exit code for ConfigParseError...
r47576 use_detailed_exit_code: bool,
Simon Sapin
rhg: Add a `rhg.on-unsupported` configuration key...
r47424 ) -> ! {
Simon Sapin
rhg: Add support for automatic fallback to Python...
r47425 if let (
OnUnsupported::Fallback { executable },
Raphaël Gomès
rhg: signal when falling back in logs...
r49622 Err(CommandError::UnsupportedFeature { message }),
Simon Sapin
rhg: Add support for automatic fallback to Python...
r47425 ) = (&on_unsupported, &result)
{
Arseniy Alekseyev
rhg: refactor to pass argv down, instead of caling args_os()...
r49961 let mut args = original_args.iter();
Arseniy Alekseyev
rhg: only complain about poorly configured fallback when falling back...
r49176 let executable = match executable {
None => {
exit_no_fallback(
ui,
OnUnsupported::Abort,
Err(CommandError::abort(
"abort: 'rhg.on-unsupported=fallback' without \
'rhg.fallback-executable' set.",
)),
false,
);
}
Some(executable) => executable,
};
Raphaël Gomès
rust: run `cargo clippy`...
r50809 let executable_path = get_path_from_bytes(executable);
Simon Sapin
rhg: Add support for automatic fallback to Python...
r47425 let this_executable = args.next().expect("exepcted argv[0] to exist");
Raphaël Gomès
rust-clippy: fix remaining warnings in `rhg`...
r50826 if executable_path == *this_executable {
Simon Sapin
rhg: Add support for automatic fallback to Python...
r47425 // Avoid spawning infinitely many processes until resource
// exhaustion.
let _ = ui.write_stderr(&format_bytes!(
b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
points to `rhg` itself.\n",
executable
));
on_unsupported = OnUnsupported::Abort
} else {
Raphaël Gomès
rhg: signal when falling back in logs...
r49622 log::debug!("falling back (see trace-level log)");
log::trace!("{}", local_to_utf8(message));
Raphaël Gomès
rhg: use `Command::exec` instead of `Command::status`...
r50043 if let Err(err) = which::which(executable_path) {
exit_no_fallback(
ui,
OnUnsupported::Abort,
Err(CommandError::InvalidFallback {
path: executable.to_owned(),
err: err.to_string(),
}),
use_detailed_exit_code,
)
}
Arseniy Alekseyev
rhg: only complain about poorly configured fallback when falling back...
r49176 // `args` is now `argv[1..]` since we’ve already consumed
// `argv[0]`
Simon Sapin
rhg: Add support for --cwd...
r47470 let mut command = Command::new(executable_path);
command.args(args);
if let Some(initial) = initial_current_dir {
command.current_dir(initial);
}
Raphaël Gomès
rhg: use `Command::exec` instead of `Command::status`...
r50043 // We don't use subprocess because proper signal handling is harder
// and we don't want to keep `rhg` around after a fallback anyway.
// For example, if `rhg` is run in the background and falls back to
// `hg` which, in turn, waits for a signal, we'll get stuck if
// we're doing plain subprocess.
//
// If `exec` returns, we can only assume our process is very broken
// (see its documentation), so only try to forward the error code
// when exiting.
let err = command.exec();
std::process::exit(
err.raw_os_error().unwrap_or(exit_codes::ABORT),
);
Simon Sapin
rhg: Add support for automatic fallback to Python...
r47425 }
}
Pulkit Goyal
rhg: add support for detailed exit code for ConfigParseError...
r47576 exit_no_fallback(ui, on_unsupported, result, use_detailed_exit_code)
Simon Sapin
rhg: Remove `rhg.fallback-executable=hg` default configuration...
r47482 }
fn exit_no_fallback(
ui: &Ui,
on_unsupported: OnUnsupported,
result: Result<(), CommandError>,
Pulkit Goyal
rhg: add support for detailed exit code for ConfigParseError...
r47576 use_detailed_exit_code: bool,
Simon Sapin
rhg: Remove `rhg.fallback-executable=hg` default configuration...
r47482 ) -> ! {
Simon Sapin
rhg: Add a `rhg.on-unsupported` configuration key...
r47424 match &result {
Ok(_) => {}
Simon Sapin
rhg: `cat` command: print error messages for missing files...
r47478 Err(CommandError::Unsuccessful) => {}
Raphaël Gomès
rust: add support for hints in error messages...
r50382 Err(CommandError::Abort { message, hint, .. }) => {
// Ignore errors when writing to stderr, we’re already exiting
// with failure code so there’s not much more we can do.
Simon Sapin
rhg: Add a `rhg.on-unsupported` configuration key...
r47424 if !message.is_empty() {
Simon Sapin
rhg: Align config file parse error formatting with Python...
r47465 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
Simon Sapin
rhg: Add a `rhg.on-unsupported` configuration key...
r47424 }
Raphaël Gomès
rust: add support for hints in error messages...
r50382 if let Some(hint) = hint {
let _ = ui.write_stderr(&format_bytes!(b"({})\n", hint));
}
Simon Sapin
rhg: Add a `rhg.on-unsupported` configuration key...
r47424 }
Err(CommandError::UnsupportedFeature { message }) => {
match on_unsupported {
OnUnsupported::Abort => {
let _ = ui.write_stderr(&format_bytes!(
b"unsupported feature: {}\n",
message
));
}
OnUnsupported::AbortSilent => {}
Simon Sapin
rhg: Add support for automatic fallback to Python...
r47425 OnUnsupported::Fallback { .. } => unreachable!(),
Simon Sapin
rhg: Add a `rhg.on-unsupported` configuration key...
r47424 }
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 }
Raphaël Gomès
rhg: use `Command::exec` instead of `Command::status`...
r50043 Err(CommandError::InvalidFallback { path, err }) => {
let _ = ui.write_stderr(&format_bytes!(
b"abort: invalid fallback '{}': {}\n",
path,
err.as_bytes(),
));
}
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 }
Pulkit Goyal
rhg: add support for detailed exit code for ConfigParseError...
r47576 std::process::exit(exit_code(&result, use_detailed_exit_code))
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 }
Arseniy Alekseyev
rhg: support rhg status --rev --rev
r52048 mod commands {
pub mod cat;
pub mod config;
pub mod debugdata;
pub mod debugignorerhg;
pub mod debugrequirements;
pub mod debugrhgsparse;
pub mod files;
pub mod root;
pub mod status;
}
Simon Sapin
rhg: Replace subcommand boilerplate with a macro...
r47252 macro_rules! subcommands {
($( $command: ident )+) => {
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 fn add_subcommand_args(app: clap::Command) -> clap::Command {
Simon Sapin
rhg: Replace subcommand boilerplate with a macro...
r47252 app
$(
Simon Sapin
rhg: Use clap’s support for global CLI arguments...
r47351 .subcommand(commands::$command::args())
Simon Sapin
rhg: Replace subcommand boilerplate with a macro...
r47252 )+
}
Simon Sapin
rhg: Parse system and user configuration at program start...
r47213
Simon Sapin
rhg: Group values passed to every sub-command into a struct...
r47334 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
fn subcommand_run_fn(name: &str) -> Option<RunFn> {
Simon Sapin
rhg: Replace subcommand boilerplate with a macro...
r47252 match name {
$(
stringify!($command) => Some(commands::$command::run),
)+
_ => None,
}
Antoine Cezar
rhg: add a limited `rhg cat -r` subcommand...
r46113 }
Simon Sapin
rhg: Replace subcommand boilerplate with a macro...
r47252 };
Antoine Cezar
rhg: add a limited `rhg debugdata` subcommand...
r46100 }
Simon Sapin
rhg: Replace subcommand boilerplate with a macro...
r47252
subcommands! {
cat
debugdata
debugrequirements
Arseniy Alekseyev
rhg: implement the debugignorerhg subcommand...
r49178 debugignorerhg
Raphaël Gomès
rhg: add debugrhgsparse command to help figure out bugs in rhg
r50379 debugrhgsparse
Simon Sapin
rhg: Replace subcommand boilerplate with a macro...
r47252 files
root
Simon Sapin
rhg: add limited support for the `config` sub-command...
r47255 config
Georges Racinet
rhg: Initial support for the 'status' command...
r47578 status
Simon Sapin
rhg: Replace subcommand boilerplate with a macro...
r47252 }
Georges Racinet
rhg: Initial support for the 'status' command...
r47578
Simon Sapin
rhg: Group values passed to every sub-command into a struct...
r47334 pub struct CliInvocation<'a> {
ui: &'a Ui,
Raphaël Gomès
rhg: upgrade `clap` dependency...
r50534 subcommand_args: &'a ArgMatches,
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 config: &'a Config,
Simon Sapin
rhg: Move `Repo` object creation into `main()`...
r47335 /// References inside `Result` is a bit peculiar but allow
/// `invocation.repo?` to work out with `&CliInvocation` since this
/// `Result` type is `Copy`.
repo: Result<&'a Repo, &'a NoRepoInCwdError>,
}
struct NoRepoInCwdError {
cwd: PathBuf,
Simon Sapin
rhg: Group values passed to every sub-command into a struct...
r47334 }
Simon Sapin
rhg: Move `Repo` object creation into `main()`...
r47335
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 /// CLI arguments to be parsed "early" in order to be able to read
/// configuration before using Clap. Ideally we would also use Clap for this,
/// see <https://github.com/clap-rs/clap/discussions/2366>.
///
/// These arguments are still declared when we do use Clap later, so that Clap
/// does not return an error for their presence.
struct EarlyArgs {
/// Values of all `--config` arguments. (Possibly none)
config: Vec<Vec<u8>>,
Simon Sapin
rhg: Add parsing for the --color global CLI argument...
r49583 /// Value of all the `--color` argument, if any.
color: Option<Vec<u8>>,
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 /// Value of the `-R` or `--repository` argument, if any.
repo: Option<Vec<u8>>,
Simon Sapin
rhg: Add support for --cwd...
r47470 /// Value of the `--cwd` argument, if any.
cwd: Option<Vec<u8>>,
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 }
impl EarlyArgs {
Arseniy Alekseyev
rhg: refactor to pass argv down, instead of caling args_os()...
r49961 fn parse<'a>(args: impl IntoIterator<Item = &'a OsString>) -> Self {
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 let mut args = args.into_iter().map(get_bytes_from_os_str);
let mut config = Vec::new();
Simon Sapin
rhg: Add parsing for the --color global CLI argument...
r49583 let mut color = None;
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 let mut repo = None;
Simon Sapin
rhg: Add support for --cwd...
r47470 let mut cwd = None;
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 // Use `while let` instead of `for` so that we can also call
// `args.next()` inside the loop.
while let Some(arg) = args.next() {
if arg == b"--config" {
if let Some(value) = args.next() {
config.push(value)
}
} else if let Some(value) = arg.drop_prefix(b"--config=") {
config.push(value.to_owned())
}
Simon Sapin
rhg: Add parsing for the --color global CLI argument...
r49583 if arg == b"--color" {
if let Some(value) = args.next() {
color = Some(value)
}
} else if let Some(value) = arg.drop_prefix(b"--color=") {
color = Some(value.to_owned())
}
Simon Sapin
rhg: Add support for --cwd...
r47470 if arg == b"--cwd" {
if let Some(value) = args.next() {
cwd = Some(value)
}
} else if let Some(value) = arg.drop_prefix(b"--cwd=") {
cwd = Some(value.to_owned())
}
Simon Sapin
rhg: Make configuration available as early as possible in main()...
r47423 if arg == b"--repository" || arg == b"-R" {
if let Some(value) = args.next() {
repo = Some(value)
}
} else if let Some(value) = arg.drop_prefix(b"--repository=") {
repo = Some(value.to_owned())
} else if let Some(value) = arg.drop_prefix(b"-R") {
repo = Some(value.to_owned())
}
Simon Sapin
rhg: Move `Repo` object creation into `main()`...
r47335 }
Simon Sapin
rhg: Add parsing for the --color global CLI argument...
r49583 Self {
config,
color,
repo,
cwd,
}
Simon Sapin
rhg: Move `Repo` object creation into `main()`...
r47335 }
}
Simon Sapin
rhg: Add a `rhg.on-unsupported` configuration key...
r47424
/// What to do when encountering some unsupported feature.
///
/// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
enum OnUnsupported {
/// Print an error message describing what feature is not supported,
/// and exit with code 252.
Abort,
/// Silently exit with code 252.
AbortSilent,
Simon Sapin
rhg: Add support for automatic fallback to Python...
r47425 /// Try running a Python implementation
Arseniy Alekseyev
rhg: only complain about poorly configured fallback when falling back...
r49176 Fallback { executable: Option<Vec<u8>> },
Simon Sapin
rhg: Add a `rhg.on-unsupported` configuration key...
r47424 }
impl OnUnsupported {
Simon Sapin
rhg: Add support for automatic fallback to Python...
r47425 const DEFAULT: Self = OnUnsupported::Abort;
Arseniy Alekseyev
rhg: tweak rhg fallback code structure...
r51702 fn fallback_executable(config: &Config) -> Option<Vec<u8>> {
config
.get(b"rhg", b"fallback-executable")
.map(|x| x.to_owned())
}
fn fallback(config: &Config) -> Self {
OnUnsupported::Fallback {
executable: Self::fallback_executable(config),
}
}
Arseniy Alekseyev
rhg: only complain about poorly configured fallback when falling back...
r49176 fn from_config(config: &Config) -> Self {
Simon Sapin
rhg: Add support for automatic fallback to Python...
r47425 match config
.get(b"rhg", b"on-unsupported")
.map(|value| value.to_ascii_lowercase())
.as_deref()
{
Simon Sapin
rhg: Add a `rhg.on-unsupported` configuration key...
r47424 Some(b"abort") => OnUnsupported::Abort,
Some(b"abort-silent") => OnUnsupported::AbortSilent,
Arseniy Alekseyev
rhg: tweak rhg fallback code structure...
r51702 Some(b"fallback") => Self::fallback(config),
Simon Sapin
rhg: Add support for automatic fallback to Python...
r47425 None => Self::DEFAULT,
Simon Sapin
rhg: Add a `rhg.on-unsupported` configuration key...
r47424 Some(_) => {
// TODO: warn about unknown config value
Simon Sapin
rhg: Add support for automatic fallback to Python...
r47425 Self::DEFAULT
Simon Sapin
rhg: Add a `rhg.on-unsupported` configuration key...
r47424 }
}
}
}
Simon Sapin
rhg: Fall back to Python if unsupported extensions are enabled...
r47467
Raphaël Gomès
rhg: support the new extension suboptions syntax...
r49270 /// The `*` extension is an edge-case for config sub-options that apply to all
/// extensions. For now, only `:required` exists, but that may change in the
/// future.
Raphaël Gomès
rhg: don't fallback if `strip` or `rebase` are activated...
r50376 const SUPPORTED_EXTENSIONS: &[&[u8]] = &[
b"blackbox",
b"share",
b"sparse",
b"narrow",
b"*",
b"strip",
b"rebase",
];
Simon Sapin
rhg: Fall back to Python if unsupported extensions are enabled...
r47467
fn check_extensions(config: &Config) -> Result<(), CommandError> {
Raphaël Gomès
rhg: add support for ignoring all extensions...
r49829 if let Some(b"*") = config.get(b"rhg", b"ignored-extensions") {
// All extensions are to be ignored, nothing to do here
return Ok(());
}
Raphaël Gomès
rhg: support the new extension suboptions syntax...
r49270 let enabled: HashSet<&[u8]> = config
Raphaël Gomès
rhg: support "!" syntax for disabling extensions...
r50372 .iter_section(b"extensions")
.filter_map(|(extension, value)| {
if value == b"!" {
// Filter out disabled extensions
return None;
}
Raphaël Gomès
rhg: support the new extension suboptions syntax...
r49270 // Ignore extension suboptions. Only `required` exists for now.
// `rhg` either supports an extension or doesn't, so it doesn't
// make sense to consider the loading of an extension.
Raphaël Gomès
rhg: support "!" syntax for disabling extensions...
r50372 let actual_extension =
extension.split_2(b':').unwrap_or((extension, b"")).0;
Some(actual_extension)
Raphaël Gomès
rhg: support the new extension suboptions syntax...
r49270 })
.collect();
Simon Sapin
rhg: Fall back to Python if unsupported extensions are enabled...
r47467
let mut unsupported = enabled;
for supported in SUPPORTED_EXTENSIONS {
unsupported.remove(supported);
}
Simon Sapin
rhg: Switch rhg.ignored-extensions config to Python-compatible list syntax...
r48763 if let Some(ignored_list) = config.get_list(b"rhg", b"ignored-extensions")
Simon Sapin
rhg: Add an allow-list of ignored extensions...
r47468 {
for ignored in ignored_list {
Simon Sapin
rhg: Switch rhg.ignored-extensions config to Python-compatible list syntax...
r48763 unsupported.remove(ignored.as_slice());
Simon Sapin
rhg: Add an allow-list of ignored extensions...
r47468 }
}
Simon Sapin
rhg: Fall back to Python if unsupported extensions are enabled...
r47467 if unsupported.is_empty() {
Ok(())
} else {
Raphaël Gomès
rhg: sort unsupported extensions in error message...
r49842 let mut unsupported: Vec<_> = unsupported.into_iter().collect();
// Sort the extensions to get a stable output
unsupported.sort();
Simon Sapin
rhg: Fall back to Python if unsupported extensions are enabled...
r47467 Err(CommandError::UnsupportedFeature {
message: format_bytes!(
Simon Sapin
rhg: Add an allow-list of ignored extensions...
r47468 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
Simon Sapin
rhg: Fall back to Python if unsupported extensions are enabled...
r47467 join(unsupported, b", ")
),
})
}
}
Simon Sapin
rhg: $HG_PENDING is not supported...
r49159
auto-upgrade: introduce a way to auto-upgrade to/from share-safe...
r50087 /// Array of tuples of (auto upgrade conf, feature conf, local requirement)
Raphaël Gomès
rust-clippy: fix remaining warnings in `rhg`...
r50826 #[allow(clippy::type_complexity)]
auto-upgrade: introduce a way to auto-upgrade to/from share-safe...
r50087 const AUTO_UPGRADES: &[((&str, &str), (&str, &str), &str)] = &[
(
("format", "use-share-safe.automatic-upgrade-of-mismatching-repositories"),
("format", "use-share-safe"),
requirements::SHARESAFE_REQUIREMENT,
),
auto-upgrade: introduce a way to auto-upgrade to/from tracked-hint...
r50089 (
("format", "use-dirstate-tracked-hint.automatic-upgrade-of-mismatching-repositories"),
("format", "use-dirstate-tracked-hint"),
requirements::DIRSTATE_TRACKED_HINT_V1,
),
auto-upgrade: introduce a way to auto-upgrade to/from dirstate-v2...
r50090 (
Arseniy Alekseyev
rhg: fix bugs around [use-dirstate-tracked-hint] and repo auto-upgrade...
r50395 ("format", "use-dirstate-v2.automatic-upgrade-of-mismatching-repositories"),
auto-upgrade: introduce a way to auto-upgrade to/from dirstate-v2...
r50090 ("format", "use-dirstate-v2"),
requirements::DIRSTATE_V2_REQUIREMENT,
),
auto-upgrade: introduce a way to auto-upgrade to/from share-safe...
r50087 ];
/// Mercurial allows users to automatically upgrade their repository.
/// `rhg` does not have the ability to upgrade yet, so fallback if an upgrade
/// is needed.
fn check_auto_upgrade(
config: &Config,
reqs: &HashSet<String>,
) -> Result<(), CommandError> {
for (upgrade_conf, feature_conf, local_req) in AUTO_UPGRADES.iter() {
let auto_upgrade = config
.get_bool(upgrade_conf.0.as_bytes(), upgrade_conf.1.as_bytes())?;
if auto_upgrade {
let want_it = config.get_bool(
feature_conf.0.as_bytes(),
feature_conf.1.as_bytes(),
)?;
let have_it = reqs.contains(*local_req);
let action = match (want_it, have_it) {
(true, false) => Some("upgrade"),
(false, true) => Some("downgrade"),
_ => None,
};
if let Some(action) = action {
let message = format!(
"automatic {} {}.{}",
action, upgrade_conf.0, upgrade_conf.1
);
return Err(CommandError::unsupported(message));
}
}
}
Ok(())
}
Simon Sapin
rhg: Colored output is not supported...
r49163 fn check_unsupported(
config: &Config,
Simon Sapin
rhg: Sub-repositories are not supported...
r49341 repo: Result<&Repo, &NoRepoInCwdError>,
Simon Sapin
rhg: Colored output is not supported...
r49163 ) -> Result<(), CommandError> {
Simon Sapin
rhg: $HG_PENDING is not supported...
r49159 check_extensions(config)?;
if std::env::var_os("HG_PENDING").is_some() {
// TODO: only if the value is `== repo.working_directory`?
// What about relative v.s. absolute paths?
Err(CommandError::unsupported("$HG_PENDING"))?
}
Simon Sapin
rhg: Sub-repositories are not supported...
r49341 if let Ok(repo) = repo {
if repo.has_subrepos()? {
Err(CommandError::unsupported("sub-repositories"))?
}
auto-upgrade: introduce a way to auto-upgrade to/from share-safe...
r50087 check_auto_upgrade(config, repo.requirements())?;
Simon Sapin
rhg: Sub-repositories are not supported...
r49341 }
Simon Sapin
rhg: [encode] and [decode] config sections are not supported...
r49162 if config.has_non_empty_section(b"encode") {
Err(CommandError::unsupported("[encode] config"))?
}
if config.has_non_empty_section(b"decode") {
Err(CommandError::unsupported("[decode] config"))?
}
Simon Sapin
rhg: $HG_PENDING is not supported...
r49159 Ok(())
}