Merge pull request #14 from entireio/pkt-err · Entire
Merge pull request #14 from entireio/pkt-err
c10ec48→main
Improve git protocol parsing and error handling
Changes
8
- MREADME.md+1/-1
- internal/gitproto
- Mfetch.go+62/-2
- Mfetch_test.go+244
- Mpktline.go+4/-1
- Mpktline_test.go+44
- Mrefs.go+3
- Msmarthttp.go+22
- Msmarthttp_test.go+115
100 unmodified lines
101
102
103
104
104
105
106
107
100 unmodified lines
- Optional machine-readable output with `--json`
- Optional source-side Git protocol v2 for `ls-refs` and `fetch`
## Limits
## Limitations
- Push still uses the existing v1-style `receive-pack` path.
- Protocol v2 support currently covers source discovery and source fetch only.
MREADME.md+1/-1
249 unmodified lines
func storeV2FetchPack(store storer.Storer, r io.Reader, verbose bool) error {
reader := NewPacketReader(r)
expectPackfile := false
for {
kind, payload, err := reader.ReadPacket()
if err != nil {
if errors.Is(err, io.EOF) {
if expectPackfile {
return errors.New("expected packfile to be sent after 'ready'")
}
return nil
}
return fmt.Errorf("decode protocol v2 fetch response: %w", err)
}
switch kind {
case PacketFlush:
if expectPackfile {
return errors.New("expected packfile to be sent after 'ready'")
}
return nil
case PacketDelim, PacketResponseEnd:
continue
case PacketData:
line := string(payload)
if strings.HasPrefix(line, "ERR ") {
return errors.New("remote: " + line[4:])
}
switch line {
case "packfile\n":
demux := sideband.NewDemuxer(sideband.Sideband64k, reader.BufReader())
return fmt.Errorf("update object storage: %w", err)
}
return nil
case "acknowledgments\n", "shallow-info\n":
expectPackfile = true
if err := SkipSection(reader); err != nil {
return err
}
}
continue
}
}
MREADME.md+1/-1
249 unmodified lines
func TestDecodeV2LSRefsSkipsUnbornLines(t *testing.T) {
wire := "" +
FormatPktLine("unborn HEAD symref-target:refs/heads/main\n") +
FormatPktLine("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/main\n") +
"0000"
refs, head, err := decodeV2LSRefs(bytes.NewReader([]byte(wire)))
if err != nil {
t.Fatalf("decodeV2LSRefs: %v", err)
}
if len(refs) != 1 {
t.Fatalf("expected 1 ref, got %d", len(refs))
}
if refs[0].Name().String() != refsHeadsMain {
t.Errorf("refs[0].Name() = %q, want refs/heads/main", refs[0].Name())
}
if refs[0].Hash().IsZero() {
t.Fatal("unborn line was decoded as a zero-hash ref")
}
}
func TestDecodeV2LSRefsMalformed(t *testing.T) {
wire := FormatPktLine("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n") + "0000"
}
func TestStoreV2FetchPackReturnsRemoteError(t *testing.T) {
var wire bytes.Buffer
if _, err := pktline.WriteString(&wire, "ERR upload-pack: not our ref"); err != nil {
t.Fatalf("write remote error: %v", err)
}
err := storeV2FetchPack(memory.NewStorage(), &wire, false)
if err == nil {
t.Fatal("expected remote error")
}
if got, want := err.Error(), "remote: upload-pack: not our ref"; got != want {
t.Fatalf("error = %q, want %q", got, want)
}
}
func TestOpenV2PackStreamReturnsRemoteError(t *testing.T) {
var wire bytes.Buffer
if _, err := pktline.WriteString(&wire, "ERR upload-pack: not our ref"); err != nil {
t.Fatalf("write remote error: %v", err)
}
_, err := openV2PackStream(io.NopCloser(&wire), false)
if err == nil {
t.Fatal("expected remote error")
}
if got, want := err.Error(), "remote: upload-pack: not our ref"; got != want {
t.Fatalf("error = %q, want %q", got, want)
}
}
func TestFetchPackV2ManyHavesSendsDoneAndReadsPack(t *testing.T) {
const negotiationBoundary = 16
const refCount = negotiationBoundary + 1
ep, err := transport.ParseURL("https://example.com/repo.git")
if err != nil {
t.Fatalf("parse endpoint: %v", err)
}
seenRequest := false
conn := NewConn(ep, "source", nil, roundTripperFunc(func(req *http.Request) (*http.Response, error) {
seenRequest = true
body, err := io.ReadAll(req.Body)
if err != nil {
t.Fatalf("read request body: %v", err)
}
lines := readV2FetchRequestLines(t, body)
if got := countLinesWithPrefix(lines, "want "); got != refCount {
t.Fatalf("want lines = %d, want %d; lines=%q", got, refCount, lines)
}
if got := countLinesWithPrefix(lines, "have "); got != refCount {
t.Fatalf("have lines = %d, want %d; lines=%q", got, refCount, lines)
}
if !containsLine(lines, "done") {
t.Fatalf("fetch request did not send done; lines=%q", lines)
}
var wire bytes.Buffer
if _, err := pktline.WriteString(&wire, "packfile\n"); err != nil {
t.Fatalf("write packfile header: %v", err)
}
if _, err := pktline.Write(&wire, append([]byte{1}, []byte("PACK")...)); err != nil {
t.Fatalf("write sideband packet: %v", err)
}
if err := pktline.WriteFlush(&wire); err != nil {
t.Fatalf("write flush: %v", err)
}
return &http.Response{
StatusCode: http.StatusOK,
Request: req,
Body: io.NopCloser(bytes.NewReader(wire.Bytes())),
}, nil
}))
}