upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/git/handlers.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-02-23 15:20:59 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-02-23 15:20:59 +0000
commit113928aa84894ea8f65c247d9987527e792b32a9 (patch)
treeec967d6195d9f7ec4f061449596611afe3a0950f /src/git/handlers.rs
parent26f608e5011b9d1ad6036da75b89272835e69695 (diff)
parente0ad39a489b3398f8208713bf728db0cb11475b0 (diff)
Merge master into 3ca0-announcements-purgatory
Diffstat (limited to 'src/git/handlers.rs')
-rw-r--r--src/git/handlers.rs108
1 files changed, 99 insertions, 9 deletions
diff --git a/src/git/handlers.rs b/src/git/handlers.rs
index 13d6ba0..f43cbb6 100644
--- a/src/git/handlers.rs
+++ b/src/git/handlers.rs
@@ -100,6 +100,42 @@ pub async fn handle_info_refs(
100 .unwrap()) 100 .unwrap())
101} 101}
102 102
103/// Build an HTTP 200 OK response with an ERR pkt-line for git protocol errors.
104///
105/// Per the git smart HTTP protocol spec, protocol-level errors (like "not our ref")
106/// should be returned as HTTP 200 OK with the error message in pkt-line format:
107/// `PKT-LINE("ERR" SP explanation-text)`
108///
109/// This allows git clients to properly parse and display the error message.
110fn build_git_protocol_error_response(
111 service: GitService,
112 error_message: &str,
113) -> Response<Full<Bytes>> {
114 // Format: "ERR <message>\n"
115 let err_content = format!("ERR {}\n", error_message.trim());
116 let err_pktline = PktLine::data(err_content.as_bytes()).encode();
117
118 Response::builder()
119 .status(StatusCode::OK)
120 .header("content-type", service.result_content_type())
121 .header("cache-control", "no-cache")
122 .body(Full::new(Bytes::from(err_pktline)))
123 .unwrap()
124}
125
126/// Check if a git process failure is a protocol error (vs transport error).
127///
128/// Protocol errors are communicated via stderr when git exits with code 128.
129/// These should be returned to the client as HTTP 200 with ERR pkt-line.
130///
131/// Transport errors (process spawn failures, I/O errors, signals) should
132/// remain as HTTP 500 errors.
133fn is_git_protocol_error(exit_code: Option<i32>, stderr: &[u8]) -> bool {
134 // Git uses exit code 128 for protocol/usage errors
135 // If there's stderr content, it's a protocol error message
136 exit_code == Some(128) && !stderr.is_empty()
137}
138
103/// Handle POST /git-upload-pack (clone/fetch) 139/// Handle POST /git-upload-pack (clone/fetch)
104pub async fn handle_upload_pack( 140pub async fn handle_upload_pack(
105 repo_path: PathBuf, 141 repo_path: PathBuf,
@@ -121,7 +157,10 @@ pub async fn handle_upload_pack(
121 stdin 157 stdin
122 .write_all(&request_body) 158 .write_all(&request_body)
123 .await 159 .await
124 .map_err(GitError::IoError)?; 160 .map_err(|e| {
161 error!("Failed to write to git upload-pack stdin: {}", e);
162 GitError::IoError(e)
163 })?;
125 // Close stdin to signal end of input 164 // Close stdin to signal end of input
126 drop(stdin); 165 drop(stdin);
127 } 166 }
@@ -135,7 +174,10 @@ pub async fn handle_upload_pack(
135 stdout 174 stdout
136 .read_to_end(&mut output) 175 .read_to_end(&mut output)
137 .await 176 .await
138 .map_err(GitError::IoError)?; 177 .map_err(|e| {
178 error!("Failed to read git upload-pack stdout: {}", e);
179 GitError::IoError(e)
180 })?;
139 } 181 }
140 182
141 if let Some(stderr) = git.take_stderr() { 183 if let Some(stderr) = git.take_stderr() {
@@ -143,14 +185,35 @@ pub async fn handle_upload_pack(
143 stderr 185 stderr
144 .read_to_end(&mut stderr_output) 186 .read_to_end(&mut stderr_output)
145 .await 187 .await
146 .map_err(GitError::IoError)?; 188 .map_err(|e| {
189 error!("Failed to read git upload-pack stderr: {}", e);
190 GitError::IoError(e)
191 })?;
147 } 192 }
148 193
149 // Wait for process 194 // Wait for process
150 let status = git.wait().await.map_err(GitError::IoError)?; 195 let status = git.wait().await.map_err(|e| {
196 error!("Failed to wait for git upload-pack process: {}", e);
197 GitError::IoError(e)
198 })?;
151 199
152 if !status.success() { 200 if !status.success() {
153 let stderr_str = String::from_utf8_lossy(&stderr_output); 201 let stderr_str = String::from_utf8_lossy(&stderr_output);
202
203 // Check if this is a git protocol error (exit code 128 with stderr)
204 // Protocol errors should be returned as HTTP 200 with ERR pkt-line
205 if is_git_protocol_error(status.code(), &stderr_output) {
206 warn!(
207 "Git upload-pack protocol error (returning ERR pkt-line): {}",
208 stderr_str
209 );
210 return Ok(build_git_protocol_error_response(
211 GitService::UploadPack,
212 &stderr_str,
213 ));
214 }
215
216 // Transport errors (spawn failures, signals, etc.) remain as HTTP 500
154 error!("Git upload-pack failed: {}", stderr_str); 217 error!("Git upload-pack failed: {}", stderr_str);
155 return Err(GitError::GitFailed(status.code())); 218 return Err(GitError::GitFailed(status.code()));
156 } 219 }
@@ -206,7 +269,7 @@ pub async fn handle_receive_pack(
206 } 269 }
207 270
208 // GRASP Authorization Check 271 // GRASP Authorization Check
209 info!( 272 debug!(
210 "Authorizing push for {} owned by {} via database query", 273 "Authorizing push for {} owned by {} via database query",
211 identifier, owner_pubkey 274 identifier, owner_pubkey
212 ); 275 );
@@ -251,7 +314,10 @@ pub async fn handle_receive_pack(
251 stdin 314 stdin
252 .write_all(&request_body) 315 .write_all(&request_body)
253 .await 316 .await
254 .map_err(GitError::IoError)?; 317 .map_err(|e| {
318 error!("Failed to write to git receive-pack stdin: {}", e);
319 GitError::IoError(e)
320 })?;
255 drop(stdin); 321 drop(stdin);
256 } 322 }
257 323
@@ -264,7 +330,10 @@ pub async fn handle_receive_pack(
264 stdout 330 stdout
265 .read_to_end(&mut output) 331 .read_to_end(&mut output)
266 .await 332 .await
267 .map_err(GitError::IoError)?; 333 .map_err(|e| {
334 error!("Failed to read git receive-pack stdout: {}", e);
335 GitError::IoError(e)
336 })?;
268 } 337 }
269 338
270 if let Some(stderr) = git.take_stderr() { 339 if let Some(stderr) = git.take_stderr() {
@@ -272,14 +341,35 @@ pub async fn handle_receive_pack(
272 stderr 341 stderr
273 .read_to_end(&mut stderr_output) 342 .read_to_end(&mut stderr_output)
274 .await 343 .await
275 .map_err(GitError::IoError)?; 344 .map_err(|e| {
345 error!("Failed to read git receive-pack stderr: {}", e);
346 GitError::IoError(e)
347 })?;
276 } 348 }
277 349
278 // Wait for process 350 // Wait for process
279 let status = git.wait().await.map_err(GitError::IoError)?; 351 let status = git.wait().await.map_err(|e| {
352 error!("Failed to wait for git receive-pack process: {}", e);
353 GitError::IoError(e)
354 })?;
280 355
281 if !status.success() { 356 if !status.success() {
282 let stderr_str = String::from_utf8_lossy(&stderr_output); 357 let stderr_str = String::from_utf8_lossy(&stderr_output);
358
359 // Check if this is a git protocol error (exit code 128 with stderr)
360 // Protocol errors should be returned as HTTP 200 with ERR pkt-line
361 if is_git_protocol_error(status.code(), &stderr_output) {
362 warn!(
363 "Git receive-pack protocol error (returning ERR pkt-line): {}",
364 stderr_str
365 );
366 return Ok(build_git_protocol_error_response(
367 GitService::ReceivePack,
368 &stderr_str,
369 ));
370 }
371
372 // Transport errors (spawn failures, signals, etc.) remain as HTTP 500
283 error!("Git receive-pack failed: {}", stderr_str); 373 error!("Git receive-pack failed: {}", stderr_str);
284 return Err(GitError::GitFailed(status.code())); 374 return Err(GitError::GitFailed(status.code()));
285 } 375 }