git2/
error.rs

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