use std::path::Path; use crate::service::gitcmd::run_git; use crate::share::errors::GitError; #[derive(Debug, Clone)] pub struct GitCredential { pub username: String, pub secret: String, } impl GitCredential { #[must_use] pub fn new(username: impl Into, secret: impl Into) -> Self { Self { username: username.into(), secret: secret.into(), } } } const ASKPASS_SCRIPT: &str = r#"#!/bin/sh case "$1" in *sername*) printf '%s\n' "$GITDATA_GIT_USER" ;; *) printf '%s\n' "$GITDATA_GIT_PASS" ;; esac "#; const MIRROR_REFSPECS: [&str; 2] = ["+refs/heads/*:refs/heads/*", "+refs/tags/*:refs/tags/*"]; fn mirror_args(url: &str) -> Vec { let mut args = vec![ "fetch".to_string(), "--prune".to_string(), "--force".to_string(), "--".to_string(), url.to_string(), ]; args.extend(MIRROR_REFSPECS.iter().map(|spec| (*spec).to_string())); args } pub async fn mirror_fetch( label: &str, dir: &Path, url: &str, credential: Option<&GitCredential>, timeout_secs: u64, ) -> Result<(), GitError> { let Some(cred) = credential else { run_git(label, dir, mirror_args(url), &[], timeout_secs).await?; return Ok(()); }; let script = std::env::temp_dir().join(format!("gitdata-askpass-{}.sh", uuid::Uuid::now_v7())); tokio::fs::write(&script, ASKPASS_SCRIPT) .await .map_err(|e| GitError::Internal(format!("write askpass script: {e}")))?; if let Err(e) = set_executable(&script).await { let _ = tokio::fs::remove_file(&script).await; return Err(e); } let env = vec![ ( "GIT_ASKPASS".to_string(), script.to_string_lossy().to_string(), ), ("GITDATA_GIT_USER".to_string(), cred.username.clone()), ("GITDATA_GIT_PASS".to_string(), cred.secret.clone()), ("GIT_TERMINAL_PROMPT".to_string(), "0".to_string()), ]; let result = run_git(label, dir, mirror_args(url), &env, timeout_secs).await; if let Err(e) = tokio::fs::remove_file(&script).await { tracing::warn!(error = %e, "failed to remove askpass script"); } result?; Ok(()) } #[cfg(unix)] async fn set_executable(path: &Path) -> Result<(), GitError> { use std::os::unix::fs::PermissionsExt; let perms = std::fs::Permissions::from_mode(0o700); tokio::fs::set_permissions(path, perms) .await .map_err(|e| GitError::Internal(format!("chmod askpass script: {e}")))?; Ok(()) } #[cfg(not(unix))] async fn set_executable(_path: &Path) -> Result<(), GitError> { Ok(()) }