diff options
author | Santiago M. Mola <santi@mola.io> | 2016-12-06 15:49:01 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-12-06 15:49:01 +0100 |
commit | 4429dffca6f4185d09a22725baa728b535c009a7 (patch) | |
tree | 416ef14e79ee1d9cd9bfde30c0ceff7d8bbaa4e4 /plumbing/protocol | |
parent | 22fe81f342538ae51442a72356036768f7f1a2f9 (diff) | |
download | go-git-4429dffca6f4185d09a22725baa728b535c009a7.tar.gz |
protocol/packp: add report status message. (#162)
Diffstat (limited to 'plumbing/protocol')
-rw-r--r-- | plumbing/protocol/packp/advrefs_decode.go | 4 | ||||
-rw-r--r-- | plumbing/protocol/packp/common.go | 4 | ||||
-rw-r--r-- | plumbing/protocol/packp/report_status.go | 145 | ||||
-rw-r--r-- | plumbing/protocol/packp/report_status_test.go | 253 |
4 files changed, 402 insertions, 4 deletions
diff --git a/plumbing/protocol/packp/advrefs_decode.go b/plumbing/protocol/packp/advrefs_decode.go index 964e3eb..c42c589 100644 --- a/plumbing/protocol/packp/advrefs_decode.go +++ b/plumbing/protocol/packp/advrefs_decode.go @@ -119,10 +119,6 @@ func isPrefix(payload []byte) bool { return len(payload) > 0 && payload[0] == '#' } -func isFlush(payload []byte) bool { - return len(payload) == 0 -} - // If the first hash is zero, then a no-refs is comming. Otherwise, a // list-of-refs is comming, and the hash will be followed by the first // advertised ref. diff --git a/plumbing/protocol/packp/common.go b/plumbing/protocol/packp/common.go index 2328eda..93dfaed 100644 --- a/plumbing/protocol/packp/common.go +++ b/plumbing/protocol/packp/common.go @@ -40,3 +40,7 @@ var ( // updreq shallowNoSp = []byte("shallow") ) + +func isFlush(payload []byte) bool { + return len(payload) == 0 +} diff --git a/plumbing/protocol/packp/report_status.go b/plumbing/protocol/packp/report_status.go new file mode 100644 index 0000000..f480b34 --- /dev/null +++ b/plumbing/protocol/packp/report_status.go @@ -0,0 +1,145 @@ +package packp + +import ( + "io" + + "fmt" + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/format/pktline" + "strings" +) + +const ( + ok = "ok" +) + +// ReportStatus is a report status message, as used in the git-receive-pack +// process whenever the 'report-status' capability is negotiated. +type ReportStatus struct { + UnpackStatus string + CommandStatuses []*CommandStatus +} + +// NewReportStatus creates a new ReportStatus message. +func NewReportStatus() *ReportStatus { + return &ReportStatus{} +} + +// Ok returns true if the report status reported no error. +func (s *ReportStatus) Ok() bool { + return s.UnpackStatus == ok +} + +// Encode writes the report status to a writer. +func (s *ReportStatus) Encode(w io.Writer) error { + e := pktline.NewEncoder(w) + if err := e.Encodef("unpack %s", s.UnpackStatus); err != nil { + return err + } + + for _, cs := range s.CommandStatuses { + if err := cs.encode(w); err != nil { + return err + } + } + + return e.Flush() +} + +// Decode reads from the given reader and decodes a report-status message. It +// does not read more input than what is needed to fill the report status. +func (s *ReportStatus) Decode(r io.Reader) error { + scan := pktline.NewScanner(r) + if err := s.scanFirstLine(scan); err != nil { + return err + } + + if err := s.decodeReportStatus(scan.Bytes()); err != nil { + return err + } + + flushed := false + for scan.Scan() { + b := scan.Bytes() + if isFlush(b) { + flushed = true + break + } + + if err := s.decodeCommandStatus(b); err != nil { + return err + } + } + + if !flushed { + return fmt.Errorf("missing flush") + } + + return scan.Err() +} + +func (s *ReportStatus) scanFirstLine(scan *pktline.Scanner) error { + if scan.Scan() { + return nil + } + + if scan.Err() != nil { + return scan.Err() + } + + return io.ErrUnexpectedEOF +} + +func (s *ReportStatus) decodeReportStatus(b []byte) error { + if isFlush(b) { + return fmt.Errorf("premature flush") + } + + line := string(b) + fields := strings.SplitN(line, " ", 2) + if len(fields) != 2 || fields[0] != "unpack" { + return fmt.Errorf("malformed unpack status: %s", line) + } + + s.UnpackStatus = fields[1] + return nil +} + +func (s *ReportStatus) decodeCommandStatus(b []byte) error { + line := string(b) + fields := strings.SplitN(line, " ", 3) + status := ok + if len(fields) == 3 && fields[0] == "ng" { + status = fields[2] + } else if len(fields) != 2 || fields[0] != "ok" { + return fmt.Errorf("malformed command status: %s", line) + } + + cs := &CommandStatus{ + ReferenceName: plumbing.ReferenceName(fields[1]), + Status: status, + } + s.CommandStatuses = append(s.CommandStatuses, cs) + return nil +} + +// CommandStatus is the status of a reference in a report status. +// See ReportStatus struct. +type CommandStatus struct { + ReferenceName plumbing.ReferenceName + Status string +} + +// Ok returns true if the command status reported no error. +func (s *CommandStatus) Ok() bool { + return s.Status == ok +} + +func (s *CommandStatus) encode(w io.Writer) error { + e := pktline.NewEncoder(w) + if s.Ok() { + return e.Encodef("ok %s", s.ReferenceName.String()) + } + + return e.Encodef("ng %s %s", s.ReferenceName.String(), s.Status) +} diff --git a/plumbing/protocol/packp/report_status_test.go b/plumbing/protocol/packp/report_status_test.go new file mode 100644 index 0000000..064e514 --- /dev/null +++ b/plumbing/protocol/packp/report_status_test.go @@ -0,0 +1,253 @@ +package packp + +import ( + "bytes" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/format/pktline" + + . "gopkg.in/check.v1" +) + +type ReportStatusSuite struct{} + +var _ = Suite(&ReportStatusSuite{}) + +func (s *ReportStatusSuite) TestOk(c *C) { + rs := NewReportStatus() + rs.UnpackStatus = "ok" + c.Assert(rs.Ok(), Equals, true) + rs.UnpackStatus = "OK" + c.Assert(rs.Ok(), Equals, false) + rs.UnpackStatus = "" + c.Assert(rs.Ok(), Equals, false) + + cs := &CommandStatus{} + cs.Status = "ok" + c.Assert(cs.Ok(), Equals, true) + cs.Status = "OK" + c.Assert(cs.Ok(), Equals, false) + cs.Status = "" + c.Assert(cs.Ok(), Equals, false) +} + +func (s *ReportStatusSuite) testEncodeDecodeOk(c *C, rs *ReportStatus, lines ...string) { + s.testDecodeOk(c, rs, lines...) + s.testEncodeOk(c, rs, lines...) +} + +func (s *ReportStatusSuite) testDecodeOk(c *C, expected *ReportStatus, lines ...string) { + r := toPktLines(c, lines) + rs := NewReportStatus() + c.Assert(rs.Decode(r), IsNil) + c.Assert(rs, DeepEquals, expected) +} + +func (s *ReportStatusSuite) testDecodeError(c *C, errorMatch string, lines ...string) { + r := toPktLines(c, lines) + rs := NewReportStatus() + c.Assert(rs.Decode(r), ErrorMatches, errorMatch) +} + +func (s *ReportStatusSuite) testEncodeOk(c *C, input *ReportStatus, lines ...string) { + expected := pktlines(c, lines...) + var buf bytes.Buffer + c.Assert(input.Encode(&buf), IsNil) + obtained := buf.Bytes() + + comment := Commentf("\nobtained = %s\nexpected = %s\n", string(obtained), string(expected)) + + c.Assert(obtained, DeepEquals, expected, comment) +} + +func (s *ReportStatusSuite) TestEncodeDecodeOkOneReference(c *C) { + rs := NewReportStatus() + rs.UnpackStatus = "ok" + rs.CommandStatuses = []*CommandStatus{{ + ReferenceName: plumbing.ReferenceName("refs/heads/master"), + Status: "ok", + }} + + s.testEncodeDecodeOk(c, rs, + "unpack ok", + "ok refs/heads/master", + pktline.FlushString, + ) +} + +func (s *ReportStatusSuite) TestEncodeDecodeOkOneReferenceFailed(c *C) { + rs := NewReportStatus() + rs.UnpackStatus = "my error" + rs.CommandStatuses = []*CommandStatus{{ + ReferenceName: plumbing.ReferenceName("refs/heads/master"), + Status: "command error", + }} + + s.testEncodeDecodeOk(c, rs, + "unpack my error", + "ng refs/heads/master command error", + pktline.FlushString, + ) +} + +func (s *ReportStatusSuite) TestEncodeDecodeOkMoreReferences(c *C) { + rs := NewReportStatus() + rs.UnpackStatus = "ok" + rs.CommandStatuses = []*CommandStatus{{ + ReferenceName: plumbing.ReferenceName("refs/heads/master"), + Status: "ok", + }, { + ReferenceName: plumbing.ReferenceName("refs/heads/a"), + Status: "ok", + }, { + ReferenceName: plumbing.ReferenceName("refs/heads/b"), + Status: "ok", + }} + + s.testEncodeDecodeOk(c, rs, + "unpack ok", + "ok refs/heads/master", + "ok refs/heads/a", + "ok refs/heads/b", + pktline.FlushString, + ) +} + +func (s *ReportStatusSuite) TestEncodeDecodeOkMoreReferencesFailed(c *C) { + rs := NewReportStatus() + rs.UnpackStatus = "my error" + rs.CommandStatuses = []*CommandStatus{{ + ReferenceName: plumbing.ReferenceName("refs/heads/master"), + Status: "ok", + }, { + ReferenceName: plumbing.ReferenceName("refs/heads/a"), + Status: "command error", + }, { + ReferenceName: plumbing.ReferenceName("refs/heads/b"), + Status: "ok", + }} + + s.testEncodeDecodeOk(c, rs, + "unpack my error", + "ok refs/heads/master", + "ng refs/heads/a command error", + "ok refs/heads/b", + pktline.FlushString, + ) +} + +func (s *ReportStatusSuite) TestEncodeDecodeOkNoReferences(c *C) { + expected := NewReportStatus() + expected.UnpackStatus = "ok" + + s.testEncodeDecodeOk(c, expected, + "unpack ok", + pktline.FlushString, + ) +} + +func (s *ReportStatusSuite) TestEncodeDecodeOkNoReferencesFailed(c *C) { + rs := NewReportStatus() + rs.UnpackStatus = "my error" + + s.testEncodeDecodeOk(c, rs, + "unpack my error", + pktline.FlushString, + ) +} + +func (s *ReportStatusSuite) TestDecodeErrorOneReferenceNoFlush(c *C) { + expected := NewReportStatus() + expected.UnpackStatus = "ok" + expected.CommandStatuses = []*CommandStatus{{ + ReferenceName: plumbing.ReferenceName("refs/heads/master"), + Status: "ok", + }} + + s.testDecodeError(c, "missing flush", + "unpack ok", + "ok refs/heads/master", + ) +} + +func (s *ReportStatusSuite) TestDecodeErrorEmpty(c *C) { + expected := NewReportStatus() + expected.UnpackStatus = "ok" + expected.CommandStatuses = []*CommandStatus{{ + ReferenceName: plumbing.ReferenceName("refs/heads/master"), + Status: "ok", + }} + + s.testDecodeError(c, "unexpected EOF") +} + +func (s *ReportStatusSuite) TestDecodeErrorMalformed(c *C) { + expected := NewReportStatus() + expected.UnpackStatus = "ok" + expected.CommandStatuses = []*CommandStatus{{ + ReferenceName: plumbing.ReferenceName("refs/heads/master"), + Status: "ok", + }} + + s.testDecodeError(c, "malformed unpack status: unpackok", + "unpackok", + pktline.FlushString, + ) +} + +func (s *ReportStatusSuite) TestDecodeErrorMalformed2(c *C) { + expected := NewReportStatus() + expected.UnpackStatus = "ok" + expected.CommandStatuses = []*CommandStatus{{ + ReferenceName: plumbing.ReferenceName("refs/heads/master"), + Status: "ok", + }} + + s.testDecodeError(c, "malformed unpack status: UNPACK OK", + "UNPACK OK", + pktline.FlushString, + ) +} + +func (s *ReportStatusSuite) TestDecodeErrorMalformedCommandStatus(c *C) { + expected := NewReportStatus() + expected.UnpackStatus = "ok" + expected.CommandStatuses = []*CommandStatus{{ + ReferenceName: plumbing.ReferenceName("refs/heads/master"), + Status: "ok", + }} + + s.testDecodeError(c, "malformed command status: ko refs/heads/master", + "unpack ok", + "ko refs/heads/master", + pktline.FlushString, + ) +} + +func (s *ReportStatusSuite) TestDecodeErrorMalformedCommandStatus2(c *C) { + expected := NewReportStatus() + expected.UnpackStatus = "ok" + expected.CommandStatuses = []*CommandStatus{{ + ReferenceName: plumbing.ReferenceName("refs/heads/master"), + Status: "ok", + }} + + s.testDecodeError(c, "malformed command status: ng refs/heads/master", + "unpack ok", + "ng refs/heads/master", + pktline.FlushString, + ) +} + +func (s *ReportStatusSuite) TestDecodeErrorPrematureFlush(c *C) { + expected := NewReportStatus() + expected.UnpackStatus = "ok" + expected.CommandStatuses = []*CommandStatus{{ + ReferenceName: plumbing.ReferenceName("refs/heads/master"), + Status: "ok", + }} + + s.testDecodeError(c, "premature flush", + pktline.FlushString, + ) +} |