use crate::error::GitError;
pub(crate) fn map_head_error(err: git2::Error) -> GitError {
if err.code() == git2::ErrorCode::UnbornBranch || err.code() == git2::ErrorCode::NotFound {
GitError::RefNotFound {
refname: "HEAD".to_string(),
}
} else {
GitError::Internal(err)
}
}
pub(crate) fn map_remote_error(err: git2::Error) -> GitError {
if err.class() == git2::ErrorClass::Net {
GitError::Network(redact_url_credentials(err.message()))
} else if is_auth_error(&err) {
GitError::RemoteAuth {
message: redact_url_credentials(err.message()),
}
} else {
GitError::Internal(err)
}
}
pub(crate) fn map_push_error(err: git2::Error, refspecs: &[String]) -> GitError {
if err.class() == git2::ErrorClass::Net {
GitError::Network(redact_url_credentials(err.message()))
} else if is_auth_error(&err) {
GitError::RemoteAuth {
message: redact_url_credentials(err.message()),
}
} else if is_ref_rejection(&err) {
GitError::PushRejected {
refname: destination_refs(refspecs),
reason: redact_url_credentials(err.message()),
}
} else {
GitError::Internal(err)
}
}
fn is_auth_error(err: &git2::Error) -> bool {
err.code() == git2::ErrorCode::Auth
|| matches!(
err.class(),
git2::ErrorClass::Http | git2::ErrorClass::Ssh | git2::ErrorClass::Callback
)
}
fn is_ref_rejection(err: &git2::Error) -> bool {
err.code() == git2::ErrorCode::NotFastForward || err.class() == git2::ErrorClass::Reference
}
fn destination_refs(refspecs: &[String]) -> String {
if refspecs.is_empty() {
return "the remote".to_string();
}
refspecs
.iter()
.map(|spec| {
let src_dst = spec.strip_prefix('+').unwrap_or(spec);
src_dst.split_once(':').map_or(src_dst, |(_, dst)| dst)
})
.collect::<Vec<_>>()
.join(", ")
}
pub(crate) fn redact_url_credentials(message: &str) -> String {
let mut out = String::with_capacity(message.len());
let mut rest = message;
while let Some(scheme_idx) = rest.find("://") {
let after = scheme_idx + "://".len();
out.push_str(&rest[..after]);
let tail = &rest[after..];
let authority_end = tail
.find(|c: char| c == '/' || c == '?' || c == '#' || c.is_whitespace())
.unwrap_or(tail.len());
let authority = &tail[..authority_end];
if let Some(at) = authority.rfind('@') {
out.push_str("***@");
out.push_str(&authority[at + 1..]);
} else {
out.push_str(authority);
}
rest = &tail[authority_end..];
}
out.push_str(rest);
out
}
pub(crate) fn map_signature_error(err: git2::Error) -> GitError {
if err.class() == git2::ErrorClass::Config && err.code() == git2::ErrorCode::NotFound {
GitError::IdentityMissing {
key: identity_key_from_message(err.message()),
}
} else {
GitError::Internal(err)
}
}
fn identity_key_from_message(message: &str) -> String {
message
.split('\'')
.nth(1)
.filter(|key| key.starts_with("user."))
.unwrap_or("user.name / user.email")
.to_string()
}