pub struct Negotiation {
pub common: Vec<ObjectId>,
pub ack: Vec<ObjectId>,
pub ready: bool,
}Expand description
What one round of negotiation concluded.
Fields§
§common: Vec<ObjectId>The haves this repository actually holds, in the order they were sent.
What the pack is computed against, and therefore complete: this is
git’s have_obj, not its common.
ack: Vec<ObjectId>Of those, the ones the server did not already know the client held —
what goes on the wire as ACK.
§The difference, and what it is worth
A have that is common tells the server about that commit and its
whole ancestry: a client does not hold a commit without holding its
parents. So when the next have in the batch is the previous one’s
parent — which is exactly what a client walking its own history back
from a tip sends — the server already knew, and ACKing it says
nothing the client did not just say itself.
git has always done this. upload-pack.c’s process_haves marks every
common have’s parents THEY_HAVE and appends to the ACK array only
when !we_knew_they_have. gunnar ACKed the whole of common.
MEASURED 2026-08-04, bench/examples/fetchbytes-probe.rs, one
negotiated fetch across the 500-commit h2h-divergent-2000b-500d
divergence, both servers reaching ready on the same round with the
same 1 008 haves: stock git sent 1 ACK, gunnar sent 508, and
gunnar’s 508 were a straight parent chain whose head was git’s one.
At 45 payload bytes and 5 of pkt-line header that is 25 400 bytes —
against a total server→client stream of 366 818, and against a
whole-fetch deficit to stock git of 73 875 on that fixture. A third of
gunnar’s fetch wire-byte loss was ACK lines.
Fewer ACKs cannot make a negotiation worse: the client is told nothing
it did not derive, ready is decided from Negotiation::common which
is untouched, and the pack is computed from the same set as before.
ready: boolWhether the server has found a good enough cut point to send a pack.