From e2c6ae3333a3facd13aa52e1986a2ba2dbc56a9d Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 17 Nov 2023 15:28:25 -0500 Subject: plumbing: handle pktline erro-line as errors Error when encountering an error-line See https://git-scm.com/docs/pack-protocol Update plumbing/format/pktline/error.go Co-authored-by: Paulo Gomes Update plumbing/format/pktline/error.go Co-authored-by: Paulo Gomes feat: format newline --- plumbing/format/pktline/error.go | 51 ++++++++++++++++++++++++++ plumbing/format/pktline/error_test.go | 68 +++++++++++++++++++++++++++++++++++ plumbing/format/pktline/scanner.go | 9 +++++ 3 files changed, 128 insertions(+) create mode 100644 plumbing/format/pktline/error.go create mode 100644 plumbing/format/pktline/error_test.go diff --git a/plumbing/format/pktline/error.go b/plumbing/format/pktline/error.go new file mode 100644 index 0000000..2c0e5a7 --- /dev/null +++ b/plumbing/format/pktline/error.go @@ -0,0 +1,51 @@ +package pktline + +import ( + "bytes" + "errors" + "io" + "strings" +) + +var ( + // ErrInvalidErrorLine is returned by Decode when the packet line is not an + // error line. + ErrInvalidErrorLine = errors.New("expected an error-line") + + errPrefix = []byte("ERR ") +) + +// ErrorLine is a packet line that contains an error message. +// Once this packet is sent by client or server, the data transfer process is +// terminated. +// See https://git-scm.com/docs/pack-protocol#_pkt_line_format +type ErrorLine struct { + Text string +} + +// Error implements the error interface. +func (e *ErrorLine) Error() string { + return e.Text +} + +// Encode encodes the ErrorLine into a packet line. +func (e *ErrorLine) Encode(w io.Writer) error { + p := NewEncoder(w) + return p.Encodef("%s%s\n", string(errPrefix), e.Text) +} + +// Decode decodes a packet line into an ErrorLine. +func (e *ErrorLine) Decode(r io.Reader) error { + s := NewScanner(r) + if !s.Scan() { + return s.Err() + } + + line := s.Bytes() + if !bytes.HasPrefix(line, errPrefix) { + return ErrInvalidErrorLine + } + + e.Text = strings.TrimSpace(string(line[4:])) + return nil +} diff --git a/plumbing/format/pktline/error_test.go b/plumbing/format/pktline/error_test.go new file mode 100644 index 0000000..3cffd20 --- /dev/null +++ b/plumbing/format/pktline/error_test.go @@ -0,0 +1,68 @@ +package pktline + +import ( + "bytes" + "errors" + "io" + "testing" +) + +func TestEncodeEmptyErrorLine(t *testing.T) { + e := &ErrorLine{} + err := e.Encode(io.Discard) + if err != nil { + t.Fatal(err) + } +} + +func TestEncodeErrorLine(t *testing.T) { + e := &ErrorLine{ + Text: "something", + } + var buf bytes.Buffer + err := e.Encode(&buf) + if err != nil { + t.Fatal(err) + } + if buf.String() != "0012ERR something\n" { + t.Fatalf("unexpected encoded error line: %q", buf.String()) + } +} + +func TestDecodeEmptyErrorLine(t *testing.T) { + var buf bytes.Buffer + e := &ErrorLine{} + err := e.Decode(&buf) + if err != nil { + t.Fatal(err) + } + if e.Text != "" { + t.Fatalf("unexpected error line: %q", e.Text) + } +} + +func TestDecodeErrorLine(t *testing.T) { + var buf bytes.Buffer + buf.WriteString("000eERR foobar") + var e *ErrorLine + err := e.Decode(&buf) + if !errors.As(err, &e) { + t.Fatalf("expected error line, got: %T: %v", err, err) + } + if e.Text != "foobar" { + t.Fatalf("unexpected error line: %q", e.Text) + } +} + +func TestDecodeErrorLineLn(t *testing.T) { + var buf bytes.Buffer + buf.WriteString("000fERR foobar\n") + var e *ErrorLine + err := e.Decode(&buf) + if !errors.As(err, &e) { + t.Fatalf("expected error line, got: %T: %v", err, err) + } + if e.Text != "foobar" { + t.Fatalf("unexpected error line: %q", e.Text) + } +} diff --git a/plumbing/format/pktline/scanner.go b/plumbing/format/pktline/scanner.go index 5e85ed0..fbb137d 100644 --- a/plumbing/format/pktline/scanner.go +++ b/plumbing/format/pktline/scanner.go @@ -1,8 +1,10 @@ package pktline import ( + "bytes" "errors" "io" + "strings" "github.com/go-git/go-git/v5/utils/trace" ) @@ -69,6 +71,13 @@ func (s *Scanner) Scan() bool { s.payload = s.payload[:l] trace.Packet.Printf("packet: < %04x %s", l, s.payload) + if bytes.HasPrefix(s.payload, errPrefix) { + s.err = &ErrorLine{ + Text: strings.TrimSpace(string(s.payload[4:])), + } + return false + } + return true } -- cgit