upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-11-21 15:35:19 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-11-21 15:35:19 +0000
commitee7e115b2d0e6a6eee42eb875199c965696017d5 (patch)
tree634ab7f960d56dc9073ebc85baf9fa6c193a32c8 /src
parent97e21b62eab89bab1456db7df27df8f1c85399f0 (diff)
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?
Diffstat (limited to 'src')
-rw-r--r--src/git/handlers.rs47
-rw-r--r--src/git/protocol.rs8
-rw-r--r--src/git/subprocess.rs2
-rw-r--r--src/http/mod.rs30
-rw-r--r--src/nostr/builder.rs34
5 files changed, 96 insertions, 25 deletions
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 @@
4 4
5use std::path::PathBuf; 5use std::path::PathBuf;
6use hyper::{body::Bytes, Response, StatusCode}; 6use hyper::{body::Bytes, Response, StatusCode};
7use http_body_util::Full;
7use tokio::io::{AsyncReadExt, AsyncWriteExt}; 8use tokio::io::{AsyncReadExt, AsyncWriteExt};
8use tracing::{debug, error, warn}; 9use tracing::{debug, error, warn};
9 10
@@ -16,7 +17,7 @@ use super::subprocess::GitSubprocess;
16pub async fn handle_info_refs( 17pub async fn handle_info_refs(
17 repo_path: PathBuf, 18 repo_path: PathBuf,
18 service: GitService, 19 service: GitService,
19) -> Result<Response<String>, GitError> { 20) -> Result<Response<Full<Bytes>>, GitError> {
20 debug!("Handling info/refs for {:?} with service {:?}", repo_path, service); 21 debug!("Handling info/refs for {:?} with service {:?}", repo_path, service);
21 22
22 // Check if repository exists 23 // Check if repository exists
@@ -34,6 +35,8 @@ pub async fn handle_info_refs(
34 35
35 // Read the output from git 36 // Read the output from git
36 let mut output = Vec::new(); 37 let mut output = Vec::new();
38 let mut stderr_output = Vec::new();
39
37 if let Some(stdout) = git.take_stdout() { 40 if let Some(stdout) = git.take_stdout() {
38 let mut stdout = stdout; 41 let mut stdout = stdout;
39 stdout.read_to_end(&mut output).await 42 stdout.read_to_end(&mut output).await
@@ -42,6 +45,15 @@ pub async fn handle_info_refs(
42 GitError::IoError(e) 45 GitError::IoError(e)
43 })?; 46 })?;
44 } 47 }
48
49 if let Some(stderr) = git.take_stderr() {
50 let mut stderr = stderr;
51 stderr.read_to_end(&mut stderr_output).await
52 .map_err(|e| {
53 error!("Failed to read git stderr: {}", e);
54 GitError::IoError(e)
55 })?;
56 }
45 57
46 // Wait for process to complete 58 // Wait for process to complete
47 let status = git.wait().await 59 let status = git.wait().await
@@ -51,7 +63,8 @@ pub async fn handle_info_refs(
51 })?; 63 })?;
52 64
53 if !status.success() { 65 if !status.success() {
54 error!("Git process failed with status: {:?}", status); 66 let stderr_str = String::from_utf8_lossy(&stderr_output);
67 error!("Git process failed with status: {:?}, stderr: {}", status, stderr_str);
55 return Err(GitError::GitFailed(status.code())); 68 return Err(GitError::GitFailed(status.code()));
56 } 69 }
57 70
@@ -70,7 +83,7 @@ pub async fn handle_info_refs(
70 .status(StatusCode::OK) 83 .status(StatusCode::OK)
71 .header("content-type", service.advertisement_content_type()) 84 .header("content-type", service.advertisement_content_type())
72 .header("cache-control", "no-cache") 85 .header("cache-control", "no-cache")
73 .body(String::from_utf8_lossy(&response_body).to_string()) 86 .body(Full::new(Bytes::from(response_body)))
74 .unwrap()) 87 .unwrap())
75} 88}
76 89
@@ -78,7 +91,7 @@ pub async fn handle_info_refs(
78pub async fn handle_upload_pack( 91pub async fn handle_upload_pack(
79 repo_path: PathBuf, 92 repo_path: PathBuf,
80 request_body: Bytes, 93 request_body: Bytes,
81) -> Result<Response<String>, GitError> { 94) -> Result<Response<Full<Bytes>>, GitError> {
82 debug!("Handling upload-pack for {:?}", repo_path); 95 debug!("Handling upload-pack for {:?}", repo_path);
83 96
84 if !repo_path.exists() { 97 if !repo_path.exists() {
@@ -99,17 +112,27 @@ pub async fn handle_upload_pack(
99 112
100 // Read response from git's stdout 113 // Read response from git's stdout
101 let mut output = Vec::new(); 114 let mut output = Vec::new();
115 let mut stderr_output = Vec::new();
116
102 if let Some(stdout) = git.take_stdout() { 117 if let Some(stdout) = git.take_stdout() {
103 let mut stdout = stdout; 118 let mut stdout = stdout;
104 stdout.read_to_end(&mut output).await 119 stdout.read_to_end(&mut output).await
105 .map_err(GitError::IoError)?; 120 .map_err(GitError::IoError)?;
106 } 121 }
122
123 if let Some(stderr) = git.take_stderr() {
124 let mut stderr = stderr;
125 stderr.read_to_end(&mut stderr_output).await
126 .map_err(GitError::IoError)?;
127 }
107 128
108 // Wait for process 129 // Wait for process
109 let status = git.wait().await 130 let status = git.wait().await
110 .map_err(GitError::IoError)?; 131 .map_err(GitError::IoError)?;
111 132
112 if !status.success() { 133 if !status.success() {
134 let stderr_str = String::from_utf8_lossy(&stderr_output);
135 error!("Git upload-pack failed: {}", stderr_str);
113 return Err(GitError::GitFailed(status.code())); 136 return Err(GitError::GitFailed(status.code()));
114 } 137 }
115 138
@@ -117,7 +140,7 @@ pub async fn handle_upload_pack(
117 .status(StatusCode::OK) 140 .status(StatusCode::OK)
118 .header("content-type", GitService::UploadPack.result_content_type()) 141 .header("content-type", GitService::UploadPack.result_content_type())
119 .header("cache-control", "no-cache") 142 .header("cache-control", "no-cache")
120 .body(String::from_utf8_lossy(&output).to_string()) 143 .body(Full::new(Bytes::from(output)))
121 .unwrap()) 144 .unwrap())
122} 145}
123 146
@@ -127,7 +150,7 @@ pub async fn handle_upload_pack(
127pub async fn handle_receive_pack( 150pub async fn handle_receive_pack(
128 repo_path: PathBuf, 151 repo_path: PathBuf,
129 request_body: Bytes, 152 request_body: Bytes,
130) -> Result<Response<String>, GitError> { 153) -> Result<Response<Full<Bytes>>, GitError> {
131 debug!("Handling receive-pack for {:?}", repo_path); 154 debug!("Handling receive-pack for {:?}", repo_path);
132 155
133 if !repo_path.exists() { 156 if !repo_path.exists() {
@@ -151,17 +174,27 @@ pub async fn handle_receive_pack(
151 174
152 // Read response from git's stdout 175 // Read response from git's stdout
153 let mut output = Vec::new(); 176 let mut output = Vec::new();
177 let mut stderr_output = Vec::new();
178
154 if let Some(stdout) = git.take_stdout() { 179 if let Some(stdout) = git.take_stdout() {
155 let mut stdout = stdout; 180 let mut stdout = stdout;
156 stdout.read_to_end(&mut output).await 181 stdout.read_to_end(&mut output).await
157 .map_err(GitError::IoError)?; 182 .map_err(GitError::IoError)?;
158 } 183 }
184
185 if let Some(stderr) = git.take_stderr() {
186 let mut stderr = stderr;
187 stderr.read_to_end(&mut stderr_output).await
188 .map_err(GitError::IoError)?;
189 }
159 190
160 // Wait for process 191 // Wait for process
161 let status = git.wait().await 192 let status = git.wait().await
162 .map_err(GitError::IoError)?; 193 .map_err(GitError::IoError)?;
163 194
164 if !status.success() { 195 if !status.success() {
196 let stderr_str = String::from_utf8_lossy(&stderr_output);
197 error!("Git receive-pack failed: {}", stderr_str);
165 return Err(GitError::GitFailed(status.code())); 198 return Err(GitError::GitFailed(status.code()));
166 } 199 }
167 200
@@ -169,7 +202,7 @@ pub async fn handle_receive_pack(
169 .status(StatusCode::OK) 202 .status(StatusCode::OK)
170 .header("content-type", GitService::ReceivePack.result_content_type()) 203 .header("content-type", GitService::ReceivePack.result_content_type())
171 .header("cache-control", "no-cache") 204 .header("cache-control", "no-cache")
172 .body(String::from_utf8_lossy(&output).to_string()) 205 .body(Full::new(Bytes::from(output)))
173 .unwrap()) 206 .unwrap())
174} 207}
175 208
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 {
157 } 157 }
158 } 158 }
159 159
160 /// Get the git command name (without "git-" prefix) for subprocess invocation
161 pub fn command_name(&self) -> &'static str {
162 match self {
163 Self::UploadPack => "upload-pack",
164 Self::ReceivePack => "receive-pack",
165 }
166 }
167
160 /// Get the content type for the service advertisement 168 /// Get the content type for the service advertisement
161 pub fn advertisement_content_type(&self) -> &'static str { 169 pub fn advertisement_content_type(&self) -> &'static str {
162 match self { 170 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 {
30 let repo_path = repo_path.as_ref(); 30 let repo_path = repo_path.as_ref();
31 31
32 let mut cmd = Command::new("git"); 32 let mut cmd = Command::new("git");
33 cmd.arg(service.as_str()); 33 cmd.arg(service.command_name());
34 34
35 if advertise { 35 if advertise {
36 cmd.arg("--advertise-refs"); 36 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;
14use hyper::service::Service; 14use hyper::service::Service;
15use hyper::{Method, Request, Response}; 15use hyper::{Method, Request, Response};
16use hyper_util::rt::TokioIo; 16use hyper_util::rt::TokioIo;
17use http_body_util::BodyExt; 17use http_body_util::{BodyExt, Full};
18use nostr_sdk::hashes::sha1::Hash as Sha1Hash; 18use nostr_sdk::hashes::sha1::Hash as Sha1Hash;
19use nostr_sdk::hashes::{Hash, HashEngine}; 19use nostr_sdk::hashes::{Hash, HashEngine};
20use nostr_relay_builder::LocalRelay; 20use nostr_relay_builder::LocalRelay;
@@ -42,7 +42,7 @@ impl HttpService {
42} 42}
43 43
44impl Service<Request<Incoming>> for HttpService { 44impl Service<Request<Incoming>> for HttpService {
45 type Response = Response<String>; 45 type Response = Response<Full<Bytes>>;
46 type Error = String; 46 type Error = String;
47 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>; 47 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
48 48
@@ -65,6 +65,11 @@ impl Service<Request<Incoming>> for HttpService {
65 let repo_path = git::resolve_repo_path(&git_data_path, &npub, &identifier); 65 let repo_path = git::resolve_repo_path(&git_data_path, &npub, &identifier);
66 66
67 return Box::pin(async move { 67 return Box::pin(async move {
68 // Collect request body once before the match statement
69 let body_bytes = req.collect().await
70 .map(|collected| collected.to_bytes())
71 .unwrap_or_else(|_| Bytes::new());
72
68 let result = match (method.as_ref(), subpath.as_str()) { 73 let result = match (method.as_ref(), subpath.as_str()) {
69 // GET /info/refs?service=git-upload-pack or git-receive-pack 74 // GET /info/refs?service=git-upload-pack or git-receive-pack
70 (m, sp) if m == Method::GET && sp.starts_with("info/refs") => { 75 (m, sp) if m == Method::GET && sp.starts_with("info/refs") => {
@@ -85,22 +90,12 @@ impl Service<Request<Incoming>> for HttpService {
85 90
86 // POST /git-upload-pack (clone/fetch) 91 // POST /git-upload-pack (clone/fetch)
87 (m, "git-upload-pack") if m == Method::POST => { 92 (m, "git-upload-pack") if m == Method::POST => {
88 // Read request body
89 let body_bytes = req.collect().await
90 .map(|collected| collected.to_bytes())
91 .unwrap_or_else(|_| Bytes::new());
92
93 git::handlers::handle_upload_pack(repo_path, body_bytes).await 93 git::handlers::handle_upload_pack(repo_path, body_bytes).await
94 } 94 }
95 95
96 // POST /git-receive-pack (push) 96 // POST /git-receive-pack (push)
97 (m, "git-receive-pack") if m == Method::POST => { 97 (m, "git-receive-pack") if m == Method::POST => {
98 // Read request body 98 git::handlers::handle_receive_pack(repo_path, body_bytes.clone()).await
99 let body_bytes = req.collect().await
100 .map(|collected| collected.to_bytes())
101 .unwrap_or_else(|_| Bytes::new());
102
103 git::handlers::handle_receive_pack(repo_path, body_bytes).await
104 } 99 }
105 100
106 _ => { 101 _ => {
@@ -112,9 +107,10 @@ impl Service<Request<Incoming>> for HttpService {
112 Ok(response) => Ok(response), 107 Ok(response) => Ok(response),
113 Err(e) => { 108 Err(e) => {
114 tracing::error!("Git handler error: {}", e); 109 tracing::error!("Git handler error: {}", e);
110 let error_msg = format!("Git error: {}", e);
115 Ok(Response::builder() 111 Ok(Response::builder()
116 .status(e.status_code()) 112 .status(e.status_code())
117 .body(format!("Git error: {}", e)) 113 .body(Full::new(Bytes::from(error_msg)))
118 .unwrap()) 114 .unwrap())
119 } 115 }
120 } 116 }
@@ -141,7 +137,7 @@ impl Service<Request<Incoming>> for HttpService {
141 .status(200) 137 .status(200)
142 .header("content-type", "application/nostr+json") 138 .header("content-type", "application/nostr+json")
143 .header("access-control-allow-origin", "*") 139 .header("access-control-allow-origin", "*")
144 .body(json) 140 .body(Full::new(Bytes::from(json)))
145 .unwrap()) 141 .unwrap())
146 }); 142 });
147 } 143 }
@@ -185,7 +181,7 @@ impl Service<Request<Incoming>> for HttpService {
185 .header(CONNECTION, "upgrade") 181 .header(CONNECTION, "upgrade")
186 .header(UPGRADE, "websocket") 182 .header(UPGRADE, "websocket")
187 .header(SEC_WEBSOCKET_ACCEPT, derived.unwrap()) 183 .header(SEC_WEBSOCKET_ACCEPT, derived.unwrap())
188 .body("".to_string()) 184 .body(Full::new(Bytes::new()))
189 .unwrap()) 185 .unwrap())
190 }); 186 });
191 } 187 }
@@ -197,7 +193,7 @@ impl Service<Request<Incoming>> for HttpService {
197 Ok(base 193 Ok(base
198 .status(200) 194 .status(200)
199 .header("content-type", "text/html; charset=utf-8") 195 .header("content-type", "text/html; charset=utf-8")
200 .body(html) 196 .body(Full::new(Bytes::from(html)))
201 .unwrap()) 197 .unwrap())
202 }) 198 })
203 } 199 }
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 {
73 return Err(format!("git init failed: {}", stderr)); 73 return Err(format!("git init failed: {}", stderr));
74 } 74 }
75 75
76 // Create an initial empty commit so the repository can be cloned
77 // This is required because git clone fails on completely empty repositories
78 let output = std::process::Command::new("git")
79 .args(&[
80 "--git-dir", repo_path.to_str().unwrap(),
81 "commit-tree", "-m", "Initial empty commit",
82 "4b825dc642cb6eb9a060e54bf8d69288fbee4904" // Empty tree hash
83 ])
84 .output()
85 .map_err(|e| format!("Failed to create initial commit: {}", e))?;
86
87 if !output.status.success() {
88 let stderr = String::from_utf8_lossy(&output.stderr);
89 tracing::warn!("Failed to create initial commit (repository may not be cloneable): {}", stderr);
90 // Don't fail here - the repository was created successfully
91 } else {
92 // Extract commit hash from stdout
93 let commit_hash = String::from_utf8_lossy(&output.stdout).trim().to_string();
94
95 // Create master branch pointing to this commit
96 let output = std::process::Command::new("git")
97 .args(&[
98 "--git-dir", repo_path.to_str().unwrap(),
99 "update-ref", "refs/heads/master", &commit_hash
100 ])
101 .output()
102 .map_err(|e| format!("Failed to create master branch: {}", e))?;
103
104 if !output.status.success() {
105 let stderr = String::from_utf8_lossy(&output.stderr);
106 tracing::warn!("Failed to create master branch: {}", stderr);
107 }
108 }
109
76 tracing::info!("Created bare repository at {}", repo_path.display()); 110 tracing::info!("Created bare repository at {}", repo_path.display());
77 Ok(()) 111 Ok(())
78 } 112 }