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