@@ -40,275 +40,691 @@
{commit_sha, blob_sha, tree_sha}
end
defp create_chain(repo, count) do
Enum.reduce(1..count, {[], nil}, fn i, {shas, parent} ->
parents = if parent, do: [parent], else: []
{sha, _, _} = create_commit(repo, "content #{i}\n", "commit #{i}\n", parents)
if i == 1 do
:ok = Ref.put(repo, "refs/heads/main", sha, nil)
else
:ok = Ref.put(repo, "refs/heads/main", sha, parent)
end
{shas ++ [sha], sha}
end)
end
describe "init" do
test "advertises v2 capabilities" do
test "returns capability advertisement" do
repo = RepoHelper.memory_repo()
ExGitObjectstore.init(repo)
{advert, state} = UploadPackV2.init(repo)
assert state.phase == :command
{:ok, packets, ""} = PktLine.decode(advert)
assert state.buffer == <<>>
{:ok, packets, _rest} = PktLine.decode(advert)
data_lines = for {:data, d} <- packets, do: d
assert "version 2" in data_lines
assert "ls-refs" in data_lines
assert :flush in packets
assert Enum.any?(data_lines, &String.starts_with?(&1, "fetch"))
assert List.last(packets) == :flush
end
end
describe "ls-refs" do
test "returns refs for a repo with commits" do
describe "ls-refs command" do
test "returns all refs" do
repo = RepoHelper.memory_repo()
ExGitObjectstore.init(repo)
{commit_sha, _, _} = create_commit(repo, "hello", "init")
{commit_sha, _, _} = create_commit(repo, "hello\n", "init\n")
:ok = Ref.put(repo, "refs/heads/main", commit_sha, nil)
{_advert, state} = UploadPackV2.init(repo)
client_data =
PktLine.encode("command=ls-refs") <>
PktLine.delim() <>
PktLine.encode("peel") <>
PktLine.encode("symrefs") <>
# Build a ls-refs command
ls_refs_cmd =
IO.iodata_to_binary([
PktLine.encode("command=ls-refs"),
PktLine.delim(),
PktLine.encode("ref-prefix refs/heads/"),
PktLine.flush()
])
{response, new_state} = UploadPackV2.feed(state, client_data)
{response, new_state} = UploadPackV2.feed(state, ls_refs_cmd)
# Should still be in :command phase (ls-refs doesn't end the session)
assert new_state.phase == :command
assert new_state.buffer == <<>>
assert byte_size(response) > 0
{:ok, packets, _} = PktLine.decode(response)
{:ok, packets, ""} = PktLine.decode(response)
data_lines = for {:data, d} <- packets, do: d
assert Enum.any?(data_lines, &String.contains?(&1, commit_sha))
assert Enum.any?(data_lines, &String.contains?(&1, "refs/heads/main"))
end
test "filters by ref-prefix" do
repo = RepoHelper.memory_repo()
ExGitObjectstore.init(repo)
{sha1, _, _} = create_commit(repo, "a\n", "a\n")
{sha2, _, _} = create_commit(repo, "b\n", "b\n")
:ok = Ref.put(repo, "refs/heads/main", sha1, nil)
:ok = Ref.put(repo, "refs/tags/v1.0", sha2, nil)
{_advert, state} = UploadPackV2.init(repo)
# Only request heads
client_data =
PktLine.encode("command=ls-refs") <>
PktLine.delim() <>
PktLine.encode("ref-prefix refs/heads/") <>
PktLine.flush()
{response, _state} = UploadPackV2.feed(state, client_data)
{:ok, packets, ""} = PktLine.decode(response)
data_lines = for {:data, d} <- packets, do: d
assert Enum.any?(data_lines, &String.contains?(&1, "refs/heads/main"))
refute Enum.any?(data_lines, &String.contains?(&1, "refs/tags/"))
end
end
describe "fetch" do
test "returns packfile for a simple clone" do
describe "fetch command - clone (no haves)" do
test "returns packfile with all objects for a single commit" do
repo = RepoHelper.memory_repo()
ExGitObjectstore.init(repo)
{commit_sha, _, _} = create_commit(repo, "hello", "init")
{commit_sha, _blob_sha, _tree_sha} = create_commit(repo, "hello\n", "init\n")
:ok = Ref.put(repo, "refs/heads/main", commit_sha, nil)
{_advert, state} = UploadPackV2.init(repo)
# Build a fetch command
fetch_cmd =
client_data =
PktLine.encode("command=fetch") <>
IO.iodata_to_binary([
PktLine.encode("command=fetch"),
PktLine.delim(),
PktLine.encode("want #{commit_sha}"),
PktLine.encode("done"),
PktLine.delim() <>
PktLine.encode("want #{commit_sha}") <>
PktLine.encode("done") <>
PktLine.flush()
])
{response, new_state} = UploadPackV2.feed(state, client_data)
{response, new_state} = UploadPackV2.feed(state, fetch_cmd)
assert new_state.phase == :done
assert byte_size(response) > 0
# The response must contain a "packfile" section
assert String.contains?(response, "packfile")
# Extract pack data from sideband
pack_data = extract_sideband_pack(response)
assert pack_data != nil
# Response should contain "packfile" section
{:ok, packets, _} = PktLine.decode(response)
data_lines = for {:data, d} <- packets, do: d
assert "packfile" in data_lines
assert byte_size(pack_data) > 0
# Verify the pack is parseable and contains expected object types
assert {:ok, entries} = Reader.parse(pack_data)
assert length(entries) >= 3
types = Enum.map(entries, & &1.type) |> MapSet.new()
assert :blob in types
assert :tree in types
assert :commit in types
end
end
describe "buffering split data" do
test "handles ls-refs command split across two feed calls" do
test "returns packfile for repo with multiple commits" do
repo = RepoHelper.memory_repo()
ExGitObjectstore.init(repo)
{commit_sha, _, _} = create_commit(repo, "hello", "init")
:ok = Ref.put(repo, "refs/heads/main", commit_sha, nil)
{shas, _last} = create_chain(repo, 5)
tip = List.last(shas)
{_advert, state} = UploadPackV2.init(repo)
client_data =
# Build a complete ls-refs command
full_cmd =
PktLine.encode("command=fetch") <>
PktLine.delim() <>
PktLine.encode("want #{tip}") <>
PktLine.encode("done") <>
IO.iodata_to_binary([
PktLine.encode("command=ls-refs"),
PktLine.delim(),
PktLine.encode("ref-prefix refs/heads/"),
PktLine.flush()
])
# Split at an arbitrary point mid-command
split_point = div(byte_size(full_cmd), 2)
<<part1::binary-size(split_point), part2::binary>> = full_cmd
{response, new_state} = UploadPackV2.feed(state, client_data)
# First feed: should buffer, return empty response
{response1, state1} = UploadPackV2.feed(state, part1)
assert response1 == <<>>
assert state1.phase == :command
assert state1.buffer == part1
assert new_state.phase == :done
pack_data = extract_sideband_pack(response)
assert pack_data != nil
# Second feed: should complete the command
{response2, state2} = UploadPackV2.feed(state1, part2)
assert state2.phase == :command
assert state2.buffer == <<>>
assert byte_size(response2) > 0
assert {:ok, entries} = Reader.parse(pack_data)
{:ok, packets, _} = PktLine.decode(response2)
data_lines = for {:data, d} <- packets, do: d
assert Enum.any?(data_lines, &String.contains?(&1, commit_sha))
# 5 commits + 5 trees + 5 blobs = 15 objects
assert length(entries) == 15
commit_count = Enum.count(entries, &(&1.type == :commit))
assert commit_count == 5
end
test "handles fetch command split across three feed calls" do
test "returns packfile for repo with many commits (30+)" do
repo = RepoHelper.memory_repo()
ExGitObjectstore.init(repo)
{shas, _last} = create_chain(repo, 30)
tip = List.last(shas)
{_advert, state} = UploadPackV2.init(repo)
client_data =
PktLine.encode("command=fetch") <>
{commit_sha, _, _} = create_commit(repo, "content", "commit")
PktLine.delim() <>
PktLine.encode("want #{tip}") <>
PktLine.encode("done") <>
PktLine.flush()
{response, new_state} = UploadPackV2.feed(state, client_data)
assert new_state.phase == :done
pack_data = extract_sideband_pack(response)
assert pack_data != nil
assert {:ok, entries} = Reader.parse(pack_data)
assert length(entries) == 90
end
test "packfile response is parseable by git protocol" do
repo = RepoHelper.memory_repo()
ExGitObjectstore.init(repo)
{commit_sha, _, _} = create_commit(repo, "test\n", "test\n")
:ok = Ref.put(repo, "refs/heads/main", commit_sha, nil)
{_advert, state} = UploadPackV2.init(repo)
client_data =
# Build a complete fetch command
full_cmd =
IO.iodata_to_binary([
PktLine.encode("command=fetch"),
PktLine.delim(),
PktLine.encode("want #{commit_sha}"),
PktLine.encode("done"),
PktLine.encode("command=fetch") <>
PktLine.delim() <>
PktLine.encode("want #{commit_sha}") <>
PktLine.encode("done") <>
PktLine.flush()
])
# Split into three parts
size = byte_size(full_cmd)
s1 = div(size, 3)
s2 = div(size * 2, 3)
<<p1::binary-size(s1), rest::binary>> = full_cmd
<<p2::binary-size(s2 - s1), p3::binary>> = rest
{response, _state} = UploadPackV2.feed(state, client_data)
# Parse the full response as pkt-lines
# For a clone (no haves), the expected format is:
# "packfile\n" pkt-line
# sideband data (band=1 prefix for pack data)
# Feed part 1
{r1, st1} = UploadPackV2.feed(state, p1)
assert r1 == <<>>
assert st1.phase == :command
# 0000 flush
{:ok, packets, rest} = PktLine.decode(response)
# Feed part 2
{r2, st2} = UploadPackV2.feed(st1, p2)
assert r2 == <<>>
assert st2.phase == :command
# First data packet should be "packfile"
first_data =
Enum.find(packets, fn
{:data, _} -> true
_ -> false
end)
# Feed part 3 — should complete
{r3, st3} = UploadPackV2.feed(st2, p3)
assert st3.phase == :done
assert byte_size(r3) > 0
assert {:data, packfile_line} = first_data
assert String.trim(packfile_line) == "packfile"
# After the packfile line, the remaining data is sideband-encoded
{:ok, packets, _} = PktLine.decode(r3)
data_lines = for {:data, d} <- packets, do: d
assert "packfile" in data_lines
# It should end with a flush
assert :flush in packets or rest == "" or String.ends_with?(response, "0000")
end
end
test "handles split at exact pkt-line boundary" do
describe "fetch command - incremental (with haves)" do
test "sends only new objects when client has previous commit" do
repo = RepoHelper.memory_repo()
ExGitObjectstore.init(repo)
{commit_sha, _, _} = create_commit(repo, "test", "init")
{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} = UploadPackV2.init(repo)
client_data =
PktLine.encode("command=fetch") <>
PktLine.delim() <>
PktLine.encode("want #{c2_sha}") <>
PktLine.encode("have #{c1_sha}") <>
PktLine.encode("done") <>
PktLine.flush()
{response, new_state} = UploadPackV2.feed(state, client_data)
assert new_state.phase == :done
# Should have acknowledgments section with ACK for c1
assert String.contains?(response, "acknowledgments")
assert String.contains?(response, "ACK #{c1_sha}")
assert String.contains?(response, "ready")
# Should have packfile section
assert String.contains?(response, "packfile")
pack_data = extract_sideband_pack(response)
assert pack_data != nil
assert {:ok, entries} = Reader.parse(pack_data)
# Should only have new objects (c2's commit + tree + blob = 3)
assert length(entries) == 3
end
test "returns NAK when no common ancestors" 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)
{_advert, state} = UploadPackV2.init(repo)
fake_have = String.duplicate("f", 40)
client_data =
PktLine.encode("command=fetch") <>
PktLine.delim() <>
PktLine.encode("want #{c1_sha}") <>
PktLine.encode("have #{fake_have}") <>
PktLine.encode("done") <>
PktLine.flush()
{response, _state} = UploadPackV2.feed(state, client_data)
assert String.contains?(response, "acknowledgments")
assert String.contains?(response, "NAK")
end
end
describe "full HTTP-style flow (ls-refs then fetch)" do
test "simulates a complete git clone via protocol v2" do
repo = RepoHelper.memory_repo()
ExGitObjectstore.init(repo)
{commit_sha, _, _} = create_commit(repo, "hello world\n", "initial commit\n")
:ok = Ref.put(repo, "refs/heads/main", commit_sha, nil)
# Step 1: init (capability advertisement)
{advert, state} = UploadPackV2.init(repo)
assert String.contains?(advert, "version 2")
# Step 2: ls-refs (client discovers refs)
ls_refs_data =
PktLine.encode("command=ls-refs") <>
PktLine.delim() <>
PktLine.encode("peel") <>
PktLine.encode("symrefs") <>
PktLine.encode("ref-prefix refs/heads/") <>
PktLine.encode("ref-prefix refs/tags/") <>
PktLine.flush()
{ls_refs_response, state} = UploadPackV2.feed(state, ls_refs_data)
assert String.contains?(ls_refs_response, commit_sha)
assert state.phase == :command
# Step 3: fetch (client requests objects)
fetch_data =
PktLine.encode("command=fetch") <>
PktLine.delim() <>
PktLine.encode("want #{commit_sha}") <>
PktLine.encode("done") <>
PktLine.flush()
{fetch_response, state} = UploadPackV2.feed(state, fetch_data)
assert state.phase == :done
# Verify the pack data is valid
pack_data = extract_sideband_pack(fetch_response)
assert pack_data != nil
assert {:ok, entries} = Reader.parse(pack_data)
assert length(entries) >= 3
end
test "simulates incremental fetch (pull)" 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)
{c3_sha, _, _} = create_commit(repo, "v3\n", "third\n", [c2_sha])
:ok = Ref.put(repo, "refs/heads/main", c3_sha, c2_sha)
# Client already has c1, wants c3 (tip)
{_advert, state} = UploadPackV2.init(repo)
# Split exactly between the command line and the delim
fetch_data =
PktLine.encode("command=fetch") <>
PktLine.delim() <>
PktLine.encode("want #{c3_sha}") <>
PktLine.encode("have #{c1_sha}") <>
PktLine.encode("done") <>
PktLine.flush()
{response, _state} = UploadPackV2.feed(state, fetch_data)
command_line = PktLine.encode("command=ls-refs")
rest_of_cmd = IO.iodata_to_binary([PktLine.delim(), PktLine.flush()])
{r1, st1} = UploadPackV2.feed(state, command_line)
pack_data = extract_sideband_pack(response)
assert pack_data != nil
assert {:ok, entries} = Reader.parse(pack_data)
assert r1 == <<>>
assert st1.phase == :command
{r2, st2} = UploadPackV2.feed(st1, rest_of_cmd)
assert st2.phase == :command
assert byte_size(r2) > 0
# c2 + c3 each have commit + tree + blob = 6 objects
assert length(entries) == 6
end
end
describe "full v2 conversation" do
test "ls-refs followed by fetch" do
describe "error handling" do
test "returns flush for unknown command" do
repo = RepoHelper.memory_repo()
ExGitObjectstore.init(repo)
{_advert, state} = UploadPackV2.init(repo)
client_data =
PktLine.encode("command=invalid") <>
PktLine.delim() <>
PktLine.flush()
{commit_sha, _, _} = create_commit(repo, "hello world", "initial")
{response, new_state} = UploadPackV2.feed(state, client_data)
assert response == PktLine.flush()
assert new_state.phase == :done
end
test "returns flush for malformed data" do
repo = RepoHelper.memory_repo()
ExGitObjectstore.init(repo)
{_advert, state} = UploadPackV2.init(repo)
{response, new_state} = UploadPackV2.feed(state, "garbage data")
assert response == PktLine.flush()
assert new_state.phase == :done
end
test "fetch with non-existent want SHA returns error gracefully" do
repo = RepoHelper.memory_repo()
ExGitObjectstore.init(repo)
fake_sha = String.duplicate("a", 40)
{_advert, state} = UploadPackV2.init(repo)
client_data =
PktLine.encode("command=fetch") <>
PktLine.delim() <>
PktLine.encode("want #{fake_sha}") <>
PktLine.encode("done") <>
PktLine.flush()
{response, new_state} = UploadPackV2.feed(state, client_data)
assert new_state.phase == :done
# Should return something parseable (either an empty pack or a flush)
assert byte_size(response) > 0
end
end
describe "scale - large repos" do
test "handles 100 commits with multi-file trees" do
repo = RepoHelper.memory_repo()
ExGitObjectstore.init(repo)
# Create 100 commits, each with 5 files (simulates a real project)
{_shas, tip} =
Enum.reduce(1..100, {[], nil}, fn i, {shas, parent} ->
parents = if parent, do: [parent], else: []
# Create 5 blobs per commit
blobs =
Enum.map(1..5, fn j ->
content = "file #{j} content at commit #{i}\n" <> String.duplicate("x", 100)
blob = Blob.from_content(content)
{:ok, blob_sha} = Object.write(repo, blob)
%{mode: "100644", name: "file_#{j}.txt", sha: blob_sha}
end)
tree = Tree.new(blobs)
{:ok, tree_sha} = Object.write(repo, tree)
commit = %Commit{
tree: tree_sha,
parents: parents,
author: "Test <t@t.com> #{1_000_000_000 + i} +0000",
committer: "Test <t@t.com> #{1_000_000_000 + i} +0000",
message: "commit #{i}\n"
}
{:ok, sha} = Object.write(repo, commit)
if i == 1 do
:ok = Ref.put(repo, "refs/heads/main", sha, nil)
else
:ok = Ref.put(repo, "refs/heads/main", sha, parent)
end
{shas ++ [sha], sha}
end)
{_advert, state} = UploadPackV2.init(repo)
client_data =
PktLine.encode("command=fetch") <>
PktLine.delim() <>
PktLine.encode("want #{tip}") <>
PktLine.encode("done") <>
PktLine.flush()
{response, new_state} = UploadPackV2.feed(state, client_data)
assert new_state.phase == :done
assert String.contains?(response, "packfile")
pack_data = extract_sideband_pack(response)
assert pack_data != nil
assert {:ok, entries} = Reader.parse(pack_data)
# 100 commits + 100 trees + 500 blobs = 700 objects
# (some blobs may be deduplicated if content repeats)
assert length(entries) > 200
assert Enum.count(entries, &(&1.type == :commit)) == 100
end
test "pack data starts with valid PACK header" do
repo = RepoHelper.memory_repo()
ExGitObjectstore.init(repo)
{commit_sha, _, _} = create_commit(repo, "test\n", "test\n")
:ok = Ref.put(repo, "refs/heads/main", commit_sha, nil)
{_advert, state} = UploadPackV2.init(repo)
client_data =
# Step 1: ls-refs
ls_refs =
PktLine.encode("command=fetch") <>
PktLine.delim() <>
PktLine.encode("want #{commit_sha}") <>
PktLine.encode("done") <>
IO.iodata_to_binary([
PktLine.encode("command=ls-refs"),
PktLine.delim(),
PktLine.encode("ref-prefix refs/heads/"),
PktLine.flush()
])
{ls_response, state} = UploadPackV2.feed(state, ls_refs)
assert state.phase == :command
{:ok, ls_packets, _} = PktLine.decode(ls_response)
data_lines = for {:data, d} <- ls_packets, do: d
{response, _state} = UploadPackV2.feed(state, client_data)
pack_data = extract_sideband_pack(response)
assert pack_data != nil
# Git pack format starts with "PACK" magic bytes
assert <<"PACK", _version::32, _count::32, _rest::binary>> = pack_data
end
@tag :slow
test "handles 200 commits (production-scale)" do
repo = RepoHelper.memory_repo()
ExGitObjectstore.init(repo)
{_shas, tip} = create_chain(repo, 200)
{_advert, state} = UploadPackV2.init(repo)
client_data =
PktLine.encode("command=fetch") <>
PktLine.delim() <>
PktLine.encode("want #{tip}") <>
PktLine.encode("done") <>
PktLine.flush()
{response, new_state} = UploadPackV2.feed(state, client_data)
assert new_state.phase == :done
assert String.contains?(response, "packfile")
pack_data = extract_sideband_pack(response)
assert pack_data != nil
assert {:ok, entries} = Reader.parse(pack_data)
assert length(entries) == 600
end
end
describe "real git client validation" do
@tag :tmp_dir
test "response can be cloned by real git", %{tmp_dir: tmp_dir} do
# Use a memory repo — the git verify-pack validation works on the pack binary
repo = RepoHelper.memory_repo("git-clone-test")
ExGitObjectstore.init(repo)
{commit_sha, _, _} = create_commit(repo, "hello from test\n", "initial commit\n")
:ok = Ref.put(repo, "refs/heads/main", commit_sha, nil)
# Generate the v2 capability advertisement
{advert, state} = UploadPackV2.init(repo)
# Simulate ls-refs command
ls_refs_data =
PktLine.encode("command=ls-refs") <>
PktLine.delim() <>
PktLine.encode("peel") <>
PktLine.encode("symrefs") <>
PktLine.encode("ref-prefix refs/heads/") <>
PktLine.flush()
{ls_refs_response, state} = UploadPackV2.feed(state, ls_refs_data)
{:ok, packets, ""} = PktLine.decode(ls_refs_response)
data_lines = for {:data, d} <- packets, do: d
assert Enum.any?(data_lines, &String.contains?(&1, commit_sha))
# Step 2: fetch
fetch =
IO.iodata_to_binary([
PktLine.encode("command=fetch"),
PktLine.delim(),
PktLine.encode("want #{commit_sha}"),
PktLine.encode("done"),
# Simulate fetch command — new state (HTTP is stateless per-request)
{_advert2, state2} = UploadPackV2.init(repo)
fetch_data =
PktLine.encode("command=fetch") <>
PktLine.delim() <>
PktLine.encode("want #{commit_sha}") <>
PktLine.encode("done") <>
PktLine.flush()
])
{fetch_response, state} = UploadPackV2.feed(state, fetch)
assert state.phase == :done
assert byte_size(fetch_response) > 0
{fetch_response, _state} = UploadPackV2.feed(state2, fetch_data)
# Write the pack data to a file and verify git can read it
# Verify packfile in response
{:ok, fetch_packets, _} = PktLine.decode(fetch_response)
data_lines = for {:data, d} <- fetch_packets, do: d
assert "packfile" in data_lines
pack_data = extract_sideband_pack(fetch_response)
assert pack_data != nil
pack_path = Path.join(tmp_dir, "test.pack")
File.write!(pack_path, pack_data)
# Verify git can parse the pack by indexing it
{output, exit_code} = System.cmd("git", ["index-pack", pack_path], stderr_to_stdout: true)
assert exit_code == 0, "git index-pack failed: #{output}"
# Now verify-pack should work with the generated idx
{output2, exit_code2} =
System.cmd("git", ["verify-pack", "-v", pack_path], stderr_to_stdout: true)
# Extract and verify pack data
pack_data = extract_pack_data(fetch_response)
{:ok, objects} = Reader.parse(pack_data)
assert exit_code2 == 0, "git verify-pack failed: #{output2}"
assert String.contains?(output2, commit_sha)
types = Enum.map(objects, fn obj -> obj.type end)
assert :commit in types
assert :tree in types
assert :blob in types
end
end
# Extract raw pack data from sideband-encoded response
defp extract_pack_data(response) do
{:ok, packets, _} = PktLine.decode(response)
# --- Helpers ---
# Extract pack data from v2 fetch response sideband encoding.
# The response structure is:
# [acknowledgments section (optional)]
# "packfile\n" pkt-line
# sideband data (band=1 = pack data, band=2 = progress)
# 0000 flush
defp extract_sideband_pack(response) do
# Find the "packfile" marker and extract sideband data after it
case find_packfile_section(response) do
# Find the packfile marker, then collect sideband-1 data
{_, after_packfile} =
nil -> nil
sideband_start -> extract_sideband_loop(sideband_start, <<>>)
end
end
Enum.split_while(packets, fn
{:data, "packfile"} -> false
_ -> true
end)
after_packfile
|> tl()
|> Enum.flat_map(fn
{:data, data} ->
case PktLine.decode_sideband(data) do
{:pack, pack} -> [pack]
_ -> []
defp find_packfile_section(data) do
# Scan pkt-lines until we find one containing "packfile"
find_packfile_loop(data)
end
defp find_packfile_loop(<<>>), do: nil
defp find_packfile_loop(<<"0000", _::binary>>), do: nil
defp find_packfile_loop(<<"0001", rest::binary>>), do: find_packfile_loop(rest)
defp find_packfile_loop(<<hex::binary-size(4), rest::binary>>) do
case Integer.parse(hex, 16) do
{len, ""} when len >= 5 ->
payload_len = len - 4
if byte_size(rest) >= payload_len do
<<payload::binary-size(payload_len), remaining::binary>> = rest
if String.contains?(payload, "packfile") do
remaining
else
find_packfile_loop(remaining)
end
else
nil
end
_ ->
nil
end
end
defp find_packfile_loop(_), do: nil
[]
defp extract_sideband_loop(<<>>, acc), do: if(acc == <<>>, do: nil, else: acc)
defp extract_sideband_loop(<<"0000", _::binary>>, acc) do
if acc == <<>>, do: nil, else: acc
end)
|> IO.iodata_to_binary()
end
defp extract_sideband_loop(<<hex::binary-size(4), rest::binary>>, acc) do
case Integer.parse(hex, 16) do
{len, ""} when len >= 5 ->
payload_len = len - 4
if byte_size(rest) >= payload_len do
<<payload::binary-size(payload_len), rest_pkt::binary>> = rest
case payload do
<<1, pack_chunk::binary>> ->
extract_sideband_loop(rest_pkt, acc <> pack_chunk)
_ ->
extract_sideband_loop(rest_pkt, acc)
end
else
if acc == <<>>, do: nil, else: acc
end
_ ->
if acc == <<>>, do: nil, else: acc
end
end
defp extract_sideband_loop(_, acc), do: if(acc == <<>>, do: nil, else: acc)
end