error.rs 6.11 KB
Newer Older
1
use crate::ui::utf8_to_local;
2
use crate::ui::UiError;
3
use crate::NoRepoInCwdError;
4
use format_bytes::format_bytes;
5
use hg::config::{ConfigError, ConfigParseError, ConfigValueParseError};
6
use hg::errors::HgError;
7
use hg::exit_codes;
8
use hg::repo::RepoError;
9
use hg::revlog::revlog::RevlogError;
10
use hg::utils::files::get_bytes_from_path;
11
use hg::{DirstateError, DirstateMapError, StatusError};
12
13
14
use std::convert::From;

/// The kind of command error
15
#[derive(Debug)]
16
pub enum CommandError {
17
    /// Exit with an error message and "standard" failure exit code.
18
19
    Abort {
        message: Vec<u8>,
20
        detailed_exit_code: exit_codes::ExitCode,
21
    },
22

23
24
25
    /// Exit with a failure exit code but no message.
    Unsuccessful,

26
27
28
29
30
    /// 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> },
31
32
}

33
impl CommandError {
34
    pub fn abort(message: impl AsRef<str>) -> Self {
35
        CommandError::abort_with_exit_code(message, exit_codes::ABORT)
36
37
38
39
    }

    pub fn abort_with_exit_code(
        message: impl AsRef<str>,
40
        detailed_exit_code: exit_codes::ExitCode,
41
    ) -> Self {
42
43
44
45
46
        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(),
47
            detailed_exit_code: detailed_exit_code,
48
49
        }
    }
50
51
52
53
54
55

    pub fn unsupported(message: impl AsRef<str>) -> Self {
        CommandError::UnsupportedFeature {
            message: utf8_to_local(message.as_ref()).into(),
        }
    }
56
}
57

58
59
60
/// 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 {
61
62
    fn from(error: clap::Error) -> Self {
        CommandError::unsupported(error.to_string())
63
64
65
    }
}

66
67
68
impl From<HgError> for CommandError {
    fn from(error: HgError) -> Self {
        match error {
69
70
71
            HgError::UnsupportedFeature(message) => {
                CommandError::unsupported(message)
            }
72
73
74
75
76
77
            HgError::Abort {
                message,
                detailed_exit_code,
            } => {
                CommandError::abort_with_exit_code(message, detailed_exit_code)
            }
78
            _ => CommandError::abort(error.to_string()),
79
80
        }
    }
81
82
}

83
84
impl From<ConfigValueParseError> for CommandError {
    fn from(error: ConfigValueParseError) -> Self {
85
86
        CommandError::abort_with_exit_code(
            error.to_string(),
87
            exit_codes::CONFIG_ERROR_ABORT,
88
        )
89
90
91
    }
}

92
impl From<UiError> for CommandError {
93
94
95
96
97
    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("")
98
99
    }
}
100

101
102
impl From<RepoError> for CommandError {
    fn from(error: RepoError) -> Self {
103
        match error {
104
            RepoError::NotFound { at } => CommandError::Abort {
105
                message: format_bytes!(
106
                    b"abort: repository {} not found",
107
                    get_bytes_from_path(at)
108
                ),
109
                detailed_exit_code: exit_codes::ABORT,
110
            },
111
112
            RepoError::ConfigParseError(error) => error.into(),
            RepoError::Other(error) => error.into(),
113
114
115
        }
    }
}
116

117
118
119
120
121
impl<'a> From<&'a NoRepoInCwdError> for CommandError {
    fn from(error: &'a NoRepoInCwdError) -> Self {
        let NoRepoInCwdError { cwd } = error;
        CommandError::Abort {
            message: format_bytes!(
122
                b"abort: no repository found in '{}' (.hg not found)!",
123
124
                get_bytes_from_path(cwd)
            ),
125
            detailed_exit_code: exit_codes::ABORT,
126
127
128
129
        }
    }
}

130
131
132
impl From<ConfigError> for CommandError {
    fn from(error: ConfigError) -> Self {
        match error {
133
            ConfigError::Parse(error) => error.into(),
134
135
136
137
138
            ConfigError::Other(error) => error.into(),
        }
    }
}

139
140
141
142
143
impl From<ConfigParseError> for CommandError {
    fn from(error: ConfigParseError) -> Self {
        let ConfigParseError {
            origin,
            line,
144
            message,
145
146
        } = error;
        let line_message = if let Some(line_number) = line {
147
            format_bytes!(b":{}", line_number.to_string().into_bytes())
148
149
150
151
152
        } else {
            Vec::new()
        };
        CommandError::Abort {
            message: format_bytes!(
153
                b"config error at {}{}: {}",
154
                origin,
155
                line_message,
156
                message
157
            ),
158
            detailed_exit_code: exit_codes::CONFIG_ERROR_ABORT,
159
160
161
162
        }
    }
}

163
164
impl From<(RevlogError, &str)> for CommandError {
    fn from((err, rev): (RevlogError, &str)) -> CommandError {
165
        match err {
166
167
168
            RevlogError::WDirUnsupported => CommandError::abort(
                "abort: working directory revision cannot be specified",
            ),
169
            RevlogError::InvalidRevision => CommandError::abort(format!(
170
                "abort: invalid revision identifier: {}",
171
                rev
172
            )),
173
            RevlogError::AmbiguousPrefix => CommandError::abort(format!(
174
                "abort: ambiguous revision identifier: {}",
175
                rev
176
            )),
177
            RevlogError::Other(error) => error.into(),
178
179
180
        }
    }
}
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201

impl From<StatusError> for CommandError {
    fn from(error: StatusError) -> Self {
        CommandError::abort(format!("{}", error))
    }
}

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(),
        }
    }
}