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