1use crate::tasks::git::branch::shorten_branch_ref;
3use crate::tasks::git::errors::GitError as E;
4use color_eyre::eyre::Result;
5use git2::Cred;
6use git2::CredentialType;
7use git2::ErrorClass;
8use git2::ErrorCode;
9use git2::Remote;
10use git2::RemoteCallbacks;
11use git2::Repository;
12use std::thread;
13use std::time::Duration;
14use tracing::debug;
15use tracing::warn;
16
17const AUTH_RETRY_COUNT: usize = 10;
19const RETRY_SLEEP_INTERVAL_S: u64 = 2;
21
22pub(super) fn remote_callbacks(count: &mut usize) -> RemoteCallbacks {
26 let mut remote_callbacks = RemoteCallbacks::new();
27 remote_callbacks.credentials(move |url, username_from_url, allowed_types| {
28 *count += 1;
29 if *count > 2 {
30 thread::sleep(Duration::from_secs(RETRY_SLEEP_INTERVAL_S));
31 }
32 if *count > AUTH_RETRY_COUNT {
33 let extra = if allowed_types.contains(CredentialType::SSH_KEY) {
34 let ssh_add_keychain = if cfg!(target_os = "macos") { "-K " } else { "" };
37 format!(
38 "\nIf 'git clone {url}' works, you probably need to add your ssh keys to the \
39 ssh-agent. Try running 'ssh-add {ssh_add_keychain}-A' or 'ssh-add \
40 {ssh_add_keychain}~/.ssh/*id_{{rsa,ed25519}}'."
41 )
42 } else {
43 String::new()
44 };
45 let message = format!(
46 "Authentication failure while trying to fetch git repository.{extra}\nurl: {url}, \
47 username_from_url: {username_from_url:?}, allowed_types: {allowed_types:?}"
48 );
49 return Err(git2::Error::new(ErrorCode::Auth, ErrorClass::Ssh, message));
50 }
51 debug!("SSH_AUTH_SOCK: {:?}", std::env::var("SSH_AUTH_SOCK"));
52 debug!(
53 "Fetching credentials, url: {url}, username_from_url: {username_from_url:?}, count: \
54 {count}, allowed_types: {allowed_types:?}"
55 );
56 let username = username_from_url.unwrap_or("git");
57 if allowed_types.contains(CredentialType::USERNAME) {
58 Cred::username(username)
59 } else if allowed_types.contains(CredentialType::SSH_KEY) {
60 Cred::ssh_key_from_agent(username)
61 } else if allowed_types.contains(CredentialType::USER_PASS_PLAINTEXT) {
62 let git_config = git2::Config::open_default()?;
63 git2::Cred::credential_helper(&git_config, url, None)
64 } else {
65 Cred::default()
66 }
67 });
68 remote_callbacks
69}
70
71pub(super) fn set_remote_head(
75 repo: &Repository,
76 remote: &Remote,
77 default_branch: &str,
78) -> Result<bool> {
79 let mut did_work = false;
80 let remote_name = remote.name().ok_or(E::RemoteNameMissing)?;
81 let remote_ref = format!("refs/remotes/{remote_name}/HEAD");
82 let short_branch = shorten_branch_ref(default_branch);
83 let remote_head = format!("refs/remotes/{remote_name}/{short_branch}",);
84 debug!("Setting remote head for remote {remote_name}: {remote_ref} => {remote_head}",);
85 match repo.find_reference(&remote_ref) {
86 Ok(reference) => {
87 if matches!(reference.symbolic_target(), Some(target) if target == remote_head) {
88 debug!("Ref {remote_ref} already points to {remote_head}.",);
89 } else {
90 warn!(
91 "Overwriting existing {remote_ref} to point to {remote_head} instead of
92 {symbolic_target:?}",
93 symbolic_target = reference.symbolic_target(),
94 );
95 repo.reference_symbolic(
96 &remote_ref,
97 &remote_head,
98 true,
99 "up overwrite remote head",
100 )?;
101 did_work = true;
102 }
103 }
104 Err(e) if e.code() == ErrorCode::NotFound => {
105 repo.reference_symbolic(&remote_ref, &remote_head, false, "up set remote head")?;
106 did_work = true;
107 }
108 Err(e) => return Err(e.into()),
109 }
110 Ok(did_work)
111}