solid_pod_rs_git/
config.rs1use std::path::Path;
15use std::process::Stdio;
16
17use tokio::process::Command;
18
19use crate::error::GitError;
20
21#[derive(Debug, Clone)]
23pub struct GitDir {
24 pub git_dir: std::path::PathBuf,
27
28 pub is_regular: bool,
30}
31
32pub fn find_git_dir(repo_path: &Path) -> std::io::Result<Option<GitDir>> {
38 if !repo_path.exists() || !repo_path.is_dir() {
39 return Ok(None);
40 }
41
42 let dot_git = repo_path.join(".git");
43 if dot_git.exists() && dot_git.is_dir() {
44 return Ok(Some(GitDir {
45 git_dir: dot_git,
46 is_regular: true,
47 }));
48 }
49
50 let objects = repo_path.join("objects");
52 let refs = repo_path.join("refs");
53 if objects.exists() && refs.exists() {
54 return Ok(Some(GitDir {
55 git_dir: repo_path.to_path_buf(),
56 is_regular: false,
57 }));
58 }
59
60 Ok(None)
61}
62
63pub async fn apply_write_config(git_dir: &GitDir, cwd: &Path) -> Result<(), GitError> {
72 let _ = run_git_config(cwd, &git_dir.git_dir, "http.receivepack", "true").await;
74
75 if git_dir.is_regular {
77 let _ = run_git_config(
78 cwd,
79 &git_dir.git_dir,
80 "receive.denyCurrentBranch",
81 "updateInstead",
82 )
83 .await;
84 }
85
86 Ok(())
87}
88
89pub async fn run_git_config(
96 cwd: &Path,
97 git_dir: &Path,
98 key: &str,
99 value: &str,
100) -> Result<(), GitError> {
101 let mut cmd = Command::new("git");
102 cmd.arg("config")
103 .arg("--local")
104 .arg(key)
105 .arg(value)
106 .current_dir(cwd)
107 .env("GIT_DIR", git_dir)
108 .stdout(Stdio::null())
109 .stderr(Stdio::piped());
110
111 let output = match cmd.output().await {
112 Ok(o) => o,
113 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
114 return Err(GitError::BackendNotAvailable(format!(
115 "git binary not found: {e}"
116 )));
117 }
118 Err(e) => return Err(GitError::Io(e)),
119 };
120
121 if !output.status.success() {
122 let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
123 tracing::debug!(
124 target: "solid_pod_rs_git::config",
125 "git config {key}={value} failed: {stderr}"
126 );
127 return Err(GitError::BackendFailed {
128 exit_code: output.status.code(),
129 stderr,
130 });
131 }
132 Ok(())
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use tempfile::TempDir;
139
140 #[tokio::test]
141 async fn find_git_dir_empty_returns_none() {
142 let td = TempDir::new().unwrap();
143 let res = find_git_dir(td.path()).unwrap();
144 assert!(res.is_none());
145 }
146
147 #[tokio::test]
148 async fn find_git_dir_regular_detected() {
149 let td = TempDir::new().unwrap();
150 std::fs::create_dir(td.path().join(".git")).unwrap();
151 let res = find_git_dir(td.path()).unwrap().unwrap();
152 assert!(res.is_regular);
153 assert_eq!(res.git_dir, td.path().join(".git"));
154 }
155
156 #[tokio::test]
157 async fn find_git_dir_bare_detected() {
158 let td = TempDir::new().unwrap();
159 std::fs::create_dir(td.path().join("objects")).unwrap();
160 std::fs::create_dir(td.path().join("refs")).unwrap();
161 let res = find_git_dir(td.path()).unwrap().unwrap();
162 assert!(!res.is_regular);
163 assert_eq!(res.git_dir, td.path());
164 }
165
166 #[tokio::test]
168 async fn apply_write_config_roundtrip() {
169 let td = TempDir::new().unwrap();
170 let repo = td.path();
171 let status = Command::new("git")
173 .arg("init")
174 .arg(repo)
175 .stdout(Stdio::null())
176 .stderr(Stdio::null())
177 .status()
178 .await;
179 let status = match status {
180 Ok(s) => s,
181 Err(_) => return, };
183 assert!(status.success());
184
185 let gd = find_git_dir(repo).unwrap().unwrap();
186 apply_write_config(&gd, repo).await.unwrap();
187
188 let out = Command::new("git")
190 .arg("config")
191 .arg("--local")
192 .arg("receive.denyCurrentBranch")
193 .current_dir(repo)
194 .env("GIT_DIR", &gd.git_dir)
195 .output()
196 .await
197 .unwrap();
198 assert!(out.status.success());
199 assert_eq!(
200 String::from_utf8_lossy(&out.stdout).trim(),
201 "updateInstead"
202 );
203
204 let out2 = Command::new("git")
205 .arg("config")
206 .arg("--local")
207 .arg("http.receivepack")
208 .current_dir(repo)
209 .env("GIT_DIR", &gd.git_dir)
210 .output()
211 .await
212 .unwrap();
213 assert_eq!(String::from_utf8_lossy(&out2.stdout).trim(), "true");
214 }
215}