217 lines
8.6 KiB
Rust
217 lines
8.6 KiB
Rust
//! A real git component pushes an incompressible commit through the hook to native git's HTTP backend.
|
|
use sausage::{
|
|
exec::{Command, Exec, Options},
|
|
process::Runtime,
|
|
world::World,
|
|
};
|
|
use std::{
|
|
io::Write,
|
|
path::Path,
|
|
process::Stdio,
|
|
sync::{Arc, OnceLock},
|
|
};
|
|
fn git(cwd: &Path, args: &[&str]) {
|
|
assert!(
|
|
std::process::Command::new("git")
|
|
.args(args)
|
|
.current_dir(cwd)
|
|
.status()
|
|
.unwrap()
|
|
.success()
|
|
);
|
|
}
|
|
#[tokio::test]
|
|
async fn several_mb_git_push() {
|
|
static BUILT: OnceLock<std::path::PathBuf> = OnceLock::new();
|
|
let component = BUILT.get_or_init(|| {
|
|
let target = std::env::var_os("CARGO_TARGET_DIR")
|
|
.map(std::path::PathBuf::from)
|
|
.unwrap_or_else(|| {
|
|
Path::new(env!("CARGO_TARGET_TMPDIR"))
|
|
.parent()
|
|
.unwrap()
|
|
.to_owned()
|
|
});
|
|
let userland = Path::new(env!("CARGO_MANIFEST_DIR")).join("../userland");
|
|
if !userland.join("cwd-init/init.o").is_file() {
|
|
assert!(
|
|
std::process::Command::new("nu")
|
|
.args(["--no-config-file", "cwd-init/build.nu"])
|
|
.current_dir(&userland)
|
|
.status()
|
|
.unwrap()
|
|
.success()
|
|
);
|
|
}
|
|
let manifest = userland.join("Cargo.toml");
|
|
assert!(
|
|
std::process::Command::new("cargo")
|
|
.args(["build", "--release", "-p", "git", "--manifest-path"])
|
|
.arg(manifest)
|
|
.current_dir(&userland)
|
|
.args(["--target", "wasm32-wasip2", "--target-dir"])
|
|
.arg(&target)
|
|
.args(["-j", "2", "--locked"])
|
|
.status()
|
|
.unwrap()
|
|
.success()
|
|
);
|
|
target.join("wasm32-wasip2/release/git.wasm")
|
|
});
|
|
let temp = tempfile::tempdir().unwrap();
|
|
let world = World::open(temp.path()).unwrap();
|
|
let root = temp.path().join("world");
|
|
std::fs::create_dir_all(root.join("bin")).unwrap();
|
|
std::fs::copy(component, root.join("bin/git.wasm")).unwrap();
|
|
let repo = root.join("repo");
|
|
std::fs::create_dir_all(&repo).unwrap();
|
|
git(&repo, &["init", "-b", "main"]);
|
|
let mut data = vec![0; 4 * 1024 * 1024];
|
|
openssl::rand::rand_bytes(&mut data).unwrap();
|
|
std::fs::write(repo.join("large"), data).unwrap();
|
|
git(&repo, &["add", "large"]);
|
|
git(
|
|
&repo,
|
|
&[
|
|
"-c",
|
|
"user.name=Test",
|
|
"-c",
|
|
"user.email=test@example.com",
|
|
"commit",
|
|
"--no-gpg-sign",
|
|
"-m",
|
|
"large commit",
|
|
],
|
|
);
|
|
let remote = temp.path().join("remote");
|
|
std::fs::create_dir_all(&remote).unwrap();
|
|
git(&remote, &["init", "--bare", "destination.git"]);
|
|
git(
|
|
&remote.join("destination.git"),
|
|
&["config", "http.receivepack", "true"],
|
|
);
|
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
let port = listener.local_addr().unwrap().port();
|
|
git(
|
|
&repo,
|
|
&[
|
|
"remote",
|
|
"add",
|
|
"origin",
|
|
&format!("http://127.0.0.1:{port}/destination.git"),
|
|
],
|
|
);
|
|
let uploaded = Arc::new(std::sync::atomic::AtomicUsize::new(0));
|
|
let counter = uploaded.clone();
|
|
let server = tokio::spawn(async move {
|
|
loop {
|
|
let (socket, _) = listener.accept().await.unwrap();
|
|
let remote = remote.clone();
|
|
let counter = counter.clone();
|
|
let service =
|
|
hyper::service::service_fn(move |request: http::Request<hyper::body::Incoming>| {
|
|
let remote = remote.clone();
|
|
let counter = counter.clone();
|
|
async move {
|
|
use http_body_util::{BodyExt, Full};
|
|
let (parts, body) = request.into_parts();
|
|
let body = body.collect().await.unwrap().to_bytes();
|
|
counter.fetch_add(body.len(), std::sync::atomic::Ordering::Relaxed);
|
|
let response = tokio::task::spawn_blocking(move || {
|
|
let mut process = std::process::Command::new("git")
|
|
.arg("http-backend")
|
|
.env("GIT_PROJECT_ROOT", remote)
|
|
.env("GIT_HTTP_EXPORT_ALL", "1")
|
|
.env("REQUEST_METHOD", parts.method.as_str())
|
|
.env("PATH_INFO", parts.uri.path())
|
|
.env("QUERY_STRING", parts.uri.query().unwrap_or(""))
|
|
.env(
|
|
"CONTENT_TYPE",
|
|
parts
|
|
.headers
|
|
.get("content-type")
|
|
.map(|h| h.to_str().unwrap())
|
|
.unwrap_or(""),
|
|
)
|
|
.env("CONTENT_LENGTH", body.len().to_string())
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.spawn()
|
|
.unwrap();
|
|
let mut stdin = process.stdin.take().unwrap();
|
|
let output = std::thread::scope(|scope| {
|
|
scope.spawn(move || {
|
|
stdin.write_all(&body).unwrap();
|
|
});
|
|
process.wait_with_output().unwrap()
|
|
});
|
|
assert!(output.status.success());
|
|
let split = output
|
|
.stdout
|
|
.windows(4)
|
|
.position(|b| b == b"\r\n\r\n")
|
|
.unwrap();
|
|
let headers = std::str::from_utf8(&output.stdout[..split]).unwrap();
|
|
let mut response = http::Response::builder();
|
|
for line in headers.split("\r\n") {
|
|
let (name, value) = line.split_once(':').unwrap();
|
|
if name.eq_ignore_ascii_case("status") {
|
|
response = response.status(
|
|
value
|
|
.trim()
|
|
.split(' ')
|
|
.next()
|
|
.unwrap()
|
|
.parse::<u16>()
|
|
.unwrap(),
|
|
);
|
|
} else {
|
|
response = response.header(name, value.trim());
|
|
}
|
|
}
|
|
response
|
|
.body(Full::new(bytes::Bytes::copy_from_slice(
|
|
&output.stdout[split + 4..],
|
|
)))
|
|
.unwrap()
|
|
})
|
|
.await
|
|
.unwrap();
|
|
Ok::<_, std::convert::Infallible>(response)
|
|
}
|
|
});
|
|
hyper::server::conn::http1::Builder::new()
|
|
.keep_alive(false)
|
|
.serve_connection(hyper_util::rt::TokioIo::new(socket), service)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
});
|
|
let runtime = Runtime::new(world).unwrap();
|
|
let mut output = Vec::new();
|
|
let code = runtime
|
|
.exec(
|
|
Command::Argv(vec![
|
|
"/bin/git.wasm".into(),
|
|
"push".into(),
|
|
"origin".into(),
|
|
"main".into(),
|
|
]),
|
|
Options {
|
|
cwd: "/repo".into(),
|
|
timeout: Some(std::time::Duration::from_secs(60)),
|
|
..Default::default()
|
|
},
|
|
&mut |_, b| output.extend_from_slice(b),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
server.abort();
|
|
assert_eq!(code, 0, "{}", String::from_utf8_lossy(&output));
|
|
assert!(uploaded.load(std::sync::atomic::Ordering::Relaxed) > 3 * 1024 * 1024);
|
|
git(
|
|
&temp.path().join("remote/destination.git"),
|
|
&["cat-file", "-e", "refs/heads/main:large"],
|
|
);
|
|
}
|