fix: UploadPackV2 emits `ready` (or omits acks) when client sends `done` #18
fix/upload-pack-v2-done-ready
into main
Closes #27.
Summary
git fetch against a repo served by UploadPackV2 was failing with
fatal: expected no other sections to be sent after no 'ready'
whenever the client’s local history was unrelated to what’s on the server (reproduced against fangorn/hephaestus on 2026-04-18).
The server was emitting an acknowledgments section ending in NAK, followed by a packfile section. That’s a protocol-v2 violation: the spec says the acks section must end with ready if a packfile follows — otherwise the section must be omitted entirely.
Fix
handle_fetch/2 now parses done from the fetch args and build_acknowledgments/3 branches on it:
| client state | response |
|---|---|
done + no matching haves |
acks section omitted, packfile follows |
done + some ACKs |
acknowledgments / ACK … / ready / delim + packfile |
no done, no ACKs yet |
acknowledgments / NAK / flush (multi-round continues) |
no done, some ACKs |
acknowledgments / ACK … / flush (multi-round continues) |
Tests
Two layers, both new or updated in this PR:
-
State machine —
test/ex_git_objectstore/protocol/upload_pack_v2_test.exswith \done` and no matching haves: acks+packfile response must include `ready`` — the exact protocol invariant from the specwith \done` and no matching haves: packfile is sent (client wants unconditional send)` — locks in that a fix can’t just drop the packfileno common ancestors + done: omits acks section and sends packfile— updated to reflect correct behaviour
-
Real
gitclient interop — new filetest/ex_git_objectstore/integration/upload_pack_v2_git_client_test.exs- Spins up an in-process
git://TCP daemon overUploadPackV2and runs the realgitbinary against it clone of a single-commit repo succeedsclone of a multi-commit chain succeedsfetch works when client has unrelated history (hephaestus regression)fetch after clone is a no-op (matching haves)
- Spins up an in-process
Before the fix, the hephaestus-regression test reproduces the exact client error. After the fix, all four interop tests pass.
Test plan
-
mix test --include integration— 713 / 713 pass locally -
mix format --check-formatted -
mix credo --stricton touched files — no new issues - Downstream:
anvilstill compiles with the local path dep - CI passes on this branch