gunnar-sendpack 1.1.0

git's receive-pack wire format, both ends: the send-pack conversation gitoxide does not have, plus the server-side encoders for the same grammar. Plumbing only, no gunnar types.
Documentation

send-pack, and the receive-pack grammar under it — the half of git that gitoxide does not have.

Verified in gix 0.86: gix-protocol/src/ carries fetch/, ls_refs.rs and handshake/, and nothing for push. Direction::Push exists only as a refspec direction. So a git client written on gix has to put this on the wire itself, and a git server written on gix has to put the other side of it on the wire itself, and until this crate existed those two were written twice — once per direction, in two crates, in two different styles, agreeing only by inspection.

One grammar, both directions

That is the load-bearing idea, and it is why this crate is not called send-pack alone. Four formats make up the v0 push conversation:

format client server
[capabilities] parses the remote's, writes its own writes its own, parses the client's
[advertisement] [advertisement::parse] [advertisement::lines]
[command] [command::lines] [command::parse_line]
[report] [report::parse] [report::lines]

Each row is one implementation with two entry points, so an encoder and its decoder cannot drift: there is nothing for them to drift from. A test that round-trips one row exercises both ends of a real conversation rather than one end against a fixture written by the same hand.

Payloads, not packets

Every lines function returns payloads without pkt-line framing, without a terminating flush-pkt, and without a trailing newline, and every parser takes them the same way. git frames these sections as text pkt-lines, <4-hex-length><payload>\n, and the newline belongs to the framer.

That split is what lets a server that already owns a pkt-line framer and a socket use this crate without paying for a second framing layer: with default-features = false the whole crate is gix-hash + bstr + thiserror. The blocking-io feature — on by default, because a client is the majority consumer — adds [Transport], the pkt-line reader and [send_pack], framed with [gix_packetline].

Example: what a client does

# fn main() -> Result<(), gunnar_sendpack::Error> {
use gunnar_sendpack::{read_advertisement, send_pack, IoPair, PushCommand, SendPackOptions};

// Two pipes to a remote `git-receive-pack`.
# let (stdout, stdin): (std::fs::File, std::fs::File) = unimplemented!();
let mut transport = IoPair::new(stdout, stdin);
let (reader, _) = { use gunnar_sendpack::Transport as _; transport.io() };
let adv = read_advertisement(reader)?;

let tip = adv.oid_of("refs/heads/main");
# let new_tip = tip;
let commands = vec![PushCommand::update("refs/heads/main", tip, new_tip)];
let report = send_pack(
    &mut transport,
    &adv,
    &commands,
    adv.hash_kind,
    Some(|w: &mut dyn std::io::Write| Ok(w.write_all(b"PACK...")?)),
    &SendPackOptions::default(),
)?;
assert!(report.is_ok(), "{:?}", report.failure_summary());
# Ok(())
# }

Licence

MIT OR Apache-2.0, to match gitoxide's.