Drain go-git upload-pack double-NAK before sideband demux

main

Commit

Soph5mo ago

Root cause: go-git's v6 upload-pack server (plumbing/transport/upload_pack.go) emits two NAK pktlines when the client sends haves but none are reachable from the wants. The first NAK is written via ServerResponse.Encode for the "if len(haves) > 0" branch (empty ACKs => NAK), the second via the "no common ack found" branch. The response looks like:

0008NAK\n 0008NAK\n 0009\x01PACK... (sideband channel 1)

go-git's ServerResponse.Decode returns after consuming the first NAK, so the second NAK sits in front of the sideband-wrapped pack. The sideband demuxer then reads "NAK\n" as a frame and fails with "unknown channel NAK" (channel byte 'N' = 0x4e) because valid channels are 0x01/0x02/0x03.

This reliably hits any fetch where the target's advertised ref is not an ancestor of the source's want - exactly the rewind/overwrite scenario replicate mode is designed for. The same bug affected sync --force via the materialized strategy, but was masked in existing tests because multi-ref scenarios happened to include at least one have whose hash was reachable from a want, producing an ACK and skipping the two-NAK branch.

Fix: wrap the post-Decode reader in bufio.Reader and drain any extra "0008NAK\n" pktlines before handing off to the sideband demuxer. Applied to both fetchToStoreV1 (materialized path) and fetchPackV1 (relay path). A short stream that cannot satisfy the 8-byte peek is treated as "no more NAKs" so the downstream reader surfaces the real read error.

Also reverts the V1->V2 workaround in TestRun_IntegrationReplicateOverwrites- DivergentBranch now that V1 handles this scenario correctly, and adds TestFetchPackV1DrainsSecondNAK asserting the drainer's behavior directly.

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com Entire-Checkpoint: a8de9a2a91c3

Checkpoints

can you review the changes in this branch

Claude CodeOpus 4.6[1m]
View session
Checkpoint 1