use std::fs::File;
use std::io::{self, BufReader, Read, Write};
use std::path::PathBuf;
use std::process::ExitCode;
use gunnar_lfs::{FsLfsStore, Operation, Session};
fn main() -> ExitCode {
let args: Vec<String> = std::env::args().skip(1).collect();
let (git_dir, operation) = match args.as_slice() {
[git_dir, operation] => (PathBuf::from(git_dir), operation.as_str()),
_ => {
eprintln!("usage: gunnar-lfs-serve <git-dir> <upload|download>");
return ExitCode::from(64);
}
};
let Some(operation) = Operation::parse(operation) else {
eprintln!("gunnar-lfs-serve: `{operation}` is not an lfs operation");
return ExitCode::from(64);
};
let store = match FsLfsStore::open_or_init(&git_dir) {
Ok(store) => store,
Err(err) => {
eprintln!("gunnar-lfs-serve: {err}");
return ExitCode::from(69);
}
};
let transcript = std::env::var_os("GUNNAR_LFS_TRANSCRIPT_DIR").map(PathBuf::from);
let stem = format!("{}.{operation}", std::process::id());
let tee_in = transcript.as_ref().map(|dir| {
File::create(dir.join(format!("{stem}.client-to-server.bin"))).expect("transcript")
});
let tee_out = transcript.as_ref().map(|dir| {
File::create(dir.join(format!("{stem}.server-to-client.bin"))).expect("transcript")
});
let mut input = BufReader::new(TeeRead {
inner: io::stdin(),
tee: tee_in,
});
let mut output = TeeWrite {
inner: io::stdout(),
tee: tee_out,
};
match Session::new(&store, operation).serve(&mut input, &mut output) {
Ok(summary) => {
let _ = output.flush();
eprintln!(
"gunnar-lfs-serve: commands={} batches={} batch_objects={} \
sent={}/{}B received={}/{}B clean_quit={}",
summary.commands,
summary.batches,
summary.batch_objects,
summary.objects_sent,
summary.bytes_sent,
summary.objects_received,
summary.bytes_received,
summary.clean_quit,
);
ExitCode::SUCCESS
}
Err(err) => {
let _ = output.flush();
eprintln!("gunnar-lfs-serve: session failed: {err}");
ExitCode::from(70)
}
}
}
struct TeeRead<R: Read> {
inner: R,
tee: Option<File>,
}
impl<R: Read> Read for TeeRead<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let n = self.inner.read(buf)?;
if let Some(tee) = self.tee.as_mut() {
tee.write_all(&buf[..n])?;
}
Ok(n)
}
}
struct TeeWrite<W: Write> {
inner: W,
tee: Option<File>,
}
impl<W: Write> Write for TeeWrite<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let n = self.inner.write(buf)?;
if let Some(tee) = self.tee.as_mut() {
tee.write_all(&buf[..n])?;
}
Ok(n)
}
fn flush(&mut self) -> io::Result<()> {
if let Some(tee) = self.tee.as_mut() {
tee.flush()?;
}
self.inner.flush()
}
}