use libc::c_int; use std::env::JoinPathsError; use std::error; use std::ffi::{CStr, NulError}; use std::fmt; use std::str; use crate::{raw, ErrorClass, ErrorCode}; /// A structure to represent errors coming out of libgit2. #[derive(Debug, PartialEq)] pub struct Error { code: c_int, klass: c_int, message: String, } impl Error { /// Creates a new error. /// /// This is mainly intended for implementers of custom transports or /// database backends, where it is desirable to propagate an [`Error`] /// through `libgit2`. pub fn new>(code: ErrorCode, class: ErrorClass, message: S) -> Self { let mut err = Error::from_str(message.as_ref()); err.set_code(code); err.set_class(class); err } /// Returns the last error that happened with the code specified by `code`. /// /// The `code` argument typically comes from the return value of a function /// call. This code will later be returned from the `code` function. /// /// Historically this function returned `Some` or `None` based on the return /// value of `git_error_last` but nowadays it always returns `Some` so it's /// safe to unwrap the return value. This API will change in the next major /// version. pub fn last_error(code: c_int) -> Option { crate::init(); unsafe { // Note that whenever libgit2 returns an error any negative value // indicates that an error happened. Auxiliary information is // *usually* in `git_error_last` but unfortunately that's not always // the case. Sometimes a negative error code is returned from // libgit2 *without* calling `git_error_set` internally to configure // the error. // // To handle this case and hopefully provide better error messages // on our end we unconditionally call `git_error_clear` when we're done // with an error. This is an attempt to clear it as aggressively as // possible when we can to ensure that error information from one // api invocation doesn't leak over to the next api invocation. // // Additionally if `git_error_last` returns null then we returned a // canned error out. let ptr = raw::git_error_last(); let err = if ptr.is_null() { let mut error = Error::from_str("an unknown git error occurred"); error.code = code; error } else { Error::from_raw(code, ptr) }; raw::git_error_clear(); Some(err) } } unsafe fn from_raw(code: c_int, ptr: *const raw::git_error) -> Error { let message = CStr::from_ptr((*ptr).message as *const _).to_bytes(); let message = String::from_utf8_lossy(message).into_owned(); Error { code, klass: (*ptr).klass, message, } } /// Creates a new error from the given string as the error. /// /// The error returned will have the code `GIT_ERROR` and the class /// `GIT_ERROR_NONE`. pub fn from_str(s: &str) -> Error { Error { code: raw::GIT_ERROR as c_int, klass: raw::GIT_ERROR_NONE as c_int, message: s.to_string(), } } /// Return the error code associated with this error. /// /// An error code is intended to be programmatically actionable most of the /// time. For example the code `GIT_EAGAIN` indicates that an error could be /// fixed by trying again, while the code `GIT_ERROR` is more bland and /// doesn't convey anything in particular. pub fn code(&self) -> ErrorCode { match self.raw_code() { raw::GIT_OK => super::ErrorCode::GenericError, raw::GIT_ERROR => super::ErrorCode::GenericError, raw::GIT_ENOTFOUND => super::ErrorCode::NotFound, raw::GIT_EEXISTS => super::ErrorCode::Exists, raw::GIT_EAMBIGUOUS => super::ErrorCode::Ambiguous, raw::GIT_EBUFS => super::ErrorCode::BufSize, raw::GIT_EUSER => super::ErrorCode::User, raw::GIT_EBAREREPO => super::ErrorCode::BareRepo, raw::GIT_EUNBORNBRANCH => super::ErrorCode::UnbornBranch, raw::GIT_EUNMERGED => super::ErrorCode::Unmerged, raw::GIT_ENONFASTFORWARD => super::ErrorCode::NotFastForward, raw::GIT_EINVALIDSPEC => super::ErrorCode::InvalidSpec, raw::GIT_ECONFLICT => super::ErrorCode::Conflict, raw::GIT_ELOCKED => super::ErrorCode::Locked, raw::GIT_EMODIFIED => super::ErrorCode::Modified, raw::GIT_PASSTHROUGH => super::ErrorCode::GenericError, raw::GIT_ITEROVER => super::ErrorCode::GenericError, raw::GIT_EAUTH => super::ErrorCode::Auth, raw::GIT_ECERTIFICATE => super::ErrorCode::Certificate, raw::GIT_EAPPLIED => super::ErrorCode::Applied, raw::GIT_EPEEL => super::ErrorCode::Peel, raw::GIT_EEOF => super::ErrorCode::Eof, raw::GIT_EINVALID => super::ErrorCode::Invalid, raw::GIT_EUNCOMMITTED => super::ErrorCode::Uncommitted, raw::GIT_EDIRECTORY => super::ErrorCode::Directory, raw::GIT_EMERGECONFLICT => super::ErrorCode::MergeConflict, raw::GIT_EMISMATCH => super::ErrorCode::HashsumMismatch, raw::GIT_EINDEXDIRTY => super::ErrorCode::IndexDirty, raw::GIT_EAPPLYFAIL => super::ErrorCode::ApplyFail, raw::GIT_EOWNER => super::ErrorCode::Owner, _ => super::ErrorCode::GenericError, } } /// Modify the error code associated with this error. /// /// This is mainly intended to be used by implementers of custom transports /// or database backends, and should be used with care. pub fn set_code(&mut self, code: ErrorCode) { self.code = match code { ErrorCode::GenericError => raw::GIT_ERROR, ErrorCode::NotFound => raw::GIT_ENOTFOUND, ErrorCode::Exists => raw::GIT_EEXISTS, ErrorCode::Ambiguous => raw::GIT_EAMBIGUOUS, ErrorCode::BufSize => raw::GIT_EBUFS, ErrorCode::User => raw::GIT_EUSER, ErrorCode::BareRepo => raw::GIT_EBAREREPO, ErrorCode::UnbornBranch => raw::GIT_EUNBORNBRANCH, ErrorCode::Unmerged => raw::GIT_EUNMERGED, ErrorCode::NotFastForward => raw::GIT_ENONFASTFORWARD, ErrorCode::InvalidSpec => raw::GIT_EINVALIDSPEC, ErrorCode::Conflict => raw::GIT_ECONFLICT, ErrorCode::Locked => raw::GIT_ELOCKED, ErrorCode::Modified => raw::GIT_EMODIFIED, ErrorCode::Auth => raw::GIT_EAUTH, ErrorCode::Certificate => raw::GIT_ECERTIFICATE, ErrorCode::Applied => raw::GIT_EAPPLIED, ErrorCode::Peel => raw::GIT_EPEEL, ErrorCode::Eof => raw::GIT_EEOF, ErrorCode::Invalid => raw::GIT_EINVALID, ErrorCode::Uncommitted => raw::GIT_EUNCOMMITTED, ErrorCode::Directory => raw::GIT_EDIRECTORY, ErrorCode::MergeConflict => raw::GIT_EMERGECONFLICT, ErrorCode::HashsumMismatch => raw::GIT_EMISMATCH, ErrorCode::IndexDirty => raw::GIT_EINDEXDIRTY, ErrorCode::ApplyFail => raw::GIT_EAPPLYFAIL, ErrorCode::Owner => raw::GIT_EOWNER, }; } /// Return the error class associated with this error. /// /// Error classes are in general mostly just informative. For example the /// class will show up in the error message but otherwise an error class is /// typically not directly actionable. pub fn class(&self) -> ErrorClass { match self.raw_class() { raw::GIT_ERROR_NONE => super::ErrorClass::None, raw::GIT_ERROR_NOMEMORY => super::ErrorClass::NoMemory, raw::GIT_ERROR_OS => super::ErrorClass::Os, raw::GIT_ERROR_INVALID => super::ErrorClass::Invalid, raw::GIT_ERROR_REFERENCE => super::ErrorClass::Reference, raw::GIT_ERROR_ZLIB => super::ErrorClass::Zlib, raw::GIT_ERROR_REPOSITORY => super::ErrorClass::Repository, raw::GIT_ERROR_CONFIG => super::ErrorClass::Config, raw::GIT_ERROR_REGEX => super::ErrorClass::Regex, raw::GIT_ERROR_ODB => super::ErrorClass::Odb, raw::GIT_ERROR_INDEX => super::ErrorClass::Index, raw::GIT_ERROR_OBJECT => super::ErrorClass::Object, raw::GIT_ERROR_NET => super::ErrorClass::Net, raw::GIT_ERROR_TAG => super::ErrorClass::Tag, raw::GIT_ERROR_TREE => super::ErrorClass::Tree, raw::GIT_ERROR_INDEXER => super::ErrorClass::Indexer, raw::GIT_ERROR_SSL => super::ErrorClass::Ssl, raw::GIT_ERROR_SUBMODULE => super::ErrorClass::Submodule, raw::GIT_ERROR_THREAD => super::ErrorClass::Thread, raw::GIT_ERROR_STASH => super::ErrorClass::Stash, raw::GIT_ERROR_CHECKOUT => super::ErrorClass::Checkout, raw::GIT_ERROR_FETCHHEAD => super::ErrorClass::FetchHead, raw::GIT_ERROR_MERGE => super::ErrorClass::Merge, raw::GIT_ERROR_SSH => super::ErrorClass::Ssh, raw::GIT_ERROR_FILTER => super::ErrorClass::Filter, raw::GIT_ERROR_REVERT => super::ErrorClass::Revert, raw::GIT_ERROR_CALLBACK => super::ErrorClass::Callback, raw::GIT_ERROR_CHERRYPICK => super::ErrorClass::CherryPick, raw::GIT_ERROR_DESCRIBE => super::ErrorClass::Describe, raw::GIT_ERROR_REBASE => super::ErrorClass::Rebase, raw::GIT_ERROR_FILESYSTEM => super::ErrorClass::Filesystem, raw::GIT_ERROR_PATCH => super::ErrorClass::Patch, raw::GIT_ERROR_WORKTREE => super::ErrorClass::Worktree, raw::GIT_ERROR_SHA1 => super::ErrorClass::Sha1, raw::GIT_ERROR_HTTP => super::ErrorClass::Http, _ => super::ErrorClass::None, } } /// Modify the error class associated with this error. /// /// This is mainly intended to be used by implementers of custom transports /// or database backends, and should be used with care. pub fn set_class(&mut self, class: ErrorClass) { self.klass = match class { ErrorClass::None => raw::GIT_ERROR_NONE, ErrorClass::NoMemory => raw::GIT_ERROR_NOMEMORY, ErrorClass::Os => raw::GIT_ERROR_OS, ErrorClass::Invalid => raw::GIT_ERROR_INVALID, ErrorClass::Reference => raw::GIT_ERROR_REFERENCE, ErrorClass::Zlib => raw::GIT_ERROR_ZLIB, ErrorClass::Repository => raw::GIT_ERROR_REPOSITORY, ErrorClass::Config => raw::GIT_ERROR_CONFIG, ErrorClass::Regex => raw::GIT_ERROR_REGEX, ErrorClass::Odb => raw::GIT_ERROR_ODB, ErrorClass::Index => raw::GIT_ERROR_INDEX, ErrorClass::Object => raw::GIT_ERROR_OBJECT, ErrorClass::Net => raw::GIT_ERROR_NET, ErrorClass::Tag => raw::GIT_ERROR_TAG, ErrorClass::Tree => raw::GIT_ERROR_TREE, ErrorClass::Indexer => raw::GIT_ERROR_INDEXER, ErrorClass::Ssl => raw::GIT_ERROR_SSL, ErrorClass::Submodule => raw::GIT_ERROR_SUBMODULE, ErrorClass::Thread => raw::GIT_ERROR_THREAD, ErrorClass::Stash => raw::GIT_ERROR_STASH, ErrorClass::Checkout => raw::GIT_ERROR_CHECKOUT, ErrorClass::FetchHead => raw::GIT_ERROR_FETCHHEAD, ErrorClass::Merge => raw::GIT_ERROR_MERGE, ErrorClass::Ssh => raw::GIT_ERROR_SSH, ErrorClass::Filter => raw::GIT_ERROR_FILTER, ErrorClass::Revert => raw::GIT_ERROR_REVERT, ErrorClass::Callback => raw::GIT_ERROR_CALLBACK, ErrorClass::CherryPick => raw::GIT_ERROR_CHERRYPICK, ErrorClass::Describe => raw::GIT_ERROR_DESCRIBE, ErrorClass::Rebase => raw::GIT_ERROR_REBASE, ErrorClass::Filesystem => raw::GIT_ERROR_FILESYSTEM, ErrorClass::Patch => raw::GIT_ERROR_PATCH, ErrorClass::Worktree => raw::GIT_ERROR_WORKTREE, ErrorClass::Sha1 => raw::GIT_ERROR_SHA1, ErrorClass::Http => raw::GIT_ERROR_HTTP, } as c_int; } /// Return the raw error code associated with this error. pub fn raw_code(&self) -> raw::git_error_code { macro_rules! check( ($($e:ident,)*) => ( $(if self.code == raw::$e as c_int { raw::$e }) else * else { raw::GIT_ERROR } ) ); check!( GIT_OK, GIT_ERROR, GIT_ENOTFOUND, GIT_EEXISTS, GIT_EAMBIGUOUS, GIT_EBUFS, GIT_EUSER, GIT_EBAREREPO, GIT_EUNBORNBRANCH, GIT_EUNMERGED, GIT_ENONFASTFORWARD, GIT_EINVALIDSPEC, GIT_ECONFLICT, GIT_ELOCKED, GIT_EMODIFIED, GIT_EAUTH, GIT_ECERTIFICATE, GIT_EAPPLIED, GIT_EPEEL, GIT_EEOF, GIT_EINVALID, GIT_EUNCOMMITTED, GIT_PASSTHROUGH, GIT_ITEROVER, GIT_RETRY, GIT_EMISMATCH, GIT_EINDEXDIRTY, GIT_EAPPLYFAIL, GIT_EOWNER, ) } /// Return the raw error class associated with this error. pub fn raw_class(&self) -> raw::git_error_t { macro_rules! check( ($($e:ident,)*) => ( $(if self.klass == raw::$e as c_int { raw::$e }) else * else { raw::GIT_ERROR_NONE } ) ); check!( GIT_ERROR_NONE, GIT_ERROR_NOMEMORY, GIT_ERROR_OS, GIT_ERROR_INVALID, GIT_ERROR_REFERENCE, GIT_ERROR_ZLIB, GIT_ERROR_REPOSITORY, GIT_ERROR_CONFIG, GIT_ERROR_REGEX, GIT_ERROR_ODB, GIT_ERROR_INDEX, GIT_ERROR_OBJECT, GIT_ERROR_NET, GIT_ERROR_TAG, GIT_ERROR_TREE, GIT_ERROR_INDEXER, GIT_ERROR_SSL, GIT_ERROR_SUBMODULE, GIT_ERROR_THREAD, GIT_ERROR_STASH, GIT_ERROR_CHECKOUT, GIT_ERROR_FETCHHEAD, GIT_ERROR_MERGE, GIT_ERROR_SSH, GIT_ERROR_FILTER, GIT_ERROR_REVERT, GIT_ERROR_CALLBACK, GIT_ERROR_CHERRYPICK, GIT_ERROR_DESCRIBE, GIT_ERROR_REBASE, GIT_ERROR_FILESYSTEM, GIT_ERROR_PATCH, GIT_ERROR_WORKTREE, GIT_ERROR_SHA1, GIT_ERROR_HTTP, ) } /// Return the message associated with this error pub fn message(&self) -> &str { &self.message } } impl error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.message)?; match self.class() { ErrorClass::None => {} other => write!(f, "; class={:?} ({})", other, self.klass)?, } match self.code() { ErrorCode::GenericError => {} other => write!(f, "; code={:?} ({})", other, self.code)?, } Ok(()) } } impl From for Error { fn from(_: NulError) -> Error { Error::from_str( "data contained a nul byte that could not be \ represented as a string", ) } } impl From for Error { fn from(e: JoinPathsError) -> Error { Error::from_str(&e.to_string()) } } #[cfg(test)] mod tests { use crate::{ErrorClass, ErrorCode}; #[test] fn smoke() { let (_td, repo) = crate::test::repo_init(); let err = repo.find_submodule("does_not_exist").err().unwrap(); assert_eq!(err.code(), ErrorCode::NotFound); assert_eq!(err.class(), ErrorClass::Submodule); } }