aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/protocol
diff options
context:
space:
mode:
authorMáximo Cuadros <mcuadros@gmail.com>2016-12-06 15:46:09 +0100
committerGitHub <noreply@github.com>2016-12-06 15:46:09 +0100
commit22fe81f342538ae51442a72356036768f7f1a2f9 (patch)
treeccfe9fcd48d3c8f349b42413f71f26ba23a4cba9 /plumbing/protocol
parent4b5849db76905830e0124b6b9f4294ee13308e0f (diff)
downloadgo-git-22fe81f342538ae51442a72356036768f7f1a2f9.tar.gz
protocol/packp: UploadPackResponse implementation (#161)
* plumbing/protocol: paktp avoid duplication of haves, wants and shallow * protocol/pakp: UploadPackResponse implementation * changes * changes * changes * debug * changes
Diffstat (limited to 'plumbing/protocol')
-rw-r--r--plumbing/protocol/packp/common.go11
-rw-r--r--plumbing/protocol/packp/shallowupd.go73
-rw-r--r--plumbing/protocol/packp/shallowupd_test.go63
-rw-r--r--plumbing/protocol/packp/srvresp.go70
-rw-r--r--plumbing/protocol/packp/srvresp_test.go48
-rw-r--r--plumbing/protocol/packp/ulreq.go13
-rw-r--r--plumbing/protocol/packp/ulreq_encode_test.go2
-rw-r--r--plumbing/protocol/packp/uppackreq.go14
-rw-r--r--plumbing/protocol/packp/uppackreq_test.go4
-rw-r--r--plumbing/protocol/packp/uppackresp.go77
-rw-r--r--plumbing/protocol/packp/uppackresp_test.go82
11 files changed, 446 insertions, 11 deletions
diff --git a/plumbing/protocol/packp/common.go b/plumbing/protocol/packp/common.go
index c8db931..2328eda 100644
--- a/plumbing/protocol/packp/common.go
+++ b/plumbing/protocol/packp/common.go
@@ -17,12 +17,12 @@ var (
eol = []byte("\n")
eq = []byte{'='}
- // advrefs
+ // advertised-refs
null = []byte("\x00")
peeled = []byte("^{}")
noHeadMark = []byte(" capabilities^{}\x00")
- // ulreq
+ // upload-request
want = []byte("want ")
shallow = []byte("shallow ")
deepen = []byte("deepen")
@@ -30,6 +30,13 @@ var (
deepenSince = []byte("deepen-since ")
deepenReference = []byte("deepen-not ")
+ // shallow-update
+ unshallow = []byte("unshallow ")
+
+ // server-response
+ ack = []byte("ACK")
+ nak = []byte("NAK")
+
// updreq
shallowNoSp = []byte("shallow")
)
diff --git a/plumbing/protocol/packp/shallowupd.go b/plumbing/protocol/packp/shallowupd.go
new file mode 100644
index 0000000..89063de
--- /dev/null
+++ b/plumbing/protocol/packp/shallowupd.go
@@ -0,0 +1,73 @@
+package packp
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+
+ "gopkg.in/src-d/go-git.v4/plumbing"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/pktline"
+)
+
+const (
+ shallowLineLen = 48
+ unshallowLineLen = 50
+)
+
+type ShallowUpdate struct {
+ Shallows []plumbing.Hash
+ Unshallows []plumbing.Hash
+}
+
+func (r *ShallowUpdate) Decode(reader io.Reader) error {
+ s := pktline.NewScanner(reader)
+
+ for s.Scan() {
+ line := s.Bytes()
+
+ var err error
+ switch {
+ case bytes.HasPrefix(line, shallow):
+ err = r.decodeShallowLine(line)
+ case bytes.HasPrefix(line, unshallow):
+ err = r.decodeUnshallowLine(line)
+ case bytes.Compare(line, pktline.Flush) == 0:
+ return nil
+ }
+
+ if err != nil {
+ return err
+ }
+ }
+
+ return s.Err()
+}
+
+func (r *ShallowUpdate) decodeShallowLine(line []byte) error {
+ hash, err := r.decodeLine(line, shallow, shallowLineLen)
+ if err != nil {
+ return err
+ }
+
+ r.Shallows = append(r.Shallows, hash)
+ return nil
+}
+
+func (r *ShallowUpdate) decodeUnshallowLine(line []byte) error {
+ hash, err := r.decodeLine(line, unshallow, unshallowLineLen)
+ if err != nil {
+ return err
+ }
+
+ r.Unshallows = append(r.Unshallows, hash)
+ return nil
+}
+
+func (r *ShallowUpdate) decodeLine(line, prefix []byte, expLen int) (plumbing.Hash, error) {
+ if len(line) != expLen {
+ return plumbing.ZeroHash, fmt.Errorf("malformed %s%q", prefix, line)
+ }
+
+ raw := string(line[expLen-40 : expLen])
+ return plumbing.NewHash(raw), nil
+}
diff --git a/plumbing/protocol/packp/shallowupd_test.go b/plumbing/protocol/packp/shallowupd_test.go
new file mode 100644
index 0000000..d64fb5d
--- /dev/null
+++ b/plumbing/protocol/packp/shallowupd_test.go
@@ -0,0 +1,63 @@
+package packp
+
+import (
+ "bytes"
+
+ "gopkg.in/src-d/go-git.v4/plumbing"
+
+ . "gopkg.in/check.v1"
+)
+
+type ShallowUpdateSuite struct{}
+
+var _ = Suite(&ShallowUpdateSuite{})
+
+func (s *ShallowUpdateSuite) TestDecode(c *C) {
+ raw := "" +
+ "0034shallow aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "0034shallow bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" +
+ "0000"
+
+ su := &ShallowUpdate{}
+ err := su.Decode(bytes.NewBufferString(raw))
+ c.Assert(err, IsNil)
+
+ plumbing.HashesSort(su.Shallows)
+
+ c.Assert(su.Unshallows, HasLen, 0)
+ c.Assert(su.Shallows, HasLen, 2)
+ c.Assert(su.Shallows, DeepEquals, []plumbing.Hash{
+ plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
+ plumbing.NewHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
+ })
+}
+
+func (s *ShallowUpdateSuite) TestDecodeUnshallow(c *C) {
+ raw := "" +
+ "0036unshallow aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "0036unshallow bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" +
+ "0000"
+
+ su := &ShallowUpdate{}
+ err := su.Decode(bytes.NewBufferString(raw))
+ c.Assert(err, IsNil)
+
+ plumbing.HashesSort(su.Unshallows)
+
+ c.Assert(su.Shallows, HasLen, 0)
+ c.Assert(su.Unshallows, HasLen, 2)
+ c.Assert(su.Unshallows, DeepEquals, []plumbing.Hash{
+ plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
+ plumbing.NewHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
+ })
+}
+
+func (s *ShallowUpdateSuite) TestDecodeMalformed(c *C) {
+ raw := "" +
+ "0035unshallow aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "0000"
+
+ su := &ShallowUpdate{}
+ err := su.Decode(bytes.NewBufferString(raw))
+ c.Assert(err, NotNil)
+}
diff --git a/plumbing/protocol/packp/srvresp.go b/plumbing/protocol/packp/srvresp.go
new file mode 100644
index 0000000..3284fa2
--- /dev/null
+++ b/plumbing/protocol/packp/srvresp.go
@@ -0,0 +1,70 @@
+package packp
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+
+ "gopkg.in/src-d/go-git.v4/plumbing"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/pktline"
+)
+
+const ackLineLen = 44
+
+// ServerResponse object acknowledgement from upload-pack service
+// TODO: implement support for multi_ack or multi_ack_detailed responses
+type ServerResponse struct {
+ ACKs []plumbing.Hash
+}
+
+// Decode decodes the response into the struct, isMultiACK should be true, if
+// the request was done with multi_ack or multi_ack_detailed capabilities
+func (r *ServerResponse) Decode(reader io.Reader, isMultiACK bool) error {
+ if isMultiACK {
+ return errors.New("multi_ack and multi_ack_detailed are not supported")
+ }
+
+ s := pktline.NewScanner(reader)
+
+ for s.Scan() {
+ line := s.Bytes()
+
+ if err := r.decodeLine(line); err != nil {
+ return err
+ }
+
+ if !isMultiACK {
+ break
+ }
+ }
+
+ return s.Err()
+}
+
+func (r *ServerResponse) decodeLine(line []byte) error {
+ if len(line) == 0 {
+ return fmt.Errorf("unexpected flush")
+ }
+
+ if bytes.Compare(line[0:3], ack) == 0 {
+ return r.decodeACKLine(line)
+ }
+
+ if bytes.Compare(line[0:3], nak) == 0 {
+ return nil
+ }
+
+ return fmt.Errorf("unexpected content %q", string(line))
+}
+
+func (r *ServerResponse) decodeACKLine(line []byte) error {
+ if len(line) < ackLineLen {
+ return fmt.Errorf("malformed ACK %q", line)
+ }
+
+ sp := bytes.Index(line, []byte(" "))
+ h := plumbing.NewHash(string(line[sp+1 : sp+41]))
+ r.ACKs = append(r.ACKs, h)
+ return nil
+}
diff --git a/plumbing/protocol/packp/srvresp_test.go b/plumbing/protocol/packp/srvresp_test.go
new file mode 100644
index 0000000..6078855
--- /dev/null
+++ b/plumbing/protocol/packp/srvresp_test.go
@@ -0,0 +1,48 @@
+package packp
+
+import (
+ "bytes"
+
+ "gopkg.in/src-d/go-git.v4/plumbing"
+
+ . "gopkg.in/check.v1"
+)
+
+type ServerResponseSuite struct{}
+
+var _ = Suite(&ServerResponseSuite{})
+
+func (s *ServerResponseSuite) TestDecodeNAK(c *C) {
+ raw := "0008NAK\n"
+
+ sr := &ServerResponse{}
+ err := sr.Decode(bytes.NewBufferString(raw), false)
+ c.Assert(err, IsNil)
+
+ c.Assert(sr.ACKs, HasLen, 0)
+}
+
+func (s *ServerResponseSuite) TestDecodeACK(c *C) {
+ raw := "0031ACK 6ecf0ef2c2dffb796033e5a02219af86ec6584e5\n"
+
+ sr := &ServerResponse{}
+ err := sr.Decode(bytes.NewBufferString(raw), false)
+ c.Assert(err, IsNil)
+
+ c.Assert(sr.ACKs, HasLen, 1)
+ c.Assert(sr.ACKs[0], Equals, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"))
+}
+
+func (s *ServerResponseSuite) TestDecodeMalformed(c *C) {
+ raw := "0029ACK 6ecf0ef2c2dffb796033e5a02219af86ec6584e\n"
+
+ sr := &ServerResponse{}
+ err := sr.Decode(bytes.NewBufferString(raw), false)
+ c.Assert(err, NotNil)
+}
+
+func (s *ServerResponseSuite) TestDecodeMultiACK(c *C) {
+ sr := &ServerResponse{}
+ err := sr.Decode(bytes.NewBuffer(nil), true)
+ c.Assert(err, NotNil)
+}
diff --git a/plumbing/protocol/packp/ulreq.go b/plumbing/protocol/packp/ulreq.go
index 254e85e..d57c3fc 100644
--- a/plumbing/protocol/packp/ulreq.go
+++ b/plumbing/protocol/packp/ulreq.go
@@ -25,6 +25,7 @@ type UploadRequest struct {
// DepthCommit, DepthSince and DepthReference.
type Depth interface {
isDepth()
+ IsZero() bool
}
// DepthCommits values stores the maximum number of requested commits in
@@ -34,16 +35,28 @@ type DepthCommits int
func (d DepthCommits) isDepth() {}
+func (d DepthCommits) IsZero() bool {
+ return d == 0
+}
+
// DepthSince values requests only commits newer than the specified time.
type DepthSince time.Time
func (d DepthSince) isDepth() {}
+func (d DepthSince) IsZero() bool {
+ return time.Time(d).IsZero()
+}
+
// DepthReference requests only commits not to found in the specified reference.
type DepthReference string
func (d DepthReference) isDepth() {}
+func (d DepthReference) IsZero() bool {
+ return string(d) == ""
+}
+
// NewUploadRequest returns a pointer to a new UploadRequest value, ready to be
// used. It has no capabilities, wants or shallows and an infinite depth. Please
// note that to encode an upload-request it has to have at least one wanted hash.
diff --git a/plumbing/protocol/packp/ulreq_encode_test.go b/plumbing/protocol/packp/ulreq_encode_test.go
index b414a37..0890678 100644
--- a/plumbing/protocol/packp/ulreq_encode_test.go
+++ b/plumbing/protocol/packp/ulreq_encode_test.go
@@ -6,9 +6,9 @@ import (
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/format/pktline"
+ "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability"
. "gopkg.in/check.v1"
- "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability"
)
type UlReqEncodeSuite struct{}
diff --git a/plumbing/protocol/packp/uppackreq.go b/plumbing/protocol/packp/uppackreq.go
index 887d27a..84e2b4e 100644
--- a/plumbing/protocol/packp/uppackreq.go
+++ b/plumbing/protocol/packp/uppackreq.go
@@ -13,15 +13,16 @@ import (
// UploadPackRequest represents a upload-pack request.
// Zero-value is not safe, use NewUploadPackRequest instead.
type UploadPackRequest struct {
- *UploadRequest
- *UploadHaves
+ UploadRequest
+ UploadHaves
}
// NewUploadPackRequest creates a new UploadPackRequest and returns a pointer.
func NewUploadPackRequest() *UploadPackRequest {
+ ur := NewUploadRequest()
return &UploadPackRequest{
- UploadHaves: &UploadHaves{},
- UploadRequest: NewUploadRequest(),
+ UploadHaves: UploadHaves{},
+ UploadRequest: *ur,
}
}
@@ -30,9 +31,10 @@ func NewUploadPackRequest() *UploadPackRequest {
// ones, based on the adv value (advertaised capabilities), the UploadPackRequest
// it has no wants, haves or shallows and an infinite depth
func NewUploadPackRequestFromCapabilities(adv *capability.List) *UploadPackRequest {
+ ur := NewUploadRequestFromCapabilities(adv)
return &UploadPackRequest{
- UploadHaves: &UploadHaves{},
- UploadRequest: NewUploadRequestFromCapabilities(adv),
+ UploadHaves: UploadHaves{},
+ UploadRequest: *ur,
}
}
diff --git a/plumbing/protocol/packp/uppackreq_test.go b/plumbing/protocol/packp/uppackreq_test.go
index e551f45..273f916 100644
--- a/plumbing/protocol/packp/uppackreq_test.go
+++ b/plumbing/protocol/packp/uppackreq_test.go
@@ -1,11 +1,11 @@
package packp
import (
+ "bytes"
+
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability"
- "bytes"
-
. "gopkg.in/check.v1"
)
diff --git a/plumbing/protocol/packp/uppackresp.go b/plumbing/protocol/packp/uppackresp.go
new file mode 100644
index 0000000..a117956
--- /dev/null
+++ b/plumbing/protocol/packp/uppackresp.go
@@ -0,0 +1,77 @@
+package packp
+
+import (
+ "errors"
+ "io"
+
+ "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability"
+)
+
+// ErrUploadPackResponseNotDecoded is returned if Read is called without
+// decoding first
+var ErrUploadPackResponseNotDecoded = errors.New("upload-pack-response should be decoded")
+
+// UploadPackResponse contains all the information responded by the upload-pack
+// service, the response implements io.ReadCloser that allows to read the
+// packfile directly from it.
+type UploadPackResponse struct {
+ ShallowUpdate
+ ServerResponse
+ r io.ReadCloser
+
+ isShallow bool
+ isMultiACK bool
+ isOk bool
+}
+
+// NewUploadPackResponse create a new UploadPackResponse instance, the request
+// being responded by the response is required.
+func NewUploadPackResponse(req *UploadPackRequest) *UploadPackResponse {
+ isShallow := !req.Depth.IsZero()
+ isMultiACK := req.Capabilities.Supports(capability.MultiACK) ||
+ req.Capabilities.Supports(capability.MultiACKDetailed)
+
+ return &UploadPackResponse{
+ isShallow: isShallow,
+ isMultiACK: isMultiACK,
+ }
+}
+
+// Decode decodes all the responses sent by upload-pack service into the struct
+// and prepares it to read the packfile using the Read method
+func (r *UploadPackResponse) Decode(reader io.ReadCloser) error {
+ if r.isShallow {
+ if err := r.ShallowUpdate.Decode(reader); err != nil {
+ return err
+ }
+ }
+
+ if err := r.ServerResponse.Decode(reader, r.isMultiACK); err != nil {
+ return err
+ }
+
+ // now the reader is ready to read the packfile content
+ r.r = reader
+
+ return nil
+}
+
+// Read reads the packfile data, if the request was done with any Sideband
+// capability the content read should be demultiplexed. If the methods wasn't
+// called before the ErrUploadPackResponseNotDecoded will be return
+func (r *UploadPackResponse) Read(p []byte) (int, error) {
+ if r.r == nil {
+ return 0, ErrUploadPackResponseNotDecoded
+ }
+
+ return r.r.Read(p)
+}
+
+// Close the underlying reader, if any
+func (r *UploadPackResponse) Close() error {
+ if r.r == nil {
+ return nil
+ }
+
+ return r.r.Close()
+}
diff --git a/plumbing/protocol/packp/uppackresp_test.go b/plumbing/protocol/packp/uppackresp_test.go
new file mode 100644
index 0000000..c81bb76
--- /dev/null
+++ b/plumbing/protocol/packp/uppackresp_test.go
@@ -0,0 +1,82 @@
+package packp
+
+import (
+ "bytes"
+ "io/ioutil"
+
+ "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability"
+
+ . "gopkg.in/check.v1"
+)
+
+type UploadPackResponseSuite struct{}
+
+var _ = Suite(&UploadPackResponseSuite{})
+
+func (s *UploadPackResponseSuite) TestDecodeNAK(c *C) {
+ raw := "0008NAK\n[PACK]"
+
+ req := NewUploadPackRequest()
+ res := NewUploadPackResponse(req)
+ defer res.Close()
+
+ err := res.Decode(ioutil.NopCloser(bytes.NewBufferString(raw)))
+ c.Assert(err, IsNil)
+
+ pack, err := ioutil.ReadAll(res)
+ c.Assert(err, IsNil)
+ c.Assert(pack, DeepEquals, []byte("[PACK]"))
+}
+
+func (s *UploadPackResponseSuite) TestDecodeDepth(c *C) {
+ raw := "00000008NAK\n[PACK]"
+
+ req := NewUploadPackRequest()
+ req.Depth = DepthCommits(1)
+
+ res := NewUploadPackResponse(req)
+ defer res.Close()
+
+ err := res.Decode(ioutil.NopCloser(bytes.NewBufferString(raw)))
+ c.Assert(err, IsNil)
+
+ pack, err := ioutil.ReadAll(res)
+ c.Assert(err, IsNil)
+ c.Assert(pack, DeepEquals, []byte("[PACK]"))
+}
+
+func (s *UploadPackResponseSuite) TestDecodeMalformed(c *C) {
+ raw := "00000008ACK\n[PACK]"
+
+ req := NewUploadPackRequest()
+ req.Depth = DepthCommits(1)
+
+ res := NewUploadPackResponse(req)
+ defer res.Close()
+
+ err := res.Decode(ioutil.NopCloser(bytes.NewBufferString(raw)))
+ c.Assert(err, NotNil)
+}
+
+func (s *UploadPackResponseSuite) TestDecodeMultiACK(c *C) {
+ req := NewUploadPackRequest()
+ req.Capabilities.Set(capability.MultiACK)
+
+ res := NewUploadPackResponse(req)
+ defer res.Close()
+
+ err := res.Decode(ioutil.NopCloser(bytes.NewBuffer(nil)))
+ c.Assert(err, NotNil)
+}
+
+func (s *UploadPackResponseSuite) TestReadNoDecode(c *C) {
+ req := NewUploadPackRequest()
+ req.Capabilities.Set(capability.MultiACK)
+
+ res := NewUploadPackResponse(req)
+ defer res.Close()
+
+ n, err := res.Read(nil)
+ c.Assert(err, Equals, ErrUploadPackResponseNotDecoded)
+ c.Assert(n, Equals, 0)
+}