use std::{thread, time::Duration};
use color_eyre::eyre::Result;
use git2::{Cred, CredentialType, ErrorClass, ErrorCode, Remote, RemoteCallbacks, Repository};
use log::{debug, warn};
use crate::tasks::git::{branch::shorten_branch_ref, errors::GitError as E};
const AUTH_RETRY_COUNT: usize = 10;
const RETRY_SLEEP_INTERVAL_S: u64 = 2;
pub(super) fn remote_callbacks(count: &mut usize) -> RemoteCallbacks {
let mut remote_callbacks = RemoteCallbacks::new();
remote_callbacks.credentials(move |url, username_from_url, allowed_types| {
*count += 1;
if *count > 2 {
thread::sleep(Duration::from_secs(RETRY_SLEEP_INTERVAL_S));
}
if *count > AUTH_RETRY_COUNT {
let extra = if allowed_types.contains(CredentialType::SSH_KEY) {
let ssh_add_keychain = if cfg!(target_os = "macos") { "-K " } else { "" };
format!(
"\nIf 'git clone {url}' works, you probably need to add your ssh keys to the ssh-agent. \
Try running 'ssh-add {ssh_add_keychain}-A' or 'ssh-add {ssh_add_keychain}~/.ssh/*id_{{rsa,ed25519}}'."
)
} else {
String::new()
};
let message = format!("Authentication failure while trying to fetch git repository.{extra}\n\
url: {url}, username_from_url: {username_from_url:?}, allowed_types: {allowed_types:?}"
);
return Err(git2::Error::new(ErrorCode::Auth, ErrorClass::Ssh, message));
}
debug!("SSH_AUTH_SOCK: {:?}", std::env::var("SSH_AUTH_SOCK"));
debug!(
"Fetching credentials, url: {url}, username_from_url: {username_from_url:?}, count: {count}, allowed_types: {allowed_types:?}"
);
let username = username_from_url.unwrap_or("git");
if allowed_types.contains(CredentialType::USERNAME) {
Cred::username(username)
} else if allowed_types.contains(CredentialType::SSH_KEY) {
Cred::ssh_key_from_agent(username)
} else if allowed_types.contains(CredentialType::USER_PASS_PLAINTEXT) {
let git_config = git2::Config::open_default()?;
git2::Cred::credential_helper(&git_config, url, None)
} else {
Cred::default()
}
});
remote_callbacks
}
pub(super) fn set_remote_head(
repo: &Repository,
remote: &Remote,
default_branch: &str,
) -> Result<bool> {
let mut did_work = false;
let remote_name = remote.name().ok_or(E::RemoteNameMissing)?;
let remote_ref = format!("refs/remotes/{remote_name}/HEAD");
let short_branch = shorten_branch_ref(default_branch);
let remote_head = format!("refs/remotes/{remote_name}/{short_branch}",);
debug!("Setting remote head for remote {remote_name}: {remote_ref} => {remote_head}",);
match repo.find_reference(&remote_ref) {
Ok(reference) => {
if matches!(reference.symbolic_target(), Some(target) if target == remote_head) {
debug!("Ref {remote_ref} already points to {remote_head}.",);
} else {
warn!(
"Overwriting existing {remote_ref} to point to {remote_head} instead of
{symbolic_target:?}",
symbolic_target = reference.symbolic_target(),
);
repo.reference_symbolic(
&remote_ref,
&remote_head,
true,
"up-rs overwrite remote head",
)?;
did_work = true;
}
}
Err(e) if e.code() == ErrorCode::NotFound => {
repo.reference_symbolic(&remote_ref, &remote_head, false, "up-rs set remote head")?;
did_work = true;
}
Err(e) => return Err(e.into()),
}
Ok(did_work)
}