-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathgit_protocol.rs
161 lines (143 loc) · 4.85 KB
/
git_protocol.rs
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#![allow(incomplete_features)]
use bytes::{Buf, BufMut, BytesMut};
use std::{collections::HashMap, path::PathBuf, process::Stdio};
use tokio::{
io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader},
process::{ChildStdout, Command},
};
use warp::{
http,
hyper::{body::Sender, Body, Response},
Rejection,
};
use crate::errors::FreighterError;
#[derive(Default)]
pub struct GitCommand {}
/// ### References Codes
///
/// - [conduit-git-http-backend][<https://github.com/conduit-rust/conduit-git-http-backend/blob/master/src/lib.rs>].
///
///
/// handle request from git client
impl GitCommand {
pub async fn git_info_refs(
&self,
mut body: impl Buf,
work_dir: PathBuf,
) -> Result<Response<Body>, Rejection> {
let mut cmd = Command::new("git");
// git 数据检查
cmd.args([
"upload-pack",
// "--http-backend-info-refs",
"--stateless-rpc",
"--advertise-refs",
work_dir.join("crates.io-index").to_str().unwrap(),
]);
cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
let p = cmd.spawn().unwrap();
let mut git_input = p.stdin.unwrap();
while body.has_remaining() {
git_input.write_all_buf(&mut body.chunk()).await.unwrap();
let cnt = body.chunk().len();
body.advance(cnt);
}
let git_output = BufReader::new(p.stdout.unwrap());
let mut headers = HashMap::new();
headers.insert(
"Content-Type".to_string(),
"application/x-git-upload-pack-advertisement".to_string(),
);
headers.insert(
"Cache-Control".to_string(),
"no-cache, max-age=0, must-revalidate".to_string(),
);
tracing::info!("headers: {:?}", headers);
let mut resp = Response::builder();
for (key, val) in headers {
resp = resp.header(&key, val);
}
let (sender, body) = Body::channel();
tokio::spawn(send(sender, git_output, true));
let resp = resp.body(body).unwrap();
Ok(resp)
}
pub async fn git_upload_pack(
&self,
mut body: impl Buf,
work_dir: PathBuf,
method: http::Method,
content_type: Option<String>,
) -> Result<Response<Body>, Rejection> {
let mut cmd = Command::new("git");
cmd.arg("http-backend");
cmd.env("GIT_PROJECT_ROOT", &work_dir);
cmd.env("PATH_INFO", "/crates.io-index/git-upload-pack");
cmd.env("REQUEST_METHOD", method.as_str());
// cmd.env("QUERY_STRING", query);
if let Some(content_type) = content_type {
cmd.env("CONTENT_TYPE", content_type);
}
cmd.env("GIT_HTTP_EXPORT_ALL", "true");
cmd.stderr(Stdio::inherit());
cmd.stdout(Stdio::piped());
cmd.stdin(Stdio::piped());
let p = cmd.spawn().unwrap();
let mut git_input = p.stdin.unwrap();
while body.has_remaining() {
git_input.write_all_buf(&mut body.chunk()).await.unwrap();
let cnt = body.chunk().len();
println!(
"request body: {:?}",
String::from_utf8(body.chunk().to_vec()).unwrap()
);
body.advance(cnt);
}
let mut git_output = BufReader::new(p.stdout.unwrap());
let mut headers = HashMap::new();
loop {
let mut line = String::new();
git_output.read_line(&mut line).await.unwrap();
let line = line.trim_end();
if line.is_empty() {
break;
}
if let Some((key, value)) = line.split_once(": ") {
headers.insert(key.to_string(), value.to_string());
}
}
tracing::info!("headers: {:?}", headers);
let mut resp = Response::builder();
for (key, val) in headers {
resp = resp.header(&key, val);
}
let (sender, body) = Body::channel();
tokio::spawn(send(sender, git_output, false));
let resp = resp.body(body).unwrap();
Ok(resp)
}
}
async fn send(
mut sender: Sender,
mut git_output: BufReader<ChildStdout>,
add_refs: bool,
) -> Result<(), FreighterError> {
if add_refs {
let mut buf = BytesMut::new();
buf.put(&b"001e# service=git-upload-pack\n0000"[..]);
sender.send_data(buf.freeze()).await.unwrap();
}
loop {
let mut bytes_out = BytesMut::new();
let res = git_output.read_buf(&mut bytes_out).await;
tracing::info!("reading output :{:?}", res);
if bytes_out.is_empty() {
tracing::info!("send:empty");
return Ok(());
}
if add_refs {
tracing::info!("send: bytes_out: {:?}", bytes_out.clone().freeze());
}
sender.send_data(bytes_out.freeze()).await.unwrap();
}
}