@@ -197,6 +197,139 @@
end
end
describe "buffering split data" do
test "handles clone data split across two feed calls" do
repo = RepoHelper.memory_repo()
ExGitObjectstore.init(repo)
{commit_sha, _, _} = create_commit(repo, "hello\n", "init\n")
:ok = Ref.put(repo, "refs/heads/main", commit_sha, nil)
{_advert, state} = UploadPack.init(repo)
# Build complete clone request
full_data =
PktLine.encode("want #{commit_sha}") <>
PktLine.flush() <>
PktLine.encode("done")
# Split at an arbitrary point mid-pkt-line
split_point = div(byte_size(full_data), 2)
<<part1::binary-size(split_point), part2::binary>> = full_data
# First feed: should buffer, return empty
{response1, state1} = UploadPack.feed(state, part1)
assert response1 == <<>>
assert state1.phase == :negotiation
assert state1.negotiate_buffer == part1
# Second feed: should complete and return packfile
{response2, state2} = UploadPack.feed(state1, part2)
assert UploadPack.done?(state2)
assert byte_size(response2) > 0
# Verify pack data is present and parseable
pack_data = extract_sideband_pack(response2)
assert pack_data != nil
{:ok, entries} = Reader.parse(pack_data)
types = Enum.map(entries, & &1.type) |> MapSet.new()
assert :commit in types
assert :tree in types
assert :blob in types
end
test "handles clone data split into three feed calls" do
repo = RepoHelper.memory_repo()
ExGitObjectstore.init(repo)
{commit_sha, _, _} = create_commit(repo, "content\n", "commit\n")
:ok = Ref.put(repo, "refs/heads/main", commit_sha, nil)
{_advert, state} = UploadPack.init(repo)
full_data =
PktLine.encode("want #{commit_sha}") <>
PktLine.flush() <>
PktLine.encode("done")
# Split into three parts
size = byte_size(full_data)
s1 = div(size, 3)
s2 = div(size * 2, 3)
<<p1::binary-size(s1), rest::binary>> = full_data
<<p2::binary-size(s2 - s1), p3::binary>> = rest
# Feed part 1
{r1, st1} = UploadPack.feed(state, p1)
assert r1 == <<>>
assert st1.phase == :negotiation
# Feed part 2
{r2, st2} = UploadPack.feed(st1, p2)
assert r2 == <<>>
assert st2.phase == :negotiation
# Feed part 3 — should complete
{r3, st3} = UploadPack.feed(st2, p3)
assert UploadPack.done?(st3)
assert byte_size(r3) > 0
end
test "handles split at exact pkt-line boundary" do
repo = RepoHelper.memory_repo()
ExGitObjectstore.init(repo)
{commit_sha, _, _} = create_commit(repo, "test\n", "init\n")
:ok = Ref.put(repo, "refs/heads/main", commit_sha, nil)
{_advert, state} = UploadPack.init(repo)
# Split exactly between the want line and the flush
want_line = PktLine.encode("want #{commit_sha}")
rest_of_data = PktLine.flush() <> PktLine.encode("done")
{r1, st1} = UploadPack.feed(state, want_line)
assert r1 == <<>>
assert st1.phase == :negotiation
{r2, st2} = UploadPack.feed(st1, rest_of_data)
assert UploadPack.done?(st2)
assert byte_size(r2) > 0
end
test "handles fetch with haves split across feeds" do
repo = RepoHelper.memory_repo()
ExGitObjectstore.init(repo)
{c1_sha, _, _} = create_commit(repo, "v1\n", "first\n")
:ok = Ref.put(repo, "refs/heads/main", c1_sha, nil)
{c2_sha, _, _} = create_commit(repo, "v2\n", "second\n", [c1_sha])
:ok = Ref.put(repo, "refs/heads/main", c2_sha, c1_sha)
{_advert, state} = UploadPack.init(repo)
full_data =
PktLine.encode("want #{c2_sha}") <>
PktLine.encode("have #{c1_sha}") <>
PktLine.flush() <>
PktLine.encode("done")
# Split mid-way through the "have" line
want_size = byte_size(PktLine.encode("want #{c2_sha}"))
split_point = want_size + 10
<<part1::binary-size(split_point), part2::binary>> = full_data
{r1, st1} = UploadPack.feed(state, part1)
assert r1 == <<>>
assert st1.phase == :negotiation
{r2, st2} = UploadPack.feed(st1, part2)
assert UploadPack.done?(st2)
assert byte_size(r2) > 0
end
end
# Extract pack data from sideband-encoded pkt-line response.
# The response contains NAK text lines and sideband data lines (band byte 1 = pack).
# PktLine.decode strips newlines from data, so we need raw parsing for sideband.