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