1 use libc::c_int;
2 use std::env::JoinPathsError;
3 use std::error;
4 use std::ffi::{CStr, NulError};
5 use std::fmt;
6 use std::str;
7 
8 use crate::{raw, ErrorClass, ErrorCode};
9 
10 /// A structure to represent errors coming out of libgit2.
11 #[derive(Debug, PartialEq)]
12 pub struct Error {
13     code: c_int,
14     klass: c_int,
15     message: String,
16 }
17 
18 impl Error {
19     /// Creates a new error.
20     ///
21     /// This is mainly intended for implementors of custom transports or
22     /// database backends, where it is desirable to propagate an [`Error`]
23     /// through `libgit2`.
new<S: AsRef<str>>(code: ErrorCode, class: ErrorClass, message: S) -> Self24     pub fn new<S: AsRef<str>>(code: ErrorCode, class: ErrorClass, message: S) -> Self {
25         let mut err = Error::from_str(message.as_ref());
26         err.set_code(code);
27         err.set_class(class);
28         err
29     }
30 
31     /// Returns the last error that happened with the code specified by `code`.
32     ///
33     /// The `code` argument typically comes from the return value of a function
34     /// call. This code will later be returned from the `code` function.
35     ///
36     /// Historically this function returned `Some` or `None` based on the return
37     /// value of `git_error_last` but nowadays it always returns `Some` so it's
38     /// safe to unwrap the return value. This API will change in the next major
39     /// version.
last_error(code: c_int) -> Option<Error>40     pub fn last_error(code: c_int) -> Option<Error> {
41         crate::init();
42         unsafe {
43             // Note that whenever libgit2 returns an error any negative value
44             // indicates that an error happened. Auxiliary information is
45             // *usually* in `git_error_last` but unfortunately that's not always
46             // the case. Sometimes a negative error code is returned from
47             // libgit2 *without* calling `git_error_set` internally to configure
48             // the error.
49             //
50             // To handle this case and hopefully provide better error messages
51             // on our end we unconditionally call `git_error_clear` when we're done
52             // with an error. This is an attempt to clear it as aggressively as
53             // possible when we can to ensure that error information from one
54             // api invocation doesn't leak over to the next api invocation.
55             //
56             // Additionally if `git_error_last` returns null then we returned a
57             // canned error out.
58             let ptr = raw::git_error_last();
59             let err = if ptr.is_null() {
60                 let mut error = Error::from_str("an unknown git error occurred");
61                 error.code = code;
62                 error
63             } else {
64                 Error::from_raw(code, ptr)
65             };
66             raw::git_error_clear();
67             Some(err)
68         }
69     }
70 
from_raw(code: c_int, ptr: *const raw::git_error) -> Error71     unsafe fn from_raw(code: c_int, ptr: *const raw::git_error) -> Error {
72         let msg = CStr::from_ptr((*ptr).message as *const _).to_bytes();
73         let msg = String::from_utf8_lossy(msg).into_owned();
74         Error {
75             code: code,
76             klass: (*ptr).klass,
77             message: msg,
78         }
79     }
80 
81     /// Creates a new error from the given string as the error.
82     ///
83     /// The error returned will have the code `GIT_ERROR` and the class
84     /// `GIT_ERROR_NONE`.
from_str(s: &str) -> Error85     pub fn from_str(s: &str) -> Error {
86         Error {
87             code: raw::GIT_ERROR as c_int,
88             klass: raw::GIT_ERROR_NONE as c_int,
89             message: s.to_string(),
90         }
91     }
92 
93     /// Return the error code associated with this error.
94     ///
95     /// An error code is intended to be programmatically actionable most of the
96     /// time. For example the code `GIT_EAGAIN` indicates that an error could be
97     /// fixed by trying again, while the code `GIT_ERROR` is more bland and
98     /// doesn't convey anything in particular.
code(&self) -> ErrorCode99     pub fn code(&self) -> ErrorCode {
100         match self.raw_code() {
101             raw::GIT_OK => super::ErrorCode::GenericError,
102             raw::GIT_ERROR => super::ErrorCode::GenericError,
103             raw::GIT_ENOTFOUND => super::ErrorCode::NotFound,
104             raw::GIT_EEXISTS => super::ErrorCode::Exists,
105             raw::GIT_EAMBIGUOUS => super::ErrorCode::Ambiguous,
106             raw::GIT_EBUFS => super::ErrorCode::BufSize,
107             raw::GIT_EUSER => super::ErrorCode::User,
108             raw::GIT_EBAREREPO => super::ErrorCode::BareRepo,
109             raw::GIT_EUNBORNBRANCH => super::ErrorCode::UnbornBranch,
110             raw::GIT_EUNMERGED => super::ErrorCode::Unmerged,
111             raw::GIT_ENONFASTFORWARD => super::ErrorCode::NotFastForward,
112             raw::GIT_EINVALIDSPEC => super::ErrorCode::InvalidSpec,
113             raw::GIT_ECONFLICT => super::ErrorCode::Conflict,
114             raw::GIT_ELOCKED => super::ErrorCode::Locked,
115             raw::GIT_EMODIFIED => super::ErrorCode::Modified,
116             raw::GIT_PASSTHROUGH => super::ErrorCode::GenericError,
117             raw::GIT_ITEROVER => super::ErrorCode::GenericError,
118             raw::GIT_EAUTH => super::ErrorCode::Auth,
119             raw::GIT_ECERTIFICATE => super::ErrorCode::Certificate,
120             raw::GIT_EAPPLIED => super::ErrorCode::Applied,
121             raw::GIT_EPEEL => super::ErrorCode::Peel,
122             raw::GIT_EEOF => super::ErrorCode::Eof,
123             raw::GIT_EINVALID => super::ErrorCode::Invalid,
124             raw::GIT_EUNCOMMITTED => super::ErrorCode::Uncommitted,
125             raw::GIT_EDIRECTORY => super::ErrorCode::Directory,
126             raw::GIT_EMERGECONFLICT => super::ErrorCode::MergeConflict,
127             raw::GIT_EMISMATCH => super::ErrorCode::HashsumMismatch,
128             raw::GIT_EINDEXDIRTY => super::ErrorCode::IndexDirty,
129             raw::GIT_EAPPLYFAIL => super::ErrorCode::ApplyFail,
130             _ => super::ErrorCode::GenericError,
131         }
132     }
133 
134     /// Modify the error code associated with this error.
135     ///
136     /// This is mainly intended to be used by implementors of custom transports
137     /// or database backends, and should be used with care.
set_code(&mut self, code: ErrorCode)138     pub fn set_code(&mut self, code: ErrorCode) {
139         self.code = match code {
140             ErrorCode::GenericError => raw::GIT_ERROR,
141             ErrorCode::NotFound => raw::GIT_ENOTFOUND,
142             ErrorCode::Exists => raw::GIT_EEXISTS,
143             ErrorCode::Ambiguous => raw::GIT_EAMBIGUOUS,
144             ErrorCode::BufSize => raw::GIT_EBUFS,
145             ErrorCode::User => raw::GIT_EUSER,
146             ErrorCode::BareRepo => raw::GIT_EBAREREPO,
147             ErrorCode::UnbornBranch => raw::GIT_EUNBORNBRANCH,
148             ErrorCode::Unmerged => raw::GIT_EUNMERGED,
149             ErrorCode::NotFastForward => raw::GIT_ENONFASTFORWARD,
150             ErrorCode::InvalidSpec => raw::GIT_EINVALIDSPEC,
151             ErrorCode::Conflict => raw::GIT_ECONFLICT,
152             ErrorCode::Locked => raw::GIT_ELOCKED,
153             ErrorCode::Modified => raw::GIT_EMODIFIED,
154             ErrorCode::Auth => raw::GIT_EAUTH,
155             ErrorCode::Certificate => raw::GIT_ECERTIFICATE,
156             ErrorCode::Applied => raw::GIT_EAPPLIED,
157             ErrorCode::Peel => raw::GIT_EPEEL,
158             ErrorCode::Eof => raw::GIT_EEOF,
159             ErrorCode::Invalid => raw::GIT_EINVALID,
160             ErrorCode::Uncommitted => raw::GIT_EUNCOMMITTED,
161             ErrorCode::Directory => raw::GIT_EDIRECTORY,
162             ErrorCode::MergeConflict => raw::GIT_EMERGECONFLICT,
163             ErrorCode::HashsumMismatch => raw::GIT_EMISMATCH,
164             ErrorCode::IndexDirty => raw::GIT_EINDEXDIRTY,
165             ErrorCode::ApplyFail => raw::GIT_EAPPLYFAIL,
166         };
167     }
168 
169     /// Return the error class associated with this error.
170     ///
171     /// Error classes are in general mostly just informative. For example the
172     /// class will show up in the error message but otherwise an error class is
173     /// typically not directly actionable.
class(&self) -> ErrorClass174     pub fn class(&self) -> ErrorClass {
175         match self.raw_class() {
176             raw::GIT_ERROR_NONE => super::ErrorClass::None,
177             raw::GIT_ERROR_NOMEMORY => super::ErrorClass::NoMemory,
178             raw::GIT_ERROR_OS => super::ErrorClass::Os,
179             raw::GIT_ERROR_INVALID => super::ErrorClass::Invalid,
180             raw::GIT_ERROR_REFERENCE => super::ErrorClass::Reference,
181             raw::GIT_ERROR_ZLIB => super::ErrorClass::Zlib,
182             raw::GIT_ERROR_REPOSITORY => super::ErrorClass::Repository,
183             raw::GIT_ERROR_CONFIG => super::ErrorClass::Config,
184             raw::GIT_ERROR_REGEX => super::ErrorClass::Regex,
185             raw::GIT_ERROR_ODB => super::ErrorClass::Odb,
186             raw::GIT_ERROR_INDEX => super::ErrorClass::Index,
187             raw::GIT_ERROR_OBJECT => super::ErrorClass::Object,
188             raw::GIT_ERROR_NET => super::ErrorClass::Net,
189             raw::GIT_ERROR_TAG => super::ErrorClass::Tag,
190             raw::GIT_ERROR_TREE => super::ErrorClass::Tree,
191             raw::GIT_ERROR_INDEXER => super::ErrorClass::Indexer,
192             raw::GIT_ERROR_SSL => super::ErrorClass::Ssl,
193             raw::GIT_ERROR_SUBMODULE => super::ErrorClass::Submodule,
194             raw::GIT_ERROR_THREAD => super::ErrorClass::Thread,
195             raw::GIT_ERROR_STASH => super::ErrorClass::Stash,
196             raw::GIT_ERROR_CHECKOUT => super::ErrorClass::Checkout,
197             raw::GIT_ERROR_FETCHHEAD => super::ErrorClass::FetchHead,
198             raw::GIT_ERROR_MERGE => super::ErrorClass::Merge,
199             raw::GIT_ERROR_SSH => super::ErrorClass::Ssh,
200             raw::GIT_ERROR_FILTER => super::ErrorClass::Filter,
201             raw::GIT_ERROR_REVERT => super::ErrorClass::Revert,
202             raw::GIT_ERROR_CALLBACK => super::ErrorClass::Callback,
203             raw::GIT_ERROR_CHERRYPICK => super::ErrorClass::CherryPick,
204             raw::GIT_ERROR_DESCRIBE => super::ErrorClass::Describe,
205             raw::GIT_ERROR_REBASE => super::ErrorClass::Rebase,
206             raw::GIT_ERROR_FILESYSTEM => super::ErrorClass::Filesystem,
207             raw::GIT_ERROR_PATCH => super::ErrorClass::Patch,
208             raw::GIT_ERROR_WORKTREE => super::ErrorClass::Worktree,
209             raw::GIT_ERROR_SHA1 => super::ErrorClass::Sha1,
210             raw::GIT_ERROR_HTTP => super::ErrorClass::Http,
211             _ => super::ErrorClass::None,
212         }
213     }
214 
215     /// Modify the error class associated with this error.
216     ///
217     /// This is mainly intended to be used by implementors of custom transports
218     /// or database backends, and should be used with care.
set_class(&mut self, class: ErrorClass)219     pub fn set_class(&mut self, class: ErrorClass) {
220         self.klass = match class {
221             ErrorClass::None => raw::GIT_ERROR_NONE,
222             ErrorClass::NoMemory => raw::GIT_ERROR_NOMEMORY,
223             ErrorClass::Os => raw::GIT_ERROR_OS,
224             ErrorClass::Invalid => raw::GIT_ERROR_INVALID,
225             ErrorClass::Reference => raw::GIT_ERROR_REFERENCE,
226             ErrorClass::Zlib => raw::GIT_ERROR_ZLIB,
227             ErrorClass::Repository => raw::GIT_ERROR_REPOSITORY,
228             ErrorClass::Config => raw::GIT_ERROR_CONFIG,
229             ErrorClass::Regex => raw::GIT_ERROR_REGEX,
230             ErrorClass::Odb => raw::GIT_ERROR_ODB,
231             ErrorClass::Index => raw::GIT_ERROR_INDEX,
232             ErrorClass::Object => raw::GIT_ERROR_OBJECT,
233             ErrorClass::Net => raw::GIT_ERROR_NET,
234             ErrorClass::Tag => raw::GIT_ERROR_TAG,
235             ErrorClass::Tree => raw::GIT_ERROR_TREE,
236             ErrorClass::Indexer => raw::GIT_ERROR_INDEXER,
237             ErrorClass::Ssl => raw::GIT_ERROR_SSL,
238             ErrorClass::Submodule => raw::GIT_ERROR_SUBMODULE,
239             ErrorClass::Thread => raw::GIT_ERROR_THREAD,
240             ErrorClass::Stash => raw::GIT_ERROR_STASH,
241             ErrorClass::Checkout => raw::GIT_ERROR_CHECKOUT,
242             ErrorClass::FetchHead => raw::GIT_ERROR_FETCHHEAD,
243             ErrorClass::Merge => raw::GIT_ERROR_MERGE,
244             ErrorClass::Ssh => raw::GIT_ERROR_SSH,
245             ErrorClass::Filter => raw::GIT_ERROR_FILTER,
246             ErrorClass::Revert => raw::GIT_ERROR_REVERT,
247             ErrorClass::Callback => raw::GIT_ERROR_CALLBACK,
248             ErrorClass::CherryPick => raw::GIT_ERROR_CHERRYPICK,
249             ErrorClass::Describe => raw::GIT_ERROR_DESCRIBE,
250             ErrorClass::Rebase => raw::GIT_ERROR_REBASE,
251             ErrorClass::Filesystem => raw::GIT_ERROR_FILESYSTEM,
252             ErrorClass::Patch => raw::GIT_ERROR_PATCH,
253             ErrorClass::Worktree => raw::GIT_ERROR_WORKTREE,
254             ErrorClass::Sha1 => raw::GIT_ERROR_SHA1,
255             ErrorClass::Http => raw::GIT_ERROR_HTTP,
256         } as c_int;
257     }
258 
259     /// Return the raw error code associated with this error.
raw_code(&self) -> raw::git_error_code260     pub fn raw_code(&self) -> raw::git_error_code {
261         macro_rules! check( ($($e:ident,)*) => (
262             $(if self.code == raw::$e as c_int { raw::$e }) else *
263             else {
264                 raw::GIT_ERROR
265             }
266         ) );
267         check!(
268             GIT_OK,
269             GIT_ERROR,
270             GIT_ENOTFOUND,
271             GIT_EEXISTS,
272             GIT_EAMBIGUOUS,
273             GIT_EBUFS,
274             GIT_EUSER,
275             GIT_EBAREREPO,
276             GIT_EUNBORNBRANCH,
277             GIT_EUNMERGED,
278             GIT_ENONFASTFORWARD,
279             GIT_EINVALIDSPEC,
280             GIT_ECONFLICT,
281             GIT_ELOCKED,
282             GIT_EMODIFIED,
283             GIT_EAUTH,
284             GIT_ECERTIFICATE,
285             GIT_EAPPLIED,
286             GIT_EPEEL,
287             GIT_EEOF,
288             GIT_EINVALID,
289             GIT_EUNCOMMITTED,
290             GIT_PASSTHROUGH,
291             GIT_ITEROVER,
292             GIT_RETRY,
293             GIT_EMISMATCH,
294             GIT_EINDEXDIRTY,
295             GIT_EAPPLYFAIL,
296         )
297     }
298 
299     /// Return the raw error class associated with this error.
raw_class(&self) -> raw::git_error_t300     pub fn raw_class(&self) -> raw::git_error_t {
301         macro_rules! check( ($($e:ident,)*) => (
302             $(if self.klass == raw::$e as c_int { raw::$e }) else *
303             else {
304                 raw::GIT_ERROR_NONE
305             }
306         ) );
307         check!(
308             GIT_ERROR_NONE,
309             GIT_ERROR_NOMEMORY,
310             GIT_ERROR_OS,
311             GIT_ERROR_INVALID,
312             GIT_ERROR_REFERENCE,
313             GIT_ERROR_ZLIB,
314             GIT_ERROR_REPOSITORY,
315             GIT_ERROR_CONFIG,
316             GIT_ERROR_REGEX,
317             GIT_ERROR_ODB,
318             GIT_ERROR_INDEX,
319             GIT_ERROR_OBJECT,
320             GIT_ERROR_NET,
321             GIT_ERROR_TAG,
322             GIT_ERROR_TREE,
323             GIT_ERROR_INDEXER,
324             GIT_ERROR_SSL,
325             GIT_ERROR_SUBMODULE,
326             GIT_ERROR_THREAD,
327             GIT_ERROR_STASH,
328             GIT_ERROR_CHECKOUT,
329             GIT_ERROR_FETCHHEAD,
330             GIT_ERROR_MERGE,
331             GIT_ERROR_SSH,
332             GIT_ERROR_FILTER,
333             GIT_ERROR_REVERT,
334             GIT_ERROR_CALLBACK,
335             GIT_ERROR_CHERRYPICK,
336             GIT_ERROR_DESCRIBE,
337             GIT_ERROR_REBASE,
338             GIT_ERROR_FILESYSTEM,
339             GIT_ERROR_PATCH,
340             GIT_ERROR_WORKTREE,
341             GIT_ERROR_SHA1,
342             GIT_ERROR_HTTP,
343         )
344     }
345 
346     /// Return the message associated with this error
message(&self) -> &str347     pub fn message(&self) -> &str {
348         &self.message
349     }
350 }
351 
352 impl error::Error for Error {}
353 
354 impl fmt::Display for Error {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result355     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
356         write!(f, "{}", self.message)?;
357         match self.class() {
358             ErrorClass::None => {}
359             other => write!(f, "; class={:?} ({})", other, self.klass)?,
360         }
361         match self.code() {
362             ErrorCode::GenericError => {}
363             other => write!(f, "; code={:?} ({})", other, self.code)?,
364         }
365         Ok(())
366     }
367 }
368 
369 impl From<NulError> for Error {
from(_: NulError) -> Error370     fn from(_: NulError) -> Error {
371         Error::from_str(
372             "data contained a nul byte that could not be \
373              represented as a string",
374         )
375     }
376 }
377 
378 impl From<JoinPathsError> for Error {
from(e: JoinPathsError) -> Error379     fn from(e: JoinPathsError) -> Error {
380         Error::from_str(&e.to_string())
381     }
382 }
383 
384 #[cfg(test)]
385 mod tests {
386     use crate::{ErrorClass, ErrorCode};
387 
388     #[test]
smoke()389     fn smoke() {
390         let (_td, repo) = crate::test::repo_init();
391 
392         let err = repo.find_submodule("does_not_exist").err().unwrap();
393         assert_eq!(err.code(), ErrorCode::NotFound);
394         assert_eq!(err.class(), ErrorClass::Submodule);
395     }
396 }
397