diff --git a/rust/rhg/src/commands/cat.rs b/rust/rhg/src/commands/cat.rs --- a/rust/rhg/src/commands/cat.rs +++ b/rust/rhg/src/commands/cat.rs @@ -60,6 +60,8 @@ invocation.ui.write_stdout(&data)?; Ok(()) } - None => Err(CommandError::Unimplemented.into()), + None => Err(CommandError::unsupported( + "`rhg cat` without `--rev` / `-r`", + )), } } diff --git a/rust/rhg/src/error.rs b/rust/rhg/src/error.rs --- a/rust/rhg/src/error.rs +++ b/rust/rhg/src/error.rs @@ -15,12 +15,11 @@ /// Exit with an error message and "standard" failure exit code. Abort { message: Vec }, - /// A mercurial capability as not been implemented. - /// - /// There is no error message printed in this case. - /// Instead, we exit with a specic status code and a wrapper script may - /// fallback to Python-based Mercurial. - Unimplemented, + /// Encountered something (such as a CLI argument, repository layout, …) + /// not supported by this version of `rhg`. Depending on configuration + /// `rhg` may attempt to silently fall back to Python-based `hg`, which + /// may or may not support this feature. + UnsupportedFeature { message: Vec }, } impl CommandError { @@ -32,20 +31,28 @@ message: utf8_to_local(message.as_ref()).into(), } } + + pub fn unsupported(message: impl AsRef) -> Self { + CommandError::UnsupportedFeature { + message: utf8_to_local(message.as_ref()).into(), + } + } } /// For now we don’t differenciate between invalid CLI args and valid for `hg` /// but not supported yet by `rhg`. impl From for CommandError { - fn from(_: clap::Error) -> Self { - CommandError::Unimplemented + fn from(error: clap::Error) -> Self { + CommandError::unsupported(error.to_string()) } } impl From for CommandError { fn from(error: HgError) -> Self { match error { - HgError::UnsupportedFeature(_) => CommandError::Unimplemented, + HgError::UnsupportedFeature(message) => { + CommandError::unsupported(message) + } _ => CommandError::abort(error.to_string()), } } diff --git a/rust/rhg/src/main.rs b/rust/rhg/src/main.rs --- a/rust/rhg/src/main.rs +++ b/rust/rhg/src/main.rs @@ -84,8 +84,15 @@ let ui = ui::Ui::new(); let early_args = EarlyArgs::parse(std::env::args_os()); - let non_repo_config = Config::load(early_args.config) - .unwrap_or_else(|error| exit(&ui, Err(error.into()))); + let non_repo_config = + Config::load(early_args.config).unwrap_or_else(|error| { + // 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; + + exit(&ui, on_unsupported, Err(error.into())) + }); let repo_path = early_args.repo.as_deref().map(get_path_from_bytes); let repo_result = match Repo::find(&non_repo_config, repo_path) { @@ -94,7 +101,11 @@ // Not finding a repo is not fatal yet, if `-R` was not given Err(NoRepoInCwdError { cwd: at }) } - Err(error) => exit(&ui, Err(error.into())), + Err(error) => exit( + &ui, + OnUnsupported::from_config(&non_repo_config), + Err(error.into()), + ), }; let config = if let Ok(repo) = &repo_result { @@ -109,7 +120,7 @@ repo_result.as_ref(), config, ); - exit(&ui, result) + exit(&ui, OnUnsupported::from_config(config), result) } fn exit_code(result: &Result<(), CommandError>) -> i32 { @@ -119,16 +130,37 @@ // Exit with a specific code and no error message to let a potential // wrapper script fallback to Python-based Mercurial. - Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED, + Err(CommandError::UnsupportedFeature { .. }) => { + exitcode::UNIMPLEMENTED + } } } -fn exit(ui: &Ui, result: Result<(), CommandError>) -> ! { - if let Err(CommandError::Abort { message }) = &result { - if !message.is_empty() { - // Ignore errors when writing to stderr, we’re already exiting - // with failure code so there’s not much more we can do. - let _ = ui.write_stderr(&format_bytes!(b"abort: {}\n", message)); +fn exit( + ui: &Ui, + on_unsupported: OnUnsupported, + result: Result<(), CommandError>, +) -> ! { + match &result { + Ok(_) => {} + Err(CommandError::Abort { message }) => { + if !message.is_empty() { + // Ignore errors when writing to stderr, we’re already exiting + // with failure code so there’s not much more we can do. + let _ = + ui.write_stderr(&format_bytes!(b"abort: {}\n", message)); + } + } + Err(CommandError::UnsupportedFeature { message }) => { + match on_unsupported { + OnUnsupported::Abort => { + let _ = ui.write_stderr(&format_bytes!( + b"unsupported feature: {}\n", + message + )); + } + OnUnsupported::AbortSilent => {} + } } } std::process::exit(exit_code(&result)) @@ -226,3 +258,29 @@ Self { config, repo } } } + +/// 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, +} + +impl OnUnsupported { + fn from_config(config: &Config) -> Self { + let default = OnUnsupported::Abort; + match config.get(b"rhg", b"on-unsupported") { + Some(b"abort") => OnUnsupported::Abort, + Some(b"abort-silent") => OnUnsupported::AbortSilent, + None => default, + Some(_) => { + // TODO: warn about unknown config value + default + } + } + } +} diff --git a/tests/test-rhg.t b/tests/test-rhg.t --- a/tests/test-rhg.t +++ b/tests/test-rhg.t @@ -12,6 +12,15 @@ Unimplemented command $ rhg unimplemented-command + unsupported feature: error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context + + USAGE: + rhg [OPTIONS] + + For more information try --help + + [252] + $ rhg unimplemented-command --config rhg.on-unsupported=abort-silent [252] Finding root @@ -153,12 +162,15 @@ $ echo indoor-pool >> .hg/requires $ rhg files + unsupported feature: repository requires feature unknown to this Mercurial: indoor-pool [252] $ rhg cat -r 1 copy_of_original + unsupported feature: repository requires feature unknown to this Mercurial: indoor-pool [252] $ rhg debugrequirements + unsupported feature: repository requires feature unknown to this Mercurial: indoor-pool [252] $ echo -e '\xFF' >> .hg/requires