use anyhow::{Context, Result};
use base64::Engine;
use std::path::Path;
use tokio::process::Command;
use url::Url;
use super::{build_clone_args, CloneOpts};
use crate::git::types::CredentialKind;
pub async fn clone_https(opts: &CloneOpts, dest: &Path) -> Result<()> {
let CredentialKind::Https { user, password } = &opts
.credentials
.as_ref()
.context("missing credentials")?
.kind
else {
anyhow::bail!("expected HTTPS credentials");
};
let repo_url = embed_credentials_in_url(&opts.repo_url, user, password)?;
let extra_config = vec!["-c".to_string(), "http.sslVerify=false".to_string()];
let args = build_clone_args(opts, &repo_url, dest, &extra_config);
run_git_clone(&args).await
}
pub async fn clone_https_token(opts: &CloneOpts, dest: &Path) -> Result<()> {
let CredentialKind::Token {
token,
is_pat,
oauth_type: _,
} = &opts
.credentials
.as_ref()
.context("missing credentials")?
.kind
else {
anyhow::bail!("expected Token credentials");
};
if *is_pat {
let encoded = base64::engine::general_purpose::STANDARD.encode(format!(":{}", token));
let extra_config = vec![
"-c".to_string(),
"http.sslVerify=false".to_string(),
"-c".to_string(),
format!("http.extraHeader=Authorization: Basic {encoded}"),
];
let args = build_clone_args(opts, &opts.repo_url, dest, &extra_config);
return run_git_clone(&args).await;
}
let user = if opts.repo_url.to_lowercase().contains("bitbucket") {
"x-token-auth"
} else {
"oauth2"
};
let repo_url = embed_credentials_in_url(&opts.repo_url, user, token)?;
let extra_config = vec!["-c".to_string(), "http.sslVerify=false".to_string()];
let args = build_clone_args(opts, &repo_url, dest, &extra_config);
run_git_clone(&args).await
}
pub async fn clone_https_public(opts: &CloneOpts, dest: &Path) -> Result<()> {
let extra_config = vec!["-c".to_string(), "http.sslVerify=false".to_string()];
let args = build_clone_args(opts, &opts.repo_url, dest, &extra_config);
run_git_clone(&args).await
}
pub fn embed_credentials_in_url(raw_url: &str, user: &str, password: &str) -> Result<String> {
let mut url = Url::parse(raw_url).context("parsing URL")?;
url.set_username(user)
.map_err(|_| anyhow::anyhow!("failed to set username"))?;
url.set_password(Some(password))
.map_err(|_| anyhow::anyhow!("failed to set password"))?;
Ok(url.to_string())
}
pub fn build_https_config_args(
follow_redirects: bool,
is_pat: bool,
token: Option<&str>,
disable_credential_prompt: bool,
) -> Vec<String> {
let mut args = vec![
"-c".to_string(),
"http.sslVerify=false".to_string(),
"-c".to_string(),
format!("http.followRedirects={follow_redirects}"),
];
if is_pat {
if let Some(t) = token {
let encoded = base64::engine::general_purpose::STANDARD.encode(format!(":{t}"));
args.extend([
"-c".to_string(),
format!("http.extraHeader=Authorization: Basic {encoded}"),
]);
}
}
if disable_credential_prompt {
args.extend(["-c".to_string(), "credential.helper=".to_string()]);
}
args
}
#[allow(clippy::too_many_arguments)]
pub fn format_https_url(
repo_url: &str,
user: Option<&str>,
password: Option<&str>,
token: Option<&str>,
provider: Option<&str>,
is_pat: bool,
) -> Result<String> {
let mut parsed = Url::parse(repo_url).context("parsing URL")?;
let path = parsed.path().to_string();
parsed.set_path(&path);
if is_pat {
return Ok(parsed.to_string());
}
if let Some(t) = token {
let user_part = match provider {
Some(p) if p.eq_ignore_ascii_case("BITBUCKET") => "x-token-auth",
Some(_) => "oauth2",
None => "",
};
if user_part.is_empty() {
parsed
.set_username(t)
.map_err(|_| anyhow::anyhow!("set username"))?;
parsed
.set_password(None)
.map_err(|_| anyhow::anyhow!("set password"))?;
} else {
parsed
.set_username(user_part)
.map_err(|_| anyhow::anyhow!("set username"))?;
parsed
.set_password(Some(t))
.map_err(|_| anyhow::anyhow!("set password"))?;
}
return Ok(parsed.to_string());
}
if let (Some(u), Some(p)) = (user, password) {
parsed
.set_username(u)
.map_err(|_| anyhow::anyhow!("set username"))?;
parsed
.set_password(Some(p))
.map_err(|_| anyhow::anyhow!("set password"))?;
return Ok(parsed.to_string());
}
Ok(parsed.to_string())
}
async fn run_git_clone(args: &[String]) -> Result<()> {
let status = Command::new("git")
.args(args)
.status()
.await
.context("running git clone")?;
if !status.success() {
anyhow::bail!("git clone failed with status {status}");
}
Ok(())
}