##// END OF EJS Templates
rhg: Simplify CommandError based on its use...
Simon Sapin -
r47174:ca3f73cc default
parent child Browse files
Show More
@@ -7,6 +7,7 b''
7 7
8 8 //! Contains useful functions, traits, structs, etc. for use in core.
9 9
10 use crate::errors::{HgError, IoErrorContext};
10 11 use crate::utils::hg_path::HgPath;
11 12 use std::{io::Write, ops::Deref};
12 13
@@ -176,3 +177,10 b" pub(crate) fn strip_suffix<'a>(s: &'a st"
176 177 None
177 178 }
178 179 }
180
181 pub fn current_dir() -> Result<std::path::PathBuf, HgError> {
182 std::env::current_dir().map_err(|error| HgError::IoError {
183 error,
184 context: IoErrorContext::CurrentDir,
185 })
186 }
@@ -32,17 +32,18 b" impl<'a> Command for CatCommand<'a> {"
32 32 fn run(&self, ui: &Ui) -> Result<(), CommandError> {
33 33 let repo = Repo::find()?;
34 34 repo.check_requirements()?;
35 let cwd = std::env::current_dir()
36 .or_else(|e| Err(CommandError::CurrentDirNotFound(e)))?;
35 let cwd = hg::utils::current_dir()?;
37 36
38 37 let mut files = vec![];
39 38 for file in self.files.iter() {
39 // TODO: actually normalize `..` path segments etc?
40 40 let normalized = cwd.join(&file);
41 41 let stripped = normalized
42 42 .strip_prefix(&repo.working_directory_path())
43 .or(Err(CommandError::Abort(None)))?;
43 // TODO: error message for path arguments outside of the repo
44 .map_err(|_| CommandError::abort(""))?;
44 45 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
45 .or(Err(CommandError::Abort(None)))?;
46 .map_err(|e| CommandError::abort(e.to_string()))?;
46 47 files.push(hg_file);
47 48 }
48 49
@@ -28,8 +28,7 b" impl<'a> FilesCommand<'a> {"
28 28 repo: &Repo,
29 29 files: impl IntoIterator<Item = &'a HgPath>,
30 30 ) -> Result<(), CommandError> {
31 let cwd = std::env::current_dir()
32 .or_else(|e| Err(CommandError::CurrentDirNotFound(e)))?;
31 let cwd = hg::utils::current_dir()?;
33 32 let rooted_cwd = cwd
34 33 .strip_prefix(repo.working_directory_path())
35 34 .expect("cwd was already checked within the repository");
@@ -1,101 +1,65 b''
1 use crate::exitcode;
2 1 use crate::ui::utf8_to_local;
3 2 use crate::ui::UiError;
4 use format_bytes::format_bytes;
5 use hg::errors::HgError;
3 use hg::errors::{HgError, IoErrorContext};
6 4 use hg::operations::FindRootError;
7 5 use hg::revlog::revlog::RevlogError;
8 use hg::utils::files::get_bytes_from_path;
9 6 use std::convert::From;
10 use std::path::PathBuf;
11 7
12 8 /// The kind of command error
13 #[derive(Debug, derive_more::From)]
9 #[derive(Debug)]
14 10 pub enum CommandError {
15 /// The root of the repository cannot be found
16 RootNotFound(PathBuf),
17 /// The current directory cannot be found
18 CurrentDirNotFound(std::io::Error),
19 /// The standard output stream cannot be written to
20 StdoutError,
21 /// The standard error stream cannot be written to
22 StderrError,
23 /// The command aborted
24 Abort(Option<Vec<u8>>),
11 /// Exit with an error message and "standard" failure exit code.
12 Abort { message: Vec<u8> },
13
25 14 /// A mercurial capability as not been implemented.
15 ///
16 /// There is no error message printed in this case.
17 /// Instead, we exit with a specic status code and a wrapper script may
18 /// fallback to Python-based Mercurial.
26 19 Unimplemented,
27 /// Common cases
28 #[from]
29 Other(HgError),
30 20 }
31 21
32 22 impl CommandError {
33 pub fn get_exit_code(&self) -> exitcode::ExitCode {
34 match self {
35 CommandError::RootNotFound(_) => exitcode::ABORT,
36 CommandError::CurrentDirNotFound(_) => exitcode::ABORT,
37 CommandError::StdoutError => exitcode::ABORT,
38 CommandError::StderrError => exitcode::ABORT,
39 CommandError::Abort(_) => exitcode::ABORT,
40 CommandError::Unimplemented => exitcode::UNIMPLEMENTED_COMMAND,
41 CommandError::Other(HgError::UnsupportedFeature(_)) => {
42 exitcode::UNIMPLEMENTED_COMMAND
43 }
44 CommandError::Other(_) => exitcode::ABORT,
23 pub fn abort(message: impl AsRef<str>) -> Self {
24 CommandError::Abort {
25 // TODO: bytes-based (instead of Unicode-based) formatting
26 // of error messages to handle non-UTF-8 filenames etc:
27 // https://www.mercurial-scm.org/wiki/EncodingStrategy#Mixing_output
28 message: utf8_to_local(message.as_ref()).into(),
45 29 }
46 30 }
31 }
47 32
48 /// Return the message corresponding to the error if any
49 pub fn get_error_message_bytes(&self) -> Option<Vec<u8>> {
50 match self {
51 CommandError::RootNotFound(path) => {
52 let bytes = get_bytes_from_path(path);
53 Some(format_bytes!(
54 b"abort: no repository found in '{}' (.hg not found)!\n",
55 bytes.as_slice()
56 ))
57 }
58 CommandError::CurrentDirNotFound(e) => Some(format_bytes!(
59 b"abort: error getting current working directory: {}\n",
60 e.to_string().as_bytes(),
61 )),
62 CommandError::Abort(message) => message.to_owned(),
63
64 CommandError::StdoutError
65 | CommandError::StderrError
66 | CommandError::Unimplemented
67 | CommandError::Other(HgError::UnsupportedFeature(_)) => None,
68
69 CommandError::Other(e) => {
70 Some(format_bytes!(b"{}\n", e.to_string().as_bytes()))
71 }
33 impl From<HgError> for CommandError {
34 fn from(error: HgError) -> Self {
35 match error {
36 HgError::UnsupportedFeature(_) => CommandError::Unimplemented,
37 _ => CommandError::abort(error.to_string()),
72 38 }
73 39 }
74
75 /// Exist the process with the corresponding exit code.
76 pub fn exit(&self) {
77 std::process::exit(self.get_exit_code())
78 }
79 40 }
80 41
81 42 impl From<UiError> for CommandError {
82 fn from(error: UiError) -> Self {
83 match error {
84 UiError::StdoutError(_) => CommandError::StdoutError,
85 UiError::StderrError(_) => CommandError::StderrError,
86 }
43 fn from(_error: UiError) -> Self {
44 // If we already failed writing to stdout or stderr,
45 // writing an error message to stderr about it would be likely to fail
46 // too.
47 CommandError::abort("")
87 48 }
88 49 }
89 50
90 51 impl From<FindRootError> for CommandError {
91 52 fn from(err: FindRootError) -> Self {
92 53 match err {
93 FindRootError::RootNotFound(path) => {
94 CommandError::RootNotFound(path)
54 FindRootError::RootNotFound(path) => CommandError::abort(format!(
55 "no repository found in '{}' (.hg not found)!",
56 path.display()
57 )),
58 FindRootError::GetCurrentDirError(error) => HgError::IoError {
59 error,
60 context: IoErrorContext::CurrentDir,
95 61 }
96 FindRootError::GetCurrentDirError(e) => {
97 CommandError::CurrentDirNotFound(e)
98 }
62 .into(),
99 63 }
100 64 }
101 65 }
@@ -103,21 +67,15 b' impl From<FindRootError> for CommandErro'
103 67 impl From<(RevlogError, &str)> for CommandError {
104 68 fn from((err, rev): (RevlogError, &str)) -> CommandError {
105 69 match err {
106 RevlogError::InvalidRevision => CommandError::Abort(Some(
107 utf8_to_local(&format!(
108 "abort: invalid revision identifier {}\n",
109 rev
110 ))
111 .into(),
70 RevlogError::InvalidRevision => CommandError::abort(format!(
71 "invalid revision identifier {}",
72 rev
112 73 )),
113 RevlogError::AmbiguousPrefix => CommandError::Abort(Some(
114 utf8_to_local(&format!(
115 "abort: ambiguous revision identifier {}\n",
116 rev
117 ))
118 .into(),
74 RevlogError::AmbiguousPrefix => CommandError::abort(format!(
75 "ambiguous revision identifier {}",
76 rev
119 77 )),
120 RevlogError::Other(err) => CommandError::Other(err),
78 RevlogError::Other(error) => error.into(),
121 79 }
122 80 }
123 81 }
@@ -6,5 +6,5 b' pub const OK: ExitCode = 0;'
6 6 /// Generic abort
7 7 pub const ABORT: ExitCode = 255;
8 8
9 /// Command not implemented by rhg
10 pub const UNIMPLEMENTED_COMMAND: ExitCode = 252;
9 /// Command or feature not implemented by rhg
10 pub const UNIMPLEMENTED: ExitCode = 252;
@@ -5,6 +5,7 b' use clap::Arg;'
5 5 use clap::ArgGroup;
6 6 use clap::ArgMatches;
7 7 use clap::SubCommand;
8 use format_bytes::format_bytes;
8 9 use hg::operations::DebugDataKind;
9 10 use std::convert::TryFrom;
10 11
@@ -91,26 +92,31 b' fn main() {'
91 92
92 93 let matches = app.clone().get_matches_safe().unwrap_or_else(|err| {
93 94 let _ = ui::Ui::new().writeln_stderr_str(&err.message);
94 std::process::exit(exitcode::UNIMPLEMENTED_COMMAND)
95 std::process::exit(exitcode::UNIMPLEMENTED)
95 96 });
96 97
97 98 let ui = ui::Ui::new();
98 99
99 100 let command_result = match_subcommand(matches, &ui);
100 101
101 match command_result {
102 Ok(_) => std::process::exit(exitcode::OK),
103 Err(e) => {
104 let message = e.get_error_message_bytes();
105 if let Some(msg) = message {
106 match ui.write_stderr(&msg) {
107 Ok(_) => (),
108 Err(_) => std::process::exit(exitcode::ABORT),
109 };
110 };
111 e.exit()
102 let exit_code = match command_result {
103 Ok(_) => exitcode::OK,
104
105 // Exit with a specific code and no error message to let a potential
106 // wrapper script fallback to Python-based Mercurial.
107 Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED,
108
109 Err(CommandError::Abort { message }) => {
110 if !message.is_empty() {
111 // Ignore errors when writing to stderr, we’re already exiting
112 // with failure code so there’s not much more we can do.
113 let _ =
114 ui.write_stderr(&format_bytes!(b"abort: {}\n", message));
115 }
116 exitcode::ABORT
112 117 }
113 }
118 };
119 std::process::exit(exit_code)
114 120 }
115 121
116 122 fn match_subcommand(
@@ -38,7 +38,7 b' Unwritable file descriptor'
38 38 Deleted repository
39 39 $ rm -rf `pwd`
40 40 $ rhg root
41 abort: error getting current working directory: $ENOENT$
41 abort: $ENOENT$: current directory
42 42 [255]
43 43
44 44 Listing tracked files
@@ -163,7 +163,7 b' Requirements'
163 163
164 164 $ echo -e '\xFF' >> .hg/requires
165 165 $ rhg debugrequirements
166 corrupted repository: parse error in 'requires' file
166 abort: corrupted repository: parse error in 'requires' file
167 167 [255]
168 168
169 169 Persistent nodemap
General Comments 0
You need to be logged in to leave comments. Login now