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