From 841abfb7dc640755c443432064252907e3e55c95 Mon Sep 17 00:00:00 2001 From: "Santiago M. Mola" Date: Wed, 4 Jan 2017 11:18:41 +0100 Subject: server: add git server implementation (#190) * server: add generic server implementation (transport-independent), both for git-upload-pack and git-receive-pack. * server: move internal functions to internal/common. * cli: add git-receive-pack and git-upload-pack implementations. * format/packfile: add UpdateObjectStorage function, extracted from Remote. * transport: implement tranport RPC-like, only with git-upload-pack and git-receive-pack methods. Client renamed to Transport. * storer: add storer.Storer interface. * protocol/packp: add UploadPackResponse constructor with packfile. * protocol/packp: fix UploadPackResponse encoding, add tests. * protocol/packp/capability: implement All. --- plumbing/protocol/packp/capability/list.go | 10 +++ plumbing/protocol/packp/capability/list_test.go | 11 ++++ plumbing/protocol/packp/shallowupd.go | 19 ++++++ plumbing/protocol/packp/shallowupd_test.go | 87 +++++++++++++++++++++++++ plumbing/protocol/packp/srvresp.go | 14 ++++ plumbing/protocol/packp/updreq_decode.go | 2 +- plumbing/protocol/packp/uppackresp.go | 30 ++++++++- plumbing/protocol/packp/uppackresp_test.go | 44 +++++++++++++ 8 files changed, 215 insertions(+), 2 deletions(-) (limited to 'plumbing/protocol') diff --git a/plumbing/protocol/packp/capability/list.go b/plumbing/protocol/packp/capability/list.go index 2847580..69fdb51 100644 --- a/plumbing/protocol/packp/capability/list.go +++ b/plumbing/protocol/packp/capability/list.go @@ -167,6 +167,16 @@ func (l *List) Delete(capability Capability) { } } +// All returns a slice with all defined capabilities. +func (l *List) All() []Capability { + var cs []Capability + for _, key := range l.sort { + cs = append(cs, Capability(key)) + } + + return cs +} + // String generates the capabilities strings, the capabilities are sorted in // insertion order func (l *List) String() string { diff --git a/plumbing/protocol/packp/capability/list_test.go b/plumbing/protocol/packp/capability/list_test.go index 42f0179..0a0ad26 100644 --- a/plumbing/protocol/packp/capability/list_test.go +++ b/plumbing/protocol/packp/capability/list_test.go @@ -171,3 +171,14 @@ func (s *SuiteCapabilities) TestAddErrMultipleArgumentsAtTheSameTime(c *check.C) err := cap.Add(Agent, "foo", "bar") c.Assert(err, check.Equals, ErrMultipleArguments) } + +func (s *SuiteCapabilities) TestAll(c *check.C) { + cap := NewList() + c.Assert(NewList().All(), check.IsNil) + + cap.Add(Agent, "foo") + c.Assert(cap.All(), check.DeepEquals, []Capability{Agent}) + + cap.Add(OFSDelta) + c.Assert(cap.All(), check.DeepEquals, []Capability{Agent, OFSDelta}) +} diff --git a/plumbing/protocol/packp/shallowupd.go b/plumbing/protocol/packp/shallowupd.go index 89063de..40f58e8 100644 --- a/plumbing/protocol/packp/shallowupd.go +++ b/plumbing/protocol/packp/shallowupd.go @@ -24,6 +24,7 @@ func (r *ShallowUpdate) Decode(reader io.Reader) error { for s.Scan() { line := s.Bytes() + line = bytes.TrimSpace(line) var err error switch { @@ -71,3 +72,21 @@ func (r *ShallowUpdate) decodeLine(line, prefix []byte, expLen int) (plumbing.Ha raw := string(line[expLen-40 : expLen]) return plumbing.NewHash(raw), nil } + +func (r *ShallowUpdate) Encode(w io.Writer) error { + e := pktline.NewEncoder(w) + + for _, h := range r.Shallows { + if err := e.Encodef("%s%s\n", shallow, h.String()); err != nil { + return err + } + } + + for _, h := range r.Unshallows { + if err := e.Encodef("%s%s\n", unshallow, h.String()); err != nil { + return err + } + } + + return e.Flush() +} diff --git a/plumbing/protocol/packp/shallowupd_test.go b/plumbing/protocol/packp/shallowupd_test.go index d64fb5d..97a13fc 100644 --- a/plumbing/protocol/packp/shallowupd_test.go +++ b/plumbing/protocol/packp/shallowupd_test.go @@ -12,6 +12,26 @@ type ShallowUpdateSuite struct{} var _ = Suite(&ShallowUpdateSuite{}) +func (s *ShallowUpdateSuite) TestDecodeWithLF(c *C) { + raw := "" + + "0035shallow aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + + "0035shallow bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n" + + "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) TestDecode(c *C) { raw := "" + "0034shallow aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + @@ -61,3 +81,70 @@ func (s *ShallowUpdateSuite) TestDecodeMalformed(c *C) { err := su.Decode(bytes.NewBufferString(raw)) c.Assert(err, NotNil) } + +func (s *ShallowUpdateSuite) TestEncodeEmpty(c *C) { + su := &ShallowUpdate{} + buf := bytes.NewBuffer(nil) + c.Assert(su.Encode(buf), IsNil) + c.Assert(buf.String(), Equals, "0000") +} + +func (s *ShallowUpdateSuite) TestEncode(c *C) { + su := &ShallowUpdate{ + Shallows: []plumbing.Hash{ + plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + plumbing.NewHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), + }, + Unshallows: []plumbing.Hash{ + plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + plumbing.NewHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), + }, + } + buf := bytes.NewBuffer(nil) + c.Assert(su.Encode(buf), IsNil) + + expected := "" + + "0035shallow aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + + "0035shallow bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n" + + "0037unshallow aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + + "0037unshallow bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n" + + "0000" + + c.Assert(buf.String(), Equals, expected) +} + +func (s *ShallowUpdateSuite) TestEncodeShallow(c *C) { + su := &ShallowUpdate{ + Shallows: []plumbing.Hash{ + plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + plumbing.NewHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), + }, + } + buf := bytes.NewBuffer(nil) + c.Assert(su.Encode(buf), IsNil) + + expected := "" + + "0035shallow aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + + "0035shallow bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n" + + "0000" + + c.Assert(buf.String(), Equals, expected) +} + +func (s *ShallowUpdateSuite) TestEncodeUnshallow(c *C) { + su := &ShallowUpdate{ + Unshallows: []plumbing.Hash{ + plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + plumbing.NewHash("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), + }, + } + buf := bytes.NewBuffer(nil) + c.Assert(su.Encode(buf), IsNil) + + expected := "" + + "0037unshallow aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + + "0037unshallow bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n" + + "0000" + + c.Assert(buf.String(), Equals, expected) +} diff --git a/plumbing/protocol/packp/srvresp.go b/plumbing/protocol/packp/srvresp.go index 3284fa2..d41b50d 100644 --- a/plumbing/protocol/packp/srvresp.go +++ b/plumbing/protocol/packp/srvresp.go @@ -68,3 +68,17 @@ func (r *ServerResponse) decodeACKLine(line []byte) error { r.ACKs = append(r.ACKs, h) return nil } + +// Encode encodes the ServerResponse into a writer. +func (r *ServerResponse) Encode(w io.Writer) error { + if len(r.ACKs) > 1 { + return errors.New("multi_ack and multi_ack_detailed are not supported") + } + + e := pktline.NewEncoder(w) + if len(r.ACKs) == 0 { + return e.Encodef("%s\n", nak) + } + + return e.Encodef("%s %s\n", ack, r.ACKs[0].String()) +} diff --git a/plumbing/protocol/packp/updreq_decode.go b/plumbing/protocol/packp/updreq_decode.go index 51e8183..c15d49c 100644 --- a/plumbing/protocol/packp/updreq_decode.go +++ b/plumbing/protocol/packp/updreq_decode.go @@ -225,7 +225,7 @@ func parseCommand(b []byte) (*Command, error) { return nil, errInvalidNewObjId(err) } - return &Command{Old: oh, New: nh, Name: n}, nil + return &Command{Old: oh, New: nh, Name: plumbing.ReferenceName(n)}, nil } func parseHash(s string) (plumbing.Hash, error) { diff --git a/plumbing/protocol/packp/uppackresp.go b/plumbing/protocol/packp/uppackresp.go index a117956..ac456f3 100644 --- a/plumbing/protocol/packp/uppackresp.go +++ b/plumbing/protocol/packp/uppackresp.go @@ -5,6 +5,7 @@ import ( "io" "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability" + "gopkg.in/src-d/go-git.v4/utils/ioutil" ) // ErrUploadPackResponseNotDecoded is returned if Read is called without @@ -17,8 +18,8 @@ var ErrUploadPackResponseNotDecoded = errors.New("upload-pack-response should be type UploadPackResponse struct { ShallowUpdate ServerResponse - r io.ReadCloser + r io.ReadCloser isShallow bool isMultiACK bool isOk bool @@ -37,6 +38,16 @@ func NewUploadPackResponse(req *UploadPackRequest) *UploadPackResponse { } } +// NewUploadPackResponseWithPackfile creates a new UploadPackResponse instance, +// and sets its packfile reader. +func NewUploadPackResponseWithPackfile(req *UploadPackRequest, + pf io.ReadCloser) *UploadPackResponse { + + r := NewUploadPackResponse(req) + r.r = pf + return r +} + // 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 { @@ -56,6 +67,23 @@ func (r *UploadPackResponse) Decode(reader io.ReadCloser) error { return nil } +// Encode encodes an UploadPackResponse. +func (r *UploadPackResponse) Encode(w io.Writer) (err error) { + if r.isShallow { + if err := r.ShallowUpdate.Encode(w); err != nil { + return err + } + } + + if err := r.ServerResponse.Encode(w); err != nil { + return err + } + + defer ioutil.CheckClose(r.r, &err) + _, err = io.Copy(w, r.r) + return err +} + // 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 diff --git a/plumbing/protocol/packp/uppackresp_test.go b/plumbing/protocol/packp/uppackresp_test.go index c81bb76..c27fdda 100644 --- a/plumbing/protocol/packp/uppackresp_test.go +++ b/plumbing/protocol/packp/uppackresp_test.go @@ -7,6 +7,7 @@ import ( "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability" . "gopkg.in/check.v1" + "gopkg.in/src-d/go-git.v4/plumbing" ) type UploadPackResponseSuite struct{} @@ -80,3 +81,46 @@ func (s *UploadPackResponseSuite) TestReadNoDecode(c *C) { c.Assert(err, Equals, ErrUploadPackResponseNotDecoded) c.Assert(n, Equals, 0) } + +func (s *UploadPackResponseSuite) TestEncodeNAK(c *C) { + pf := ioutil.NopCloser(bytes.NewBuffer([]byte("[PACK]"))) + req := NewUploadPackRequest() + res := NewUploadPackResponseWithPackfile(req, pf) + defer func() { c.Assert(res.Close(), IsNil) }() + + b := bytes.NewBuffer(nil) + c.Assert(res.Encode(b), IsNil) + + expected := "0008NAK\n[PACK]" + c.Assert(string(b.Bytes()), Equals, expected) +} + +func (s *UploadPackResponseSuite) TestEncodeDepth(c *C) { + pf := ioutil.NopCloser(bytes.NewBuffer([]byte("[PACK]"))) + req := NewUploadPackRequest() + req.Depth = DepthCommits(1) + + res := NewUploadPackResponseWithPackfile(req, pf) + defer func() { c.Assert(res.Close(), IsNil) }() + + b := bytes.NewBuffer(nil) + c.Assert(res.Encode(b), IsNil) + + expected := "00000008NAK\n[PACK]" + c.Assert(string(b.Bytes()), Equals, expected) +} + +func (s *UploadPackResponseSuite) TestEncodeMultiACK(c *C) { + pf := ioutil.NopCloser(bytes.NewBuffer([]byte("[PACK]"))) + req := NewUploadPackRequest() + + res := NewUploadPackResponseWithPackfile(req, pf) + defer func() { c.Assert(res.Close(), IsNil) }() + res.ACKs = []plumbing.Hash{ + plumbing.NewHash("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f81"), + plumbing.NewHash("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f82"), + } + + b := bytes.NewBuffer(nil) + c.Assert(res.Encode(b), NotNil) +} -- cgit