use std::net::SocketAddr;
use std::sync::Arc;
use anyhow::{bail, Result};
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
use tokio::net::{TcpListener, TcpStream};
use crate::gitcmd;
use crate::storage::Store;
pub async fn serve(store: Arc<Store>, addr: SocketAddr) -> Result<()> {
let listener = TcpListener::bind(addr).await?;
println!("storage: git-exec tcp listening on {addr}");
loop {
let (socket, _) = listener.accept().await?;
let store = store.clone();
tokio::spawn(async move {
if let Err(e) = handle_conn(store, socket).await {
eprintln!("git-exec tcp: {e}");
}
});
}
}
async fn handle_conn(store: Arc<Store>, socket: TcpStream) -> Result<()> {
let (read_half, mut write_half) = socket.into_split();
let mut reader = BufReader::new(read_half);
let mut header = String::new();
reader.read_line(&mut header).await?;
let header = header.trim_end();
let mut parts = header.splitn(2, ' ');
let (Some(verb), Some(name)) = (parts.next(), parts.next()) else {
bail!("bad header {header:?}");
};
if verb != "upload-pack" && verb != "receive-pack" {
bail!("bad verb {verb:?}");
}
let repo_path = store.path(name)?;
let repo_path_str = repo_path.to_string_lossy().to_string();
let mut child = gitcmd::spawn_piped(None, &[verb, &repo_path_str], &[])?;
let mut stdin = child.stdin.take().expect("piped stdin");
let mut stdout = child.stdout.take().expect("piped stdout");
let mut stderr = child.stderr.take().expect("piped stderr");
let stdin_task = tokio::spawn(async move {
let _ = tokio::io::copy(&mut reader, &mut stdin).await;
});
let stderr_task = tokio::spawn(async move {
let mut buf = Vec::new();
let _ = stderr.read_to_end(&mut buf).await;
buf
});
let copy_result = tokio::io::copy(&mut stdout, &mut write_half).await;
let _ = write_half.shutdown().await;
stdin_task.abort();
let status = child.wait().await;
let errbuf = stderr_task.await.unwrap_or_default();
match status {
Ok(s) if !s.success() => {
eprintln!("{name} {verb}: exited {s}: {}", String::from_utf8_lossy(&errbuf));
}
Err(e) => eprintln!("{name} {verb}: wait error: {e}"),
_ => {}
}
copy_result?;
Ok(())
}