error.rs
308 lines
| 9.6 KiB
| application/rls-services+xml
|
RustLexer
Simon Sapin
|
r47165 | use crate::ui::utf8_to_local; | ||
Antoine Cezar
|
r45592 | use crate::ui::UiError; | ||
Simon Sapin
|
r47335 | use crate::NoRepoInCwdError; | ||
Simon Sapin
|
r47175 | use format_bytes::format_bytes; | ||
Simon Sapin
|
r47555 | use hg::config::{ConfigError, ConfigParseError, ConfigValueParseError}; | ||
Simon Sapin
|
r48474 | use hg::dirstate_tree::on_disk::DirstateV2ParseError; | ||
Simon Sapin
|
r47175 | use hg::errors::HgError; | ||
Pulkit Goyal
|
r48199 | use hg::exit_codes; | ||
Simon Sapin
|
r47215 | use hg::repo::RepoError; | ||
Raphaël Gomès
|
r50832 | use hg::revlog::RevlogError; | ||
Raphaël Gomès
|
r50380 | use hg::sparse::SparseConfigError; | ||
Simon Sapin
|
r47175 | use hg::utils::files::get_bytes_from_path; | ||
Spencer Baugh
|
r51759 | use hg::utils::hg_path::HgPathError; | ||
use hg::{DirstateError, DirstateMapError, PatternError, StatusError}; | ||||
Antoine Cezar
|
r45592 | use std::convert::From; | ||
/// The kind of command error | ||||
Simon Sapin
|
r47174 | #[derive(Debug)] | ||
Simon Sapin
|
r47163 | pub enum CommandError { | ||
Simon Sapin
|
r47174 | /// Exit with an error message and "standard" failure exit code. | ||
Pulkit Goyal
|
r47576 | Abort { | ||
message: Vec<u8>, | ||||
Pulkit Goyal
|
r48199 | detailed_exit_code: exit_codes::ExitCode, | ||
Raphaël Gomès
|
r50382 | hint: Option<Vec<u8>>, | ||
Pulkit Goyal
|
r47576 | }, | ||
Simon Sapin
|
r47174 | |||
Simon Sapin
|
r47478 | /// Exit with a failure exit code but no message. | ||
Unsuccessful, | ||||
Simon Sapin
|
r47424 | /// 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<u8> }, | ||||
Raphaël Gomès
|
r50043 | /// The fallback executable does not exist (or has some other problem if | ||
/// we end up being more precise about broken fallbacks). | ||||
InvalidFallback { path: Vec<u8>, err: String }, | ||||
Antoine Cezar
|
r45592 | } | ||
Simon Sapin
|
r47163 | impl CommandError { | ||
Simon Sapin
|
r47174 | pub fn abort(message: impl AsRef<str>) -> Self { | ||
Pulkit Goyal
|
r48199 | CommandError::abort_with_exit_code(message, exit_codes::ABORT) | ||
Pulkit Goyal
|
r47576 | } | ||
pub fn abort_with_exit_code( | ||||
message: impl AsRef<str>, | ||||
Pulkit Goyal
|
r48199 | detailed_exit_code: exit_codes::ExitCode, | ||
Pulkit Goyal
|
r47576 | ) -> Self { | ||
Simon Sapin
|
r47174 | CommandError::Abort { | ||
// TODO: bytes-based (instead of Unicode-based) formatting | ||||
// of error messages to handle non-UTF-8 filenames etc: | ||||
// https://www.mercurial-scm.org/wiki/EncodingStrategy#Mixing_output | ||||
message: utf8_to_local(message.as_ref()).into(), | ||||
Raphaël Gomès
|
r50809 | detailed_exit_code, | ||
Raphaël Gomès
|
r50382 | hint: None, | ||
} | ||||
} | ||||
pub fn abort_with_exit_code_and_hint( | ||||
message: impl AsRef<str>, | ||||
detailed_exit_code: exit_codes::ExitCode, | ||||
hint: Option<impl AsRef<str>>, | ||||
) -> Self { | ||||
CommandError::Abort { | ||||
message: utf8_to_local(message.as_ref()).into(), | ||||
detailed_exit_code, | ||||
hint: hint.map(|h| utf8_to_local(h.as_ref()).into()), | ||||
Antoine Cezar
|
r45592 | } | ||
} | ||||
Simon Sapin
|
r47424 | |||
Raphaël Gomès
|
r50380 | pub fn abort_with_exit_code_bytes( | ||
message: impl AsRef<[u8]>, | ||||
detailed_exit_code: exit_codes::ExitCode, | ||||
) -> Self { | ||||
// TODO: use this everywhere it makes sense instead of the string | ||||
// version. | ||||
CommandError::Abort { | ||||
message: message.as_ref().into(), | ||||
detailed_exit_code, | ||||
Raphaël Gomès
|
r50382 | hint: None, | ||
Raphaël Gomès
|
r50380 | } | ||
} | ||||
Simon Sapin
|
r47424 | pub fn unsupported(message: impl AsRef<str>) -> Self { | ||
CommandError::UnsupportedFeature { | ||||
message: utf8_to_local(message.as_ref()).into(), | ||||
} | ||||
} | ||||
Simon Sapin
|
r47174 | } | ||
Antoine Cezar
|
r45920 | |||
Simon Sapin
|
r47333 | /// For now we don’t differenciate between invalid CLI args and valid for `hg` | ||
/// but not supported yet by `rhg`. | ||||
impl From<clap::Error> for CommandError { | ||||
Simon Sapin
|
r47424 | fn from(error: clap::Error) -> Self { | ||
CommandError::unsupported(error.to_string()) | ||||
Simon Sapin
|
r47333 | } | ||
} | ||||
Simon Sapin
|
r47174 | impl From<HgError> for CommandError { | ||
fn from(error: HgError) -> Self { | ||||
match error { | ||||
Simon Sapin
|
r47424 | HgError::UnsupportedFeature(message) => { | ||
CommandError::unsupported(message) | ||||
} | ||||
Arseniy Alekseyev
|
r50069 | HgError::CensoredNodeError => { | ||
CommandError::unsupported("Encountered a censored node") | ||||
} | ||||
Pulkit Goyal
|
r48200 | HgError::Abort { | ||
message, | ||||
detailed_exit_code, | ||||
Raphaël Gomès
|
r50382 | hint, | ||
} => CommandError::abort_with_exit_code_and_hint( | ||||
message, | ||||
detailed_exit_code, | ||||
hint, | ||||
), | ||||
Simon Sapin
|
r47174 | _ => CommandError::abort(error.to_string()), | ||
Antoine Cezar
|
r45920 | } | ||
} | ||||
Antoine Cezar
|
r45592 | } | ||
Simon Sapin
|
r47555 | impl From<ConfigValueParseError> for CommandError { | ||
fn from(error: ConfigValueParseError) -> Self { | ||||
Pulkit Goyal
|
r47576 | CommandError::abort_with_exit_code( | ||
error.to_string(), | ||||
Pulkit Goyal
|
r48199 | exit_codes::CONFIG_ERROR_ABORT, | ||
Pulkit Goyal
|
r47576 | ) | ||
Simon Sapin
|
r47555 | } | ||
} | ||||
Antoine Cezar
|
r45592 | impl From<UiError> for CommandError { | ||
Simon Sapin
|
r47174 | fn from(_error: UiError) -> Self { | ||
// If we already failed writing to stdout or stderr, | ||||
// writing an error message to stderr about it would be likely to fail | ||||
// too. | ||||
CommandError::abort("") | ||||
Antoine Cezar
|
r45592 | } | ||
} | ||||
Antoine Cezar
|
r45922 | |||
Simon Sapin
|
r47215 | impl From<RepoError> for CommandError { | ||
fn from(error: RepoError) -> Self { | ||||
Simon Sapin
|
r47175 | match error { | ||
Raphaël Gomès
|
r50382 | RepoError::NotFound { at } => { | ||
CommandError::abort_with_exit_code_bytes( | ||||
format_bytes!( | ||||
b"abort: repository {} not found", | ||||
get_bytes_from_path(at) | ||||
), | ||||
exit_codes::ABORT, | ||||
) | ||||
} | ||||
Simon Sapin
|
r47215 | RepoError::ConfigParseError(error) => error.into(), | ||
RepoError::Other(error) => error.into(), | ||||
Antoine Cezar
|
r45922 | } | ||
} | ||||
} | ||||
Simon Sapin
|
r47165 | |||
Simon Sapin
|
r47335 | impl<'a> From<&'a NoRepoInCwdError> for CommandError { | ||
fn from(error: &'a NoRepoInCwdError) -> Self { | ||||
let NoRepoInCwdError { cwd } = error; | ||||
Raphaël Gomès
|
r50382 | CommandError::abort_with_exit_code_bytes( | ||
format_bytes!( | ||||
Simon Sapin
|
r47465 | b"abort: no repository found in '{}' (.hg not found)!", | ||
Simon Sapin
|
r47335 | get_bytes_from_path(cwd) | ||
), | ||||
Raphaël Gomès
|
r50382 | exit_codes::ABORT, | ||
) | ||||
Simon Sapin
|
r47335 | } | ||
} | ||||
Simon Sapin
|
r47213 | impl From<ConfigError> for CommandError { | ||
fn from(error: ConfigError) -> Self { | ||||
match error { | ||||
Simon Sapin
|
r47215 | ConfigError::Parse(error) => error.into(), | ||
Simon Sapin
|
r47213 | ConfigError::Other(error) => error.into(), | ||
} | ||||
} | ||||
} | ||||
Simon Sapin
|
r47215 | impl From<ConfigParseError> for CommandError { | ||
fn from(error: ConfigParseError) -> Self { | ||||
let ConfigParseError { | ||||
origin, | ||||
line, | ||||
Simon Sapin
|
r47465 | message, | ||
Simon Sapin
|
r47215 | } = error; | ||
let line_message = if let Some(line_number) = line { | ||||
Simon Sapin
|
r47465 | format_bytes!(b":{}", line_number.to_string().into_bytes()) | ||
Simon Sapin
|
r47215 | } else { | ||
Vec::new() | ||||
}; | ||||
Raphaël Gomès
|
r50382 | CommandError::abort_with_exit_code_bytes( | ||
format_bytes!( | ||||
Simon Sapin
|
r47465 | b"config error at {}{}: {}", | ||
Simon Sapin
|
r47249 | origin, | ||
Simon Sapin
|
r47215 | line_message, | ||
Simon Sapin
|
r47465 | message | ||
Simon Sapin
|
r47215 | ), | ||
Raphaël Gomès
|
r50382 | exit_codes::CONFIG_ERROR_ABORT, | ||
) | ||||
Simon Sapin
|
r47215 | } | ||
} | ||||
Simon Sapin
|
r47166 | impl From<(RevlogError, &str)> for CommandError { | ||
fn from((err, rev): (RevlogError, &str)) -> CommandError { | ||||
Simon Sapin
|
r47165 | match err { | ||
Pulkit Goyal
|
r47577 | RevlogError::WDirUnsupported => CommandError::abort( | ||
"abort: working directory revision cannot be specified", | ||||
), | ||||
Simon Sapin
|
r47174 | RevlogError::InvalidRevision => CommandError::abort(format!( | ||
Simon Sapin
|
r47465 | "abort: invalid revision identifier: {}", | ||
Simon Sapin
|
r47174 | rev | ||
Simon Sapin
|
r47165 | )), | ||
Simon Sapin
|
r47174 | RevlogError::AmbiguousPrefix => CommandError::abort(format!( | ||
Simon Sapin
|
r47465 | "abort: ambiguous revision identifier: {}", | ||
Simon Sapin
|
r47174 | rev | ||
Simon Sapin
|
r47165 | )), | ||
Simon Sapin
|
r47174 | RevlogError::Other(error) => error.into(), | ||
Simon Sapin
|
r47165 | } | ||
} | ||||
} | ||||
Simon Sapin
|
r47555 | |||
impl From<StatusError> for CommandError { | ||||
fn from(error: StatusError) -> Self { | ||||
Arseniy Alekseyev
|
r50434 | match error { | ||
StatusError::Pattern(_) => { | ||||
CommandError::unsupported(format!("{}", error)) | ||||
} | ||||
_ => CommandError::abort(format!("{}", error)), | ||||
} | ||||
Simon Sapin
|
r47555 | } | ||
} | ||||
Spencer Baugh
|
r51759 | impl From<HgPathError> for CommandError { | ||
fn from(error: HgPathError) -> Self { | ||||
CommandError::unsupported(format!("{}", error)) | ||||
} | ||||
} | ||||
impl From<PatternError> for CommandError { | ||||
fn from(error: PatternError) -> Self { | ||||
CommandError::unsupported(format!("{}", error)) | ||||
} | ||||
} | ||||
Simon Sapin
|
r47555 | impl From<DirstateMapError> for CommandError { | ||
fn from(error: DirstateMapError) -> Self { | ||||
CommandError::abort(format!("{}", error)) | ||||
} | ||||
} | ||||
impl From<DirstateError> for CommandError { | ||||
fn from(error: DirstateError) -> Self { | ||||
match error { | ||||
DirstateError::Common(error) => error.into(), | ||||
DirstateError::Map(error) => error.into(), | ||||
} | ||||
} | ||||
} | ||||
Simon Sapin
|
r48474 | |||
impl From<DirstateV2ParseError> for CommandError { | ||||
fn from(error: DirstateV2ParseError) -> Self { | ||||
HgError::from(error).into() | ||||
} | ||||
} | ||||
Raphaël Gomès
|
r50380 | |||
impl From<SparseConfigError> for CommandError { | ||||
fn from(e: SparseConfigError) -> Self { | ||||
match e { | ||||
SparseConfigError::IncludesAfterExcludes { context } => { | ||||
Self::abort_with_exit_code_bytes( | ||||
format_bytes!( | ||||
b"{} config cannot have includes after excludes", | ||||
context | ||||
), | ||||
exit_codes::CONFIG_PARSE_ERROR_ABORT, | ||||
) | ||||
} | ||||
SparseConfigError::EntryOutsideSection { context, line } => { | ||||
Self::abort_with_exit_code_bytes( | ||||
format_bytes!( | ||||
b"{} config entry outside of section: {}", | ||||
context, | ||||
&line, | ||||
), | ||||
exit_codes::CONFIG_PARSE_ERROR_ABORT, | ||||
) | ||||
} | ||||
Raphaël Gomès
|
r50383 | SparseConfigError::InvalidNarrowPrefix(prefix) => { | ||
Self::abort_with_exit_code_bytes( | ||||
format_bytes!( | ||||
b"invalid prefix on narrow pattern: {}", | ||||
&prefix | ||||
), | ||||
exit_codes::ABORT, | ||||
) | ||||
} | ||||
SparseConfigError::IncludesInNarrow => Self::abort( | ||||
"including other spec files using '%include' \ | ||||
is not supported in narrowspec", | ||||
), | ||||
Raphaël Gomès
|
r50380 | SparseConfigError::HgError(e) => Self::from(e), | ||
SparseConfigError::PatternError(e) => { | ||||
Self::unsupported(format!("{}", e)) | ||||
} | ||||
} | ||||
} | ||||
} | ||||