use std::path::PathBuf;
use git2::{Cred, CredentialType, FetchOptions, RemoteCallbacks, Repository};
use tracing::{info, warn};
use crate::collect::errors::Result;
pub fn fetch_remote(repo: &Repository, remote_name: &str) -> Result<()> {
let mut remote = match repo.find_remote(remote_name) {
Ok(r) => r,
Err(e) => {
info!(
remote = remote_name,
error = %e,
"no matching remote; skipping fetch (local-only repo)"
);
return Ok(());
}
};
info!("Fetching from remote '{}'", remote_name);
let mut callbacks = RemoteCallbacks::new();
callbacks.credentials(|url, username_from_url, allowed_types| {
non_interactive_credentials(url, username_from_url, allowed_types)
});
let mut fetch_options = FetchOptions::new();
fetch_options.remote_callbacks(callbacks);
match remote.fetch(&[] as &[&str], Some(&mut fetch_options), None) {
Ok(()) => Ok(()),
Err(e) => {
if is_auth_or_transport_error(&e) {
warn!(
remote = remote_name,
error = %e,
"fetch failed (auth/transport) — continuing with local refs"
);
Ok(())
} else {
warn!(
remote = remote_name,
error = %e,
"fetch failed — continuing with local refs"
);
Ok(())
}
}
}
}
fn non_interactive_credentials(
_url: &str,
username_from_url: Option<&str>,
allowed_types: CredentialType,
) -> std::result::Result<Cred, git2::Error> {
let username = username_from_url.unwrap_or("git");
if allowed_types.contains(CredentialType::SSH_KEY) {
if let Ok(cred) = Cred::ssh_key_from_agent(username) {
return Ok(cred);
}
if let Some(home) = home_dir() {
for key_name in &["id_ed25519", "id_rsa"] {
let private_key = home.join(".ssh").join(key_name);
if private_key.exists() {
if let Ok(cred) = Cred::ssh_key(username, None, private_key.as_path(), None) {
return Ok(cred);
}
}
}
}
}
if allowed_types.contains(CredentialType::DEFAULT) {
if let Ok(cred) = Cred::default() {
return Ok(cred);
}
}
Err(git2::Error::from_str(
"no non-interactive credentials available (tried SSH agent, ~/.ssh/id_ed25519, ~/.ssh/id_rsa)",
))
}
fn home_dir() -> Option<PathBuf> {
std::env::var_os("HOME").map(PathBuf::from)
}
fn is_auth_or_transport_error(e: &git2::Error) -> bool {
matches!(
e.class(),
git2::ErrorClass::Ssh
| git2::ErrorClass::Http
| git2::ErrorClass::Net
| git2::ErrorClass::Callback
)
}