From ee7e115b2d0e6a6eee42eb875199c965696017d5 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Fri, 21 Nov 2025 15:35:19 +0000 Subject: fixed http clone but do we really nedd to create a blank commit? I dont think ngit-relay does that. Do we need to se the default branch or is this automatic? --- src/git/handlers.rs | 47 ++++++++++++++++++++++++++++++++++++++++------- src/git/protocol.rs | 8 ++++++++ src/git/subprocess.rs | 2 +- src/http/mod.rs | 30 +++++++++++++----------------- src/nostr/builder.rs | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 96 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/git/handlers.rs b/src/git/handlers.rs index 0efc9d0..ac35d14 100644 --- a/src/git/handlers.rs +++ b/src/git/handlers.rs @@ -4,6 +4,7 @@ use std::path::PathBuf; use hyper::{body::Bytes, Response, StatusCode}; +use http_body_util::Full; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tracing::{debug, error, warn}; @@ -16,7 +17,7 @@ use super::subprocess::GitSubprocess; pub async fn handle_info_refs( repo_path: PathBuf, service: GitService, -) -> Result, GitError> { +) -> Result>, GitError> { debug!("Handling info/refs for {:?} with service {:?}", repo_path, service); // Check if repository exists @@ -34,6 +35,8 @@ pub async fn handle_info_refs( // Read the output from git let mut output = Vec::new(); + let mut stderr_output = Vec::new(); + if let Some(stdout) = git.take_stdout() { let mut stdout = stdout; stdout.read_to_end(&mut output).await @@ -42,6 +45,15 @@ pub async fn handle_info_refs( GitError::IoError(e) })?; } + + if let Some(stderr) = git.take_stderr() { + let mut stderr = stderr; + stderr.read_to_end(&mut stderr_output).await + .map_err(|e| { + error!("Failed to read git stderr: {}", e); + GitError::IoError(e) + })?; + } // Wait for process to complete let status = git.wait().await @@ -51,7 +63,8 @@ pub async fn handle_info_refs( })?; if !status.success() { - error!("Git process failed with status: {:?}", status); + let stderr_str = String::from_utf8_lossy(&stderr_output); + error!("Git process failed with status: {:?}, stderr: {}", status, stderr_str); return Err(GitError::GitFailed(status.code())); } @@ -70,7 +83,7 @@ pub async fn handle_info_refs( .status(StatusCode::OK) .header("content-type", service.advertisement_content_type()) .header("cache-control", "no-cache") - .body(String::from_utf8_lossy(&response_body).to_string()) + .body(Full::new(Bytes::from(response_body))) .unwrap()) } @@ -78,7 +91,7 @@ pub async fn handle_info_refs( pub async fn handle_upload_pack( repo_path: PathBuf, request_body: Bytes, -) -> Result, GitError> { +) -> Result>, GitError> { debug!("Handling upload-pack for {:?}", repo_path); if !repo_path.exists() { @@ -99,17 +112,27 @@ pub async fn handle_upload_pack( // Read response from git's stdout let mut output = Vec::new(); + let mut stderr_output = Vec::new(); + if let Some(stdout) = git.take_stdout() { let mut stdout = stdout; stdout.read_to_end(&mut output).await .map_err(GitError::IoError)?; } + + if let Some(stderr) = git.take_stderr() { + let mut stderr = stderr; + stderr.read_to_end(&mut stderr_output).await + .map_err(GitError::IoError)?; + } // Wait for process let status = git.wait().await .map_err(GitError::IoError)?; if !status.success() { + let stderr_str = String::from_utf8_lossy(&stderr_output); + error!("Git upload-pack failed: {}", stderr_str); return Err(GitError::GitFailed(status.code())); } @@ -117,7 +140,7 @@ pub async fn handle_upload_pack( .status(StatusCode::OK) .header("content-type", GitService::UploadPack.result_content_type()) .header("cache-control", "no-cache") - .body(String::from_utf8_lossy(&output).to_string()) + .body(Full::new(Bytes::from(output))) .unwrap()) } @@ -127,7 +150,7 @@ pub async fn handle_upload_pack( pub async fn handle_receive_pack( repo_path: PathBuf, request_body: Bytes, -) -> Result, GitError> { +) -> Result>, GitError> { debug!("Handling receive-pack for {:?}", repo_path); if !repo_path.exists() { @@ -151,17 +174,27 @@ pub async fn handle_receive_pack( // Read response from git's stdout let mut output = Vec::new(); + let mut stderr_output = Vec::new(); + if let Some(stdout) = git.take_stdout() { let mut stdout = stdout; stdout.read_to_end(&mut output).await .map_err(GitError::IoError)?; } + + if let Some(stderr) = git.take_stderr() { + let mut stderr = stderr; + stderr.read_to_end(&mut stderr_output).await + .map_err(GitError::IoError)?; + } // Wait for process let status = git.wait().await .map_err(GitError::IoError)?; if !status.success() { + let stderr_str = String::from_utf8_lossy(&stderr_output); + error!("Git receive-pack failed: {}", stderr_str); return Err(GitError::GitFailed(status.code())); } @@ -169,7 +202,7 @@ pub async fn handle_receive_pack( .status(StatusCode::OK) .header("content-type", GitService::ReceivePack.result_content_type()) .header("cache-control", "no-cache") - .body(String::from_utf8_lossy(&output).to_string()) + .body(Full::new(Bytes::from(output))) .unwrap()) } diff --git a/src/git/protocol.rs b/src/git/protocol.rs index 84da131..93177de 100644 --- a/src/git/protocol.rs +++ b/src/git/protocol.rs @@ -157,6 +157,14 @@ impl GitService { } } + /// Get the git command name (without "git-" prefix) for subprocess invocation + pub fn command_name(&self) -> &'static str { + match self { + Self::UploadPack => "upload-pack", + Self::ReceivePack => "receive-pack", + } + } + /// Get the content type for the service advertisement pub fn advertisement_content_type(&self) -> &'static str { match self { diff --git a/src/git/subprocess.rs b/src/git/subprocess.rs index 96eed26..dac3ace 100644 --- a/src/git/subprocess.rs +++ b/src/git/subprocess.rs @@ -30,7 +30,7 @@ impl GitSubprocess { let repo_path = repo_path.as_ref(); let mut cmd = Command::new("git"); - cmd.arg(service.as_str()); + cmd.arg(service.command_name()); if advertise { cmd.arg("--advertise-refs"); diff --git a/src/http/mod.rs b/src/http/mod.rs index 28ccd7b..c676bda 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -14,7 +14,7 @@ use hyper::server::conn::http1; use hyper::service::Service; use hyper::{Method, Request, Response}; use hyper_util::rt::TokioIo; -use http_body_util::BodyExt; +use http_body_util::{BodyExt, Full}; use nostr_sdk::hashes::sha1::Hash as Sha1Hash; use nostr_sdk::hashes::{Hash, HashEngine}; use nostr_relay_builder::LocalRelay; @@ -42,7 +42,7 @@ impl HttpService { } impl Service> for HttpService { - type Response = Response; + type Response = Response>; type Error = String; type Future = Pin> + Send>>; @@ -65,6 +65,11 @@ impl Service> for HttpService { let repo_path = git::resolve_repo_path(&git_data_path, &npub, &identifier); return Box::pin(async move { + // Collect request body once before the match statement + let body_bytes = req.collect().await + .map(|collected| collected.to_bytes()) + .unwrap_or_else(|_| Bytes::new()); + let result = match (method.as_ref(), subpath.as_str()) { // GET /info/refs?service=git-upload-pack or git-receive-pack (m, sp) if m == Method::GET && sp.starts_with("info/refs") => { @@ -85,22 +90,12 @@ impl Service> for HttpService { // POST /git-upload-pack (clone/fetch) (m, "git-upload-pack") if m == Method::POST => { - // Read request body - let body_bytes = req.collect().await - .map(|collected| collected.to_bytes()) - .unwrap_or_else(|_| Bytes::new()); - git::handlers::handle_upload_pack(repo_path, body_bytes).await } // POST /git-receive-pack (push) (m, "git-receive-pack") if m == Method::POST => { - // Read request body - let body_bytes = req.collect().await - .map(|collected| collected.to_bytes()) - .unwrap_or_else(|_| Bytes::new()); - - git::handlers::handle_receive_pack(repo_path, body_bytes).await + git::handlers::handle_receive_pack(repo_path, body_bytes.clone()).await } _ => { @@ -112,9 +107,10 @@ impl Service> for HttpService { Ok(response) => Ok(response), Err(e) => { tracing::error!("Git handler error: {}", e); + let error_msg = format!("Git error: {}", e); Ok(Response::builder() .status(e.status_code()) - .body(format!("Git error: {}", e)) + .body(Full::new(Bytes::from(error_msg))) .unwrap()) } } @@ -141,7 +137,7 @@ impl Service> for HttpService { .status(200) .header("content-type", "application/nostr+json") .header("access-control-allow-origin", "*") - .body(json) + .body(Full::new(Bytes::from(json))) .unwrap()) }); } @@ -185,7 +181,7 @@ impl Service> for HttpService { .header(CONNECTION, "upgrade") .header(UPGRADE, "websocket") .header(SEC_WEBSOCKET_ACCEPT, derived.unwrap()) - .body("".to_string()) + .body(Full::new(Bytes::new())) .unwrap()) }); } @@ -197,7 +193,7 @@ impl Service> for HttpService { Ok(base .status(200) .header("content-type", "text/html; charset=utf-8") - .body(html) + .body(Full::new(Bytes::from(html))) .unwrap()) }) } diff --git a/src/nostr/builder.rs b/src/nostr/builder.rs index 259c380..a0b82f4 100644 --- a/src/nostr/builder.rs +++ b/src/nostr/builder.rs @@ -73,6 +73,40 @@ impl Nip34WritePolicy { return Err(format!("git init failed: {}", stderr)); } + // Create an initial empty commit so the repository can be cloned + // This is required because git clone fails on completely empty repositories + let output = std::process::Command::new("git") + .args(&[ + "--git-dir", repo_path.to_str().unwrap(), + "commit-tree", "-m", "Initial empty commit", + "4b825dc642cb6eb9a060e54bf8d69288fbee4904" // Empty tree hash + ]) + .output() + .map_err(|e| format!("Failed to create initial commit: {}", e))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + tracing::warn!("Failed to create initial commit (repository may not be cloneable): {}", stderr); + // Don't fail here - the repository was created successfully + } else { + // Extract commit hash from stdout + let commit_hash = String::from_utf8_lossy(&output.stdout).trim().to_string(); + + // Create master branch pointing to this commit + let output = std::process::Command::new("git") + .args(&[ + "--git-dir", repo_path.to_str().unwrap(), + "update-ref", "refs/heads/master", &commit_hash + ]) + .output() + .map_err(|e| format!("Failed to create master branch: {}", e))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + tracing::warn!("Failed to create master branch: {}", stderr); + } + } + tracing::info!("Created bare repository at {}", repo_path.display()); Ok(()) } -- cgit v1.2.3