use std::path::PathBuf;
use std::process::Command;
use super::SourceError;
use super::cache::{default_cache_dir, git_cache_path};
pub fn resolve_git(
url: &str,
rev: Option<&str>,
depth: Option<u32>,
cache_dir: Option<PathBuf>,
force: bool,
) -> Result<PathBuf, SourceError> {
let cache_base = match cache_dir {
Some(dir) => dir,
None => default_cache_dir()?,
};
let local_path = git_cache_path(url, rev, &cache_base)?;
if !force && local_path.exists() {
return Ok(local_path);
}
if local_path.exists() {
std::fs::remove_dir_all(&local_path)
.map_err(|e| SourceError::Io(format!("Failed to remove stale cache: {e}")))?;
}
Command::new("git")
.arg("--version")
.output()
.map_err(|_| SourceError::GitNotFound)?;
let depth = depth.unwrap_or(1);
let is_commit_hash = rev
.is_some_and(|r| r.len() >= 7 && r.len() <= 40 && r.chars().all(|c| c.is_ascii_hexdigit()));
if is_commit_hash {
let output = Command::new("git")
.args(["clone", url])
.arg(&local_path)
.output()
.map_err(|e| SourceError::Io(format!("Failed to run git clone: {e}")))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(SourceError::Git(format!("git clone failed: {stderr}")));
}
let rev = rev.unwrap();
let output = Command::new("git")
.args(["-C"])
.arg(&local_path)
.args(["checkout", rev])
.output()
.map_err(|e| SourceError::Io(format!("Failed to run git checkout: {e}")))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(SourceError::Git(format!(
"git checkout {rev} failed: {stderr}"
)));
}
} else {
let mut cmd = Command::new("git");
cmd.args(["clone", "--depth", &depth.to_string()]);
if let Some(rev) = rev {
cmd.args(["--branch", rev]);
}
cmd.arg(url).arg(&local_path);
let output = cmd
.output()
.map_err(|e| SourceError::Io(format!("Failed to run git clone: {e}")))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(SourceError::Git(format!("git clone failed: {stderr}")));
}
}
Ok(local_path)
}
#[cfg(test)]
mod tests {
#[test]
fn test_is_commit_hash_detection() {
let rev = Some("abc1234");
assert!(rev.is_some_and(|r| r.len() >= 7
&& r.len() <= 40
&& r.chars().all(|c| c.is_ascii_hexdigit())));
let rev = Some("abc1234567890abc1234567890abc1234567890a");
assert!(rev.is_some_and(|r| r.len() >= 7
&& r.len() <= 40
&& r.chars().all(|c| c.is_ascii_hexdigit())));
let rev = Some("main");
assert!(!rev.is_some_and(|r| r.len() >= 7
&& r.len() <= 40
&& r.chars().all(|c| c.is_ascii_hexdigit())));
let rev = Some("abc123");
assert!(!rev.is_some_and(|r| r.len() >= 7
&& r.len() <= 40
&& r.chars().all(|c| c.is_ascii_hexdigit())));
}
}