aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--plumbing/protocol/packp/advrefs_decode.go4
-rw-r--r--plumbing/protocol/packp/common.go4
-rw-r--r--plumbing/protocol/packp/report_status.go145
-rw-r--r--plumbing/protocol/packp/report_status_test.go253
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,
+ )
+}