use std::path::Path;
use std::process::Stdio;
use tokio::process::Command;
use crate::error::GitError;
#[derive(Debug, Clone)]
pub struct GitDir {
pub git_dir: std::path::PathBuf,
pub is_regular: bool,
}
pub fn find_git_dir(repo_path: &Path) -> std::io::Result<Option<GitDir>> {
if !repo_path.exists() || !repo_path.is_dir() {
return Ok(None);
}
let dot_git = repo_path.join(".git");
if dot_git.exists() && dot_git.is_dir() {
return Ok(Some(GitDir {
git_dir: dot_git,
is_regular: true,
}));
}
let objects = repo_path.join("objects");
let refs = repo_path.join("refs");
if objects.exists() && refs.exists() {
return Ok(Some(GitDir {
git_dir: repo_path.to_path_buf(),
is_regular: false,
}));
}
Ok(None)
}
pub async fn apply_write_config(git_dir: &GitDir, cwd: &Path) -> Result<(), GitError> {
let _ = run_git_config(cwd, &git_dir.git_dir, "http.receivepack", "true").await;
if git_dir.is_regular {
let _ = run_git_config(
cwd,
&git_dir.git_dir,
"receive.denyCurrentBranch",
"updateInstead",
)
.await;
}
Ok(())
}
pub async fn run_git_config(
cwd: &Path,
git_dir: &Path,
key: &str,
value: &str,
) -> Result<(), GitError> {
let mut cmd = Command::new("git");
cmd.arg("config")
.arg("--local")
.arg(key)
.arg(value)
.current_dir(cwd)
.env("GIT_DIR", git_dir)
.stdout(Stdio::null())
.stderr(Stdio::piped());
let output = match cmd.output().await {
Ok(o) => o,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
return Err(GitError::BackendNotAvailable(format!(
"git binary not found: {e}"
)));
}
Err(e) => return Err(GitError::Io(e)),
};
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
tracing::debug!(
target: "solid_pod_rs_git::config",
"git config {key}={value} failed: {stderr}"
);
return Err(GitError::BackendFailed {
exit_code: output.status.code(),
stderr,
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[tokio::test]
async fn find_git_dir_empty_returns_none() {
let td = TempDir::new().unwrap();
let res = find_git_dir(td.path()).unwrap();
assert!(res.is_none());
}
#[tokio::test]
async fn find_git_dir_regular_detected() {
let td = TempDir::new().unwrap();
std::fs::create_dir(td.path().join(".git")).unwrap();
let res = find_git_dir(td.path()).unwrap().unwrap();
assert!(res.is_regular);
assert_eq!(res.git_dir, td.path().join(".git"));
}
#[tokio::test]
async fn find_git_dir_bare_detected() {
let td = TempDir::new().unwrap();
std::fs::create_dir(td.path().join("objects")).unwrap();
std::fs::create_dir(td.path().join("refs")).unwrap();
let res = find_git_dir(td.path()).unwrap().unwrap();
assert!(!res.is_regular);
assert_eq!(res.git_dir, td.path());
}
#[tokio::test]
async fn apply_write_config_roundtrip() {
let td = TempDir::new().unwrap();
let repo = td.path();
let status = Command::new("git")
.arg("init")
.arg(repo)
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.await;
let status = match status {
Ok(s) => s,
Err(_) => return, };
assert!(status.success());
let gd = find_git_dir(repo).unwrap().unwrap();
apply_write_config(&gd, repo).await.unwrap();
let out = Command::new("git")
.arg("config")
.arg("--local")
.arg("receive.denyCurrentBranch")
.current_dir(repo)
.env("GIT_DIR", &gd.git_dir)
.output()
.await
.unwrap();
assert!(out.status.success());
assert_eq!(
String::from_utf8_lossy(&out.stdout).trim(),
"updateInstead"
);
let out2 = Command::new("git")
.arg("config")
.arg("--local")
.arg("http.receivepack")
.current_dir(repo)
.env("GIT_DIR", &gd.git_dir)
.output()
.await
.unwrap();
assert_eq!(String::from_utf8_lossy(&out2.stdout).trim(), "true");
}
}