# HG changeset patch # User Pulkit Goyal <7895pulkit@gmail.com> # Date 2021-06-07 11:57:49 # Node ID 6e49769b7f97e688c6c128e5c2cc9dcf8612a35d # Parent 3237ed4dcda4616a7b96d8e9ad58ccb451fa5c42 rhg: add exit code to HgError::Abort() My previous attempts to have rhg end with correct exit code was more of bug hunting. I found cases which were failing and fixed them. But as one might expect, more tests started failing. Let's add exit code `HgError::Abort()` and make it users explicitly tell what exit code they want. Differential Revision: https://phab.mercurial-scm.org/D10838 diff --git a/rust/hg-core/src/config/layer.rs b/rust/hg-core/src/config/layer.rs --- a/rust/hg-core/src/config/layer.rs +++ b/rust/hg-core/src/config/layer.rs @@ -8,6 +8,7 @@ // GNU General Public License version 2 or any later version. use crate::errors::HgError; +use crate::exit_codes::CONFIG_PARSE_ERROR_ABORT; use crate::utils::files::{get_bytes_from_path, get_path_from_bytes}; use format_bytes::{format_bytes, write_bytes, DisplayBytes}; use lazy_static::lazy_static; @@ -73,11 +74,14 @@ impl ConfigLayer { if let Some((section, item, value)) = parse_one(arg) { layer.add(section, item, value, None); } else { - Err(HgError::abort(format!( - "abort: malformed --config option: '{}' \ + Err(HgError::abort( + format!( + "abort: malformed --config option: '{}' \ (use --config section.name=value)", - String::from_utf8_lossy(arg), - )))? + String::from_utf8_lossy(arg), + ), + CONFIG_PARSE_ERROR_ABORT, + ))? } } if layer.sections.is_empty() { diff --git a/rust/hg-core/src/errors.rs b/rust/hg-core/src/errors.rs --- a/rust/hg-core/src/errors.rs +++ b/rust/hg-core/src/errors.rs @@ -1,4 +1,5 @@ use crate::config::ConfigValueParseError; +use crate::exit_codes; use std::fmt; /// Common error cases that can happen in many different APIs @@ -27,9 +28,12 @@ pub enum HgError { /// Operation cannot proceed for some other reason. /// - /// The given string is a short explanation for users, not intended to be + /// The message is a short explanation for users, not intended to be /// machine-readable. - Abort(String), + Abort { + message: String, + detailed_exit_code: exit_codes::ExitCode, + }, /// A configuration value is not in the expected syntax. /// @@ -69,8 +73,15 @@ impl HgError { pub fn unsupported(explanation: impl Into) -> Self { HgError::UnsupportedFeature(explanation.into()) } - pub fn abort(explanation: impl Into) -> Self { - HgError::Abort(explanation.into()) + + pub fn abort( + explanation: impl Into, + exit_code: exit_codes::ExitCode, + ) -> Self { + HgError::Abort { + message: explanation.into(), + detailed_exit_code: exit_code, + } } } @@ -78,7 +89,7 @@ impl HgError { impl fmt::Display for HgError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - HgError::Abort(explanation) => write!(f, "{}", explanation), + HgError::Abort { message, .. } => write!(f, "{}", message), HgError::IoError { error, context } => { write!(f, "abort: {}: {}", context, error) } diff --git a/rust/hg-core/src/exit_codes.rs b/rust/hg-core/src/exit_codes.rs new file mode 100644 --- /dev/null +++ b/rust/hg-core/src/exit_codes.rs @@ -0,0 +1,19 @@ +pub type ExitCode = i32; + +/// Successful exit +pub const OK: ExitCode = 0; + +/// Generic abort +pub const ABORT: ExitCode = 255; + +// Abort when there is a config related error +pub const CONFIG_ERROR_ABORT: ExitCode = 30; + +// Abort when there is an error while parsing config +pub const CONFIG_PARSE_ERROR_ABORT: ExitCode = 10; + +/// Generic something completed but did not succeed +pub const UNSUCCESSFUL: ExitCode = 1; + +/// Command or feature not implemented by rhg +pub const UNIMPLEMENTED: ExitCode = 252; diff --git a/rust/hg-core/src/lib.rs b/rust/hg-core/src/lib.rs --- a/rust/hg-core/src/lib.rs +++ b/rust/hg-core/src/lib.rs @@ -11,6 +11,7 @@ pub use ancestors::{AncestorsIterator, L pub mod dirstate; pub mod dirstate_tree; pub mod discovery; +pub mod exit_codes; pub mod requirements; pub mod testing; // unconditionally built, for use from integration tests pub use dirstate::{ diff --git a/rust/hg-core/src/repo.rs b/rust/hg-core/src/repo.rs --- a/rust/hg-core/src/repo.rs +++ b/rust/hg-core/src/repo.rs @@ -1,5 +1,6 @@ use crate::config::{Config, ConfigError, ConfigParseError}; use crate::errors::{HgError, IoErrorContext, IoResultExt}; +use crate::exit_codes; use crate::requirements; use crate::utils::files::get_path_from_bytes; use crate::utils::SliceExt; @@ -150,6 +151,7 @@ impl Repo { Some(b"abort") | None => HgError::abort( "abort: share source does not support share-safe requirement\n\ (see `hg help config.format.use-share-safe` for more information)", + exit_codes::ABORT, ), _ => HgError::unsupported("share-safe downgrade"), } @@ -161,6 +163,7 @@ impl Repo { "abort: version mismatch: source uses share-safe \ functionality while the current share does not\n\ (see `hg help config.format.use-share-safe` for more information)", + exit_codes::ABORT, ), _ => HgError::unsupported("share-safe upgrade"), } 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 @@ -1,10 +1,10 @@ -use crate::exitcode; use crate::ui::utf8_to_local; use crate::ui::UiError; use crate::NoRepoInCwdError; use format_bytes::format_bytes; use hg::config::{ConfigError, ConfigParseError, ConfigValueParseError}; use hg::errors::HgError; +use hg::exit_codes; use hg::repo::RepoError; use hg::revlog::revlog::RevlogError; use hg::utils::files::get_bytes_from_path; @@ -17,7 +17,7 @@ pub enum CommandError { /// Exit with an error message and "standard" failure exit code. Abort { message: Vec, - detailed_exit_code: exitcode::ExitCode, + detailed_exit_code: exit_codes::ExitCode, }, /// Exit with a failure exit code but no message. @@ -32,12 +32,12 @@ pub enum CommandError { impl CommandError { pub fn abort(message: impl AsRef) -> Self { - CommandError::abort_with_exit_code(message, exitcode::ABORT) + CommandError::abort_with_exit_code(message, exit_codes::ABORT) } pub fn abort_with_exit_code( message: impl AsRef, - detailed_exit_code: exitcode::ExitCode, + detailed_exit_code: exit_codes::ExitCode, ) -> Self { CommandError::Abort { // TODO: bytes-based (instead of Unicode-based) formatting @@ -78,7 +78,7 @@ impl From for Com fn from(error: ConfigValueParseError) -> Self { CommandError::abort_with_exit_code( error.to_string(), - exitcode::CONFIG_ERROR_ABORT, + exit_codes::CONFIG_ERROR_ABORT, ) } } @@ -100,7 +100,7 @@ impl From for CommandError { b"abort: repository {} not found", get_bytes_from_path(at) ), - detailed_exit_code: exitcode::ABORT, + detailed_exit_code: exit_codes::ABORT, }, RepoError::ConfigParseError(error) => error.into(), RepoError::Other(error) => error.into(), @@ -116,7 +116,7 @@ impl<'a> From<&'a NoRepoInCwdError> for b"abort: no repository found in '{}' (.hg not found)!", get_bytes_from_path(cwd) ), - detailed_exit_code: exitcode::ABORT, + detailed_exit_code: exit_codes::ABORT, } } } @@ -149,7 +149,7 @@ impl From for CommandE line_message, message ), - detailed_exit_code: exitcode::CONFIG_ERROR_ABORT, + detailed_exit_code: exit_codes::CONFIG_ERROR_ABORT, } } } diff --git a/rust/rhg/src/exitcode.rs b/rust/rhg/src/exitcode.rs deleted file mode 100644 --- a/rust/rhg/src/exitcode.rs +++ /dev/null @@ -1,16 +0,0 @@ -pub type ExitCode = i32; - -/// Successful exit -pub const OK: ExitCode = 0; - -/// Generic abort -pub const ABORT: ExitCode = 255; - -// Abort when there is a config related error -pub const CONFIG_ERROR_ABORT: ExitCode = 30; - -/// Generic something completed but did not succeed -pub const UNSUCCESSFUL: ExitCode = 1; - -/// Command or feature not implemented by rhg -pub const UNIMPLEMENTED: ExitCode = 252; 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 @@ -6,6 +6,7 @@ use clap::Arg; use clap::ArgMatches; use format_bytes::{format_bytes, join}; use hg::config::{Config, ConfigSource}; +use hg::exit_codes; use hg::repo::{Repo, RepoError}; use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes}; use hg::utils::SliceExt; @@ -15,7 +16,6 @@ use std::process::Command; mod blackbox; mod error; -mod exitcode; mod ui; use error::CommandError; @@ -297,7 +297,7 @@ fn exit_code( use_detailed_exit_code: bool, ) -> i32 { match result { - Ok(()) => exitcode::OK, + Ok(()) => exit_codes::OK, Err(CommandError::Abort { message: _, detailed_exit_code, @@ -305,15 +305,15 @@ fn exit_code( if use_detailed_exit_code { *detailed_exit_code } else { - exitcode::ABORT + exit_codes::ABORT } } - Err(CommandError::Unsuccessful) => exitcode::UNSUCCESSFUL, + Err(CommandError::Unsuccessful) => exit_codes::UNSUCCESSFUL, // Exit with a specific code and no error message to let a potential // wrapper script fallback to Python-based Mercurial. Err(CommandError::UnsupportedFeature { .. }) => { - exitcode::UNIMPLEMENTED + exit_codes::UNIMPLEMENTED } } } @@ -352,7 +352,7 @@ fn exit( let result = command.status(); match result { Ok(status) => std::process::exit( - status.code().unwrap_or(exitcode::ABORT), + status.code().unwrap_or(exit_codes::ABORT), ), Err(error) => { let _ = ui.write_stderr(&format_bytes!(