upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/git/subprocess.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-11-21 13:37:57 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-11-21 13:37:57 +0000
commit9a8c551adfada379704d594a6ff3862f13857b8e (patch)
treea902c6b313ab40a8914a34380ee194524dd67604 /src/git/subprocess.rs
parent12756fa66e3ec7f9dd24c66598085772829a8063 (diff)
add git http handling
Diffstat (limited to 'src/git/subprocess.rs')
-rw-r--r--src/git/subprocess.rs140
1 files changed, 140 insertions, 0 deletions
diff --git a/src/git/subprocess.rs b/src/git/subprocess.rs
new file mode 100644
index 0000000..96eed26
--- /dev/null
+++ b/src/git/subprocess.rs
@@ -0,0 +1,140 @@
1//! Git Subprocess Management
2//!
3//! This module provides utilities for spawning and managing Git subprocesses
4//! for upload-pack and receive-pack operations.
5
6use std::path::Path;
7use std::process::Stdio;
8use tokio::io::{AsyncRead, AsyncWrite};
9use tokio::process::{Child, Command};
10
11use super::protocol::GitService;
12
13/// Git subprocess wrapper
14pub struct GitSubprocess {
15 child: Child,
16}
17
18impl GitSubprocess {
19 /// Spawn a git subprocess for the given service and repository path
20 ///
21 /// # Arguments
22 /// * `service` - The Git service (upload-pack or receive-pack)
23 /// * `repo_path` - Path to the bare Git repository
24 /// * `advertise` - If true, run with --advertise-refs flag
25 pub fn spawn(
26 service: GitService,
27 repo_path: impl AsRef<Path>,
28 advertise: bool,
29 ) -> std::io::Result<Self> {
30 let repo_path = repo_path.as_ref();
31
32 let mut cmd = Command::new("git");
33 cmd.arg(service.as_str());
34
35 if advertise {
36 cmd.arg("--advertise-refs");
37 }
38
39 cmd.arg("--stateless-rpc");
40 cmd.arg(repo_path);
41
42 cmd.stdin(Stdio::piped());
43 cmd.stdout(Stdio::piped());
44 cmd.stderr(Stdio::piped());
45
46 let child = cmd.spawn()?;
47
48 Ok(Self { child })
49 }
50
51 /// Get a mutable reference to stdin
52 pub fn stdin(&mut self) -> Option<&mut (impl AsyncWrite + Unpin)> {
53 self.child.stdin.as_mut()
54 }
55
56 /// Get a mutable reference to stdout
57 pub fn stdout(&mut self) -> Option<&mut (impl AsyncRead + Unpin)> {
58 self.child.stdout.as_mut()
59 }
60
61 /// Get a mutable reference to stderr
62 pub fn stderr(&mut self) -> Option<&mut (impl AsyncRead + Unpin)> {
63 self.child.stderr.as_mut()
64 }
65
66 /// Take ownership of stdin
67 pub fn take_stdin(&mut self) -> Option<impl AsyncWrite> {
68 self.child.stdin.take()
69 }
70
71 /// Take ownership of stdout
72 pub fn take_stdout(&mut self) -> Option<impl AsyncRead> {
73 self.child.stdout.take()
74 }
75
76 /// Take ownership of stderr
77 pub fn take_stderr(&mut self) -> Option<impl AsyncRead> {
78 self.child.stderr.take()
79 }
80
81 /// Wait for the subprocess to complete
82 pub async fn wait(mut self) -> std::io::Result<std::process::ExitStatus> {
83 self.child.wait().await
84 }
85
86 /// Kill the subprocess
87 pub async fn kill(&mut self) -> std::io::Result<()> {
88 self.child.kill().await
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use tempfile::TempDir;
96 use std::process::Command as StdCommand;
97
98 fn create_bare_repo() -> TempDir {
99 let dir = TempDir::new().unwrap();
100 let status = StdCommand::new("git")
101 .args(["init", "--bare"])
102 .arg(dir.path())
103 .status()
104 .expect("Failed to run git init");
105 assert!(status.success());
106 dir
107 }
108
109 #[tokio::test]
110 async fn test_spawn_upload_pack_advertise() {
111 let repo = create_bare_repo();
112 let mut proc = GitSubprocess::spawn(
113 GitService::UploadPack,
114 repo.path(),
115 true,
116 ).expect("Failed to spawn git");
117
118 // Should have spawned successfully
119 assert!(proc.stdout().is_some());
120 assert!(proc.stdin().is_some());
121
122 // Clean up
123 let _ = proc.kill().await;
124 }
125
126 #[tokio::test]
127 async fn test_spawn_receive_pack() {
128 let repo = create_bare_repo();
129 let mut proc = GitSubprocess::spawn(
130 GitService::ReceivePack,
131 repo.path(),
132 false,
133 ).expect("Failed to spawn git");
134
135 assert!(proc.stdout().is_some());
136 assert!(proc.stdin().is_some());
137
138 let _ = proc.kill().await;
139 }
140} \ No newline at end of file