diff options
Diffstat (limited to 'vendor/git2/src/error.rs')
-rw-r--r-- | vendor/git2/src/error.rs | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/vendor/git2/src/error.rs b/vendor/git2/src/error.rs new file mode 100644 index 000000000..6f1c4d4c7 --- /dev/null +++ b/vendor/git2/src/error.rs @@ -0,0 +1,399 @@ +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<S: AsRef<str>>(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<Error> { + 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<NulError> for Error { + fn from(_: NulError) -> Error { + Error::from_str( + "data contained a nul byte that could not be \ + represented as a string", + ) + } +} + +impl From<JoinPathsError> 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); + } +} |