diff options
author | Máximo Cuadros <mcuadros@gmail.com> | 2016-12-01 09:59:19 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-12-01 09:59:19 +0100 |
commit | c15bf1dff332873644290db0e186b8f5ad9b8fb2 (patch) | |
tree | 1dd79212333823b70f2792ad116864ea127e4a1c | |
parent | 7de79aef5d4aa3100382d4df3e99525f9949e8d1 (diff) | |
download | go-git-c15bf1dff332873644290db0e186b8f5ad9b8fb2.tar.gz |
capabilities: full integration (#151)
* format/pktline: fix readPayloadLen err handling
* protocol/pakp: UploadReq validation and creation of capabilities
* protocol/pakp: AdvRef tests
* protocol/pakp: capability.List.Delete
* protocol: filter unsupported capabilities
* remote capability negociation
* transport: UploadRequest validation
* requested changes
-rw-r--r-- | common_test.go | 7 | ||||
-rw-r--r-- | plumbing/protocol/packp/advrefs_test.go | 98 | ||||
-rw-r--r-- | plumbing/protocol/packp/capability/list.go | 17 | ||||
-rw-r--r-- | plumbing/protocol/packp/capability/list_test.go | 16 | ||||
-rw-r--r-- | plumbing/protocol/packp/ulreq.go | 105 | ||||
-rw-r--r-- | plumbing/protocol/packp/ulreq_decode_test.go | 2 | ||||
-rw-r--r-- | plumbing/protocol/packp/ulreq_test.go | 101 | ||||
-rw-r--r-- | plumbing/protocol/packp/upload_pack_request_test.go | 33 | ||||
-rw-r--r-- | plumbing/protocol/packp/uppackreq.go (renamed from plumbing/protocol/packp/upload_pack_request.go) | 67 | ||||
-rw-r--r-- | plumbing/protocol/packp/uppackreq_test.go | 42 | ||||
-rw-r--r-- | plumbing/transport/common.go | 20 | ||||
-rw-r--r-- | plumbing/transport/common_test.go | 10 | ||||
-rw-r--r-- | plumbing/transport/http/fetch_pack.go | 5 | ||||
-rw-r--r-- | plumbing/transport/http/fetch_pack_test.go | 6 | ||||
-rw-r--r-- | plumbing/transport/internal/common/common.go | 5 | ||||
-rw-r--r-- | plumbing/transport/test/common.go | 40 | ||||
-rw-r--r-- | remote.go | 6 |
17 files changed, 478 insertions, 102 deletions
diff --git a/common_test.go b/common_test.go index 1e90409..3aa3693 100644 --- a/common_test.go +++ b/common_test.go @@ -119,6 +119,13 @@ func (c *MockFetchPackSession) AdvertisedReferences() (*packp.AdvRefs, error) { func (c *MockFetchPackSession) FetchPack( r *packp.UploadPackRequest) (io.ReadCloser, error) { + if !r.Capabilities.Supports(capability.Agent) { + return nil, fmt.Errorf("" + + "invalid test rquest, missing Agent capability, the request" + + "should be created using NewUploadPackRequestFromCapabilities", + ) + } + f := fixtures.ByURL(c.endpoint.String()) if len(r.Wants) == 1 { diff --git a/plumbing/protocol/packp/advrefs_test.go b/plumbing/protocol/packp/advrefs_test.go index 1689938..52ddc5c 100644 --- a/plumbing/protocol/packp/advrefs_test.go +++ b/plumbing/protocol/packp/advrefs_test.go @@ -1,27 +1,89 @@ -package packp_test +package packp import ( "bytes" "fmt" "io" "strings" - "testing" "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" "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability" . "gopkg.in/check.v1" ) -func Test(t *testing.T) { TestingT(t) } +type AdvRefSuite struct{} -type SuiteDecodeEncode struct{} +var _ = Suite(&AdvRefSuite{}) -var _ = Suite(&SuiteDecodeEncode{}) +func (s *AdvRefSuite) TestAddReferenceSymbolic(c *C) { + ref := plumbing.NewSymbolicReference("foo", "bar") -func (s *SuiteDecodeEncode) test(c *C, in []string, exp []string) { + a := NewAdvRefs() + err := a.AddReference(ref) + c.Assert(err, IsNil) + + values := a.Capabilities.Get(capability.SymRef) + c.Assert(values, HasLen, 1) + c.Assert(values[0], Equals, "foo:bar") +} + +func (s *AdvRefSuite) TestAddReferenceHash(c *C) { + ref := plumbing.NewHashReference("foo", plumbing.NewHash("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c")) + + a := NewAdvRefs() + err := a.AddReference(ref) + c.Assert(err, IsNil) + + c.Assert(a.References, HasLen, 1) + c.Assert(a.References["foo"].String(), Equals, "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c") +} + +func (s *AdvRefSuite) TestAllReferences(c *C) { + hash := plumbing.NewHash("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c") + + a := NewAdvRefs() + err := a.AddReference(plumbing.NewSymbolicReference("foo", "bar")) + c.Assert(err, IsNil) + err = a.AddReference(plumbing.NewHashReference("bar", hash)) + c.Assert(err, IsNil) + + refs, err := a.AllReferences() + c.Assert(err, IsNil) + + iter, err := refs.IterReferences() + c.Assert(err, IsNil) + + var count int + iter.ForEach(func(ref *plumbing.Reference) error { + count++ + switch ref.Name() { + case "bar": + c.Assert(ref.Hash(), Equals, hash) + case "foo": + c.Assert(ref.Target().String(), Equals, "bar") + } + return nil + }) + + c.Assert(count, Equals, 2) +} + +func (s *AdvRefSuite) TestAllReferencesBadSymref(c *C) { + a := NewAdvRefs() + err := a.Capabilities.Set(capability.SymRef, "foo") + c.Assert(err, IsNil) + + _, err = a.AllReferences() + c.Assert(err, NotNil) +} + +type AdvRefsDecodeEncodeSuite struct{} + +var _ = Suite(&AdvRefsDecodeEncodeSuite{}) + +func (s *AdvRefsDecodeEncodeSuite) test(c *C, in []string, exp []string) { var err error var input io.Reader { @@ -44,7 +106,7 @@ func (s *SuiteDecodeEncode) test(c *C, in []string, exp []string) { var obtained []byte { - ar := packp.NewAdvRefs() + ar := NewAdvRefs() c.Assert(ar.Decode(input), IsNil) var buf bytes.Buffer @@ -56,7 +118,7 @@ func (s *SuiteDecodeEncode) test(c *C, in []string, exp []string) { c.Assert(string(obtained), DeepEquals, string(expected)) } -func (s *SuiteDecodeEncode) TestNoHead(c *C) { +func (s *AdvRefsDecodeEncodeSuite) TestNoHead(c *C) { input := []string{ "0000000000000000000000000000000000000000 capabilities^{}\x00", pktline.FlushString, @@ -70,7 +132,7 @@ func (s *SuiteDecodeEncode) TestNoHead(c *C) { s.test(c, input, expected) } -func (s *SuiteDecodeEncode) TestNoHeadSmart(c *C) { +func (s *AdvRefsDecodeEncodeSuite) TestNoHeadSmart(c *C) { input := []string{ "# service=git-upload-pack\n", "0000000000000000000000000000000000000000 capabilities^{}\x00", @@ -86,7 +148,7 @@ func (s *SuiteDecodeEncode) TestNoHeadSmart(c *C) { s.test(c, input, expected) } -func (s *SuiteDecodeEncode) TestNoHeadSmartBug(c *C) { +func (s *AdvRefsDecodeEncodeSuite) TestNoHeadSmartBug(c *C) { input := []string{ "# service=git-upload-pack\n", pktline.FlushString, @@ -104,7 +166,7 @@ func (s *SuiteDecodeEncode) TestNoHeadSmartBug(c *C) { s.test(c, input, expected) } -func (s *SuiteDecodeEncode) TestRefs(c *C) { +func (s *AdvRefsDecodeEncodeSuite) TestRefs(c *C) { input := []string{ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00symref=HEAD:/refs/heads/master ofs-delta multi_ack", "a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master", @@ -124,7 +186,7 @@ func (s *SuiteDecodeEncode) TestRefs(c *C) { s.test(c, input, expected) } -func (s *SuiteDecodeEncode) TestPeeled(c *C) { +func (s *AdvRefsDecodeEncodeSuite) TestPeeled(c *C) { input := []string{ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00symref=HEAD:/refs/heads/master ofs-delta multi_ack", "7777777777777777777777777777777777777777 refs/tags/v2.6.12-tree\n", @@ -148,7 +210,7 @@ func (s *SuiteDecodeEncode) TestPeeled(c *C) { s.test(c, input, expected) } -func (s *SuiteDecodeEncode) TestAll(c *C) { +func (s *AdvRefsDecodeEncodeSuite) TestAll(c *C) { input := []string{ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00symref=HEAD:/refs/heads/master ofs-delta multi_ack\n", "a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n", @@ -176,7 +238,7 @@ func (s *SuiteDecodeEncode) TestAll(c *C) { s.test(c, input, expected) } -func (s *SuiteDecodeEncode) TestAllSmart(c *C) { +func (s *AdvRefsDecodeEncodeSuite) TestAllSmart(c *C) { input := []string{ "# service=git-upload-pack\n", pktline.FlushString, @@ -208,7 +270,7 @@ func (s *SuiteDecodeEncode) TestAllSmart(c *C) { s.test(c, input, expected) } -func (s *SuiteDecodeEncode) TestAllSmartBug(c *C) { +func (s *AdvRefsDecodeEncodeSuite) TestAllSmartBug(c *C) { input := []string{ "# service=git-upload-pack\n", pktline.FlushString, @@ -254,7 +316,7 @@ func ExampleDecoder_Decode() { input := strings.NewReader(raw) // Decode the input into a newly allocated AdvRefs value. - ar := packp.NewAdvRefs() + ar := NewAdvRefs() _ = ar.Decode(input) // error check ignored for brevity // Do something interesting with the AdvRefs, e.g. print its contents. @@ -270,7 +332,7 @@ func ExampleDecoder_Decode() { func ExampleEncoder_Encode() { // Create an AdvRefs with the contents you want... - ar := packp.NewAdvRefs() + ar := NewAdvRefs() // ...add a hash for the HEAD... head := plumbing.NewHash("1111111111111111111111111111111111111111") diff --git a/plumbing/protocol/packp/capability/list.go b/plumbing/protocol/packp/capability/list.go index ff8d4a4..7197d36 100644 --- a/plumbing/protocol/packp/capability/list.go +++ b/plumbing/protocol/packp/capability/list.go @@ -145,6 +145,23 @@ func (l *List) Supports(capability Capability) bool { return ok } +// Delete deletes a capability from the List +func (l *List) Delete(capability Capability) { + if !l.Supports(capability) { + return + } + + delete(l.m, capability) + for i, c := range l.sort { + if c != string(capability) { + continue + } + + l.sort = append(l.sort[:i], l.sort[i+1:]...) + return + } +} + // 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 eeb3173..d6aac16 100644 --- a/plumbing/protocol/packp/capability/list_test.go +++ b/plumbing/protocol/packp/capability/list_test.go @@ -97,6 +97,22 @@ func (s *SuiteCapabilities) TestGetEmpty(c *check.C) { c.Assert(cap.Get(Agent), check.HasLen, 0) } +func (s *SuiteCapabilities) TestDelete(c *check.C) { + cap := NewList() + cap.Delete(SymRef) + + err := cap.Add(Sideband) + c.Assert(err, check.IsNil) + err = cap.Set(SymRef, "bar") + c.Assert(err, check.IsNil) + err = cap.Set(Sideband64k) + c.Assert(err, check.IsNil) + + cap.Delete(SymRef) + + c.Assert(cap.String(), check.Equals, "side-band side-band-64k") +} + func (s *SuiteCapabilities) TestAdd(c *check.C) { cap := NewList() err := cap.Add(SymRef, "foo", "qux") diff --git a/plumbing/protocol/packp/ulreq.go b/plumbing/protocol/packp/ulreq.go index be68b26..254e85e 100644 --- a/plumbing/protocol/packp/ulreq.go +++ b/plumbing/protocol/packp/ulreq.go @@ -1,12 +1,15 @@ package packp import ( + "fmt" "time" "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability" ) +const DefaultAgent = "go-git/4.x" + // UploadRequest values represent the information transmitted on a // upload-request message. Values from this type are not zero-value // safe, use the New function instead. @@ -41,10 +44,9 @@ type DepthReference string func (d DepthReference) isDepth() {} -// NewUploadRequest returns a pointer to a new UlReq 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. +// 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. func NewUploadRequest() *UploadRequest { return &UploadRequest{ Capabilities: capability.NewList(), @@ -54,7 +56,96 @@ func NewUploadRequest() *UploadRequest { } } -// Want adds a hash reference to the 'wants' list. -func (r *UploadRequest) Want(h ...plumbing.Hash) { - r.Wants = append(r.Wants, h...) +// NewUploadRequestFromCapabilities returns a pointer to a new UploadRequest +// value, the request capabilities are filled with the most optiomal ones, based +// on the adv value (advertaised capabilities), the UploadRequest generated it +// has no wants or shallows and an infinite depth. +func NewUploadRequestFromCapabilities(adv *capability.List) *UploadRequest { + r := NewUploadRequest() + + if adv.Supports(capability.MultiACKDetailed) { + r.Capabilities.Set(capability.MultiACKDetailed) + } else if adv.Supports(capability.MultiACK) { + r.Capabilities.Set(capability.MultiACK) + } + + if adv.Supports(capability.ThinPack) { + r.Capabilities.Set(capability.ThinPack) + } + + if adv.Supports(capability.OFSDelta) { + r.Capabilities.Set(capability.OFSDelta) + } + + if adv.Supports(capability.Agent) { + r.Capabilities.Set(capability.Agent, DefaultAgent) + } + + return r +} + +// Validate validates the content of UploadRequest, following the next rules: +// - Wants MUST have at least one reference +// - capability.Shallow MUST be present if Shallows is not empty +// - is a non-zero DepthCommits is given capability.Shallow MUST be present +// - is a DepthSince is given capability.Shallow MUST be present +// - is a DepthReference is given capability.DeepenNot MUST be present +// - MUST contain only maximum of one of capability.Sideband and capability.Sideband64k +// - MUST contain only maximum of one of capability.MultiACK and capability.MultiACKDetailed +func (r *UploadRequest) Validate() error { + if len(r.Wants) == 0 { + return fmt.Errorf("want can't be empty") + } + + if err := r.validateRequiredCapabilities(); err != nil { + return err + } + + if err := r.validateConflictCapabilities(); err != nil { + return err + } + + return nil +} + +func (r *UploadRequest) validateRequiredCapabilities() error { + msg := "missing capability %s" + + if len(r.Shallows) != 0 && !r.Capabilities.Supports(capability.Shallow) { + return fmt.Errorf(msg, capability.Shallow) + } + + switch r.Depth.(type) { + case DepthCommits: + if r.Depth != DepthCommits(0) { + if !r.Capabilities.Supports(capability.Shallow) { + return fmt.Errorf(msg, capability.Shallow) + } + } + case DepthSince: + if !r.Capabilities.Supports(capability.DeepenSince) { + return fmt.Errorf(msg, capability.DeepenSince) + } + case DepthReference: + if !r.Capabilities.Supports(capability.DeepenNot) { + return fmt.Errorf(msg, capability.DeepenNot) + } + } + + return nil +} + +func (r *UploadRequest) validateConflictCapabilities() error { + msg := "capabilities %s and %s are mutually exclusive" + if r.Capabilities.Supports(capability.Sideband) && + r.Capabilities.Supports(capability.Sideband64k) { + return fmt.Errorf(msg, capability.Sideband, capability.Sideband64k) + } + + if r.Capabilities.Supports(capability.MultiACK) && + r.Capabilities.Supports(capability.MultiACKDetailed) { + return fmt.Errorf(msg, capability.MultiACK, capability.MultiACKDetailed) + } + + return nil } diff --git a/plumbing/protocol/packp/ulreq_decode_test.go b/plumbing/protocol/packp/ulreq_decode_test.go index e7d9d7c6..82a4e1f 100644 --- a/plumbing/protocol/packp/ulreq_decode_test.go +++ b/plumbing/protocol/packp/ulreq_decode_test.go @@ -8,9 +8,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 UlReqDecodeSuite struct{} diff --git a/plumbing/protocol/packp/ulreq_test.go b/plumbing/protocol/packp/ulreq_test.go index 5e9e978..53626ee 100644 --- a/plumbing/protocol/packp/ulreq_test.go +++ b/plumbing/protocol/packp/ulreq_test.go @@ -9,8 +9,109 @@ 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" ) +type UlReqSuite struct{} + +var _ = Suite(&UlReqSuite{}) + +func (s *UlReqSuite) TestNewUploadRequestFromCapabilities(c *C) { + cap := capability.NewList() + cap.Set(capability.Sideband) + cap.Set(capability.Sideband64k) + cap.Set(capability.MultiACK) + cap.Set(capability.MultiACKDetailed) + cap.Set(capability.ThinPack) + cap.Set(capability.OFSDelta) + cap.Set(capability.Agent, "foo") + + r := NewUploadRequestFromCapabilities(cap) + c.Assert(r.Capabilities.String(), Equals, + "multi_ack_detailed thin-pack ofs-delta agent=go-git/4.x", + ) +} + +func (s *UlReqSuite) TestValidateWants(c *C) { + r := NewUploadRequest() + err := r.Validate() + c.Assert(err, NotNil) + + r.Wants = append(r.Wants, plumbing.NewHash("1111111111111111111111111111111111111111")) + err = r.Validate() + c.Assert(err, IsNil) +} + +func (s *UlReqSuite) TestValidateShallows(c *C) { + r := NewUploadRequest() + r.Wants = append(r.Wants, plumbing.NewHash("1111111111111111111111111111111111111111")) + r.Shallows = append(r.Shallows, plumbing.NewHash("2222222222222222222222222222222222222222")) + err := r.Validate() + c.Assert(err, NotNil) + + r.Capabilities.Set(capability.Shallow) + err = r.Validate() + c.Assert(err, IsNil) +} + +func (s *UlReqSuite) TestValidateDepthCommits(c *C) { + r := NewUploadRequest() + r.Wants = append(r.Wants, plumbing.NewHash("1111111111111111111111111111111111111111")) + r.Depth = DepthCommits(42) + + err := r.Validate() + c.Assert(err, NotNil) + + r.Capabilities.Set(capability.Shallow) + err = r.Validate() + c.Assert(err, IsNil) +} + +func (s *UlReqSuite) TestValidateDepthReference(c *C) { + r := NewUploadRequest() + r.Wants = append(r.Wants, plumbing.NewHash("1111111111111111111111111111111111111111")) + r.Depth = DepthReference("1111111111111111111111111111111111111111") + + err := r.Validate() + c.Assert(err, NotNil) + + r.Capabilities.Set(capability.DeepenNot) + err = r.Validate() + c.Assert(err, IsNil) +} + +func (s *UlReqSuite) TestValidateDepthSince(c *C) { + r := NewUploadRequest() + r.Wants = append(r.Wants, plumbing.NewHash("1111111111111111111111111111111111111111")) + r.Depth = DepthSince(time.Now()) + + err := r.Validate() + c.Assert(err, NotNil) + + r.Capabilities.Set(capability.DeepenSince) + err = r.Validate() + c.Assert(err, IsNil) +} + +func (s *UlReqSuite) TestValidateConflictSideband(c *C) { + r := NewUploadRequest() + r.Wants = append(r.Wants, plumbing.NewHash("1111111111111111111111111111111111111111")) + r.Capabilities.Set(capability.Sideband) + r.Capabilities.Set(capability.Sideband64k) + err := r.Validate() + c.Assert(err, NotNil) +} + +func (s *UlReqSuite) TestValidateConflictMultiACK(c *C) { + r := NewUploadRequest() + r.Wants = append(r.Wants, plumbing.NewHash("1111111111111111111111111111111111111111")) + r.Capabilities.Set(capability.MultiACK) + r.Capabilities.Set(capability.MultiACKDetailed) + err := r.Validate() + c.Assert(err, NotNil) +} + func ExampleUlReqEncoder_Encode() { // Create an empty UlReq with the contents you want... ur := NewUploadRequest() diff --git a/plumbing/protocol/packp/upload_pack_request_test.go b/plumbing/protocol/packp/upload_pack_request_test.go deleted file mode 100644 index f321736..0000000 --- a/plumbing/protocol/packp/upload_pack_request_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package packp - -import ( - "gopkg.in/src-d/go-git.v4/plumbing" - - . "gopkg.in/check.v1" -) - -type UploadPackRequestSuite struct{} - -var _ = Suite(&UploadPackRequestSuite{}) - -func (s *UploadPackRequestSuite) TestUploadPackRequest_IsEmpty(c *C) { - r := NewUploadPackRequest() - r.Want(plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c")) - r.Want(plumbing.NewHash("2b41ef280fdb67a9b250678686a0c3e03b0a9989")) - r.Have(plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) - - c.Assert(r.IsEmpty(), Equals, false) - - r = NewUploadPackRequest() - r.Want(plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c")) - r.Want(plumbing.NewHash("2b41ef280fdb67a9b250678686a0c3e03b0a9989")) - r.Have(plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c")) - - c.Assert(r.IsEmpty(), Equals, false) - - r = NewUploadPackRequest() - r.Want(plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c")) - r.Have(plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c")) - - c.Assert(r.IsEmpty(), Equals, true) -} diff --git a/plumbing/protocol/packp/upload_pack_request.go b/plumbing/protocol/packp/uppackreq.go index 42033b1..2b1cb84 100644 --- a/plumbing/protocol/packp/upload_pack_request.go +++ b/plumbing/protocol/packp/uppackreq.go @@ -6,37 +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" ) -// UploadHaves is a message to signal the references that a client has in a -// upload-pack. Do not use this directly. Use UploadPackRequest request instead. -type UploadHaves struct { - Haves []plumbing.Hash -} - -// Encode encodes the UploadHaves into the Writer. -func (u *UploadHaves) Encode(w io.Writer) error { - e := pktline.NewEncoder(w) - for _, have := range u.Haves { - if err := e.Encodef("have %s\n", have); err != nil { - return fmt.Errorf("sending haves for %q: %s", have, err) - } - } - - if len(u.Haves) != 0 { - if err := e.Flush(); err != nil { - return fmt.Errorf("sending flush-pkt after haves: %s", err) - } - } - - return nil -} - -// Have adds a hash reference to the 'haves' list. -func (r *UploadHaves) Have(h ...plumbing.Hash) { - r.Haves = append(r.Haves, h...) -} - // UploadPackRequest represents a upload-pack request. // Zero-value is not safe, use NewUploadPackRequest instead. type UploadPackRequest struct { @@ -52,6 +24,19 @@ func NewUploadPackRequest() *UploadPackRequest { } } +// NewUploadPackRequestFromCapabilities creates a new UploadPackRequest and +// returns a pointer. The request capabilities are filled with the most optiomal +// 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 { + return &UploadPackRequest{ + UploadHaves: &UploadHaves{}, + UploadRequest: NewUploadRequestFromCapabilities(adv), + } +} + +// IsEmpty a request if empty if Haves are contained in the Wants, or if Wants +// length is zero func (r *UploadPackRequest) IsEmpty() bool { return isSubset(r.Wants, r.Haves) } @@ -73,3 +58,27 @@ func isSubset(needle []plumbing.Hash, haystack []plumbing.Hash) bool { return true } + +// UploadHaves is a message to signal the references that a client has in a +// upload-pack. Do not use this directly. Use UploadPackRequest request instead. +type UploadHaves struct { + Haves []plumbing.Hash +} + +// Encode encodes the UploadHaves into the Writer. +func (u *UploadHaves) Encode(w io.Writer) error { + e := pktline.NewEncoder(w) + for _, have := range u.Haves { + if err := e.Encodef("have %s\n", have); err != nil { + return fmt.Errorf("sending haves for %q: %s", have, err) + } + } + + if len(u.Haves) != 0 { + if err := e.Flush(); err != nil { + return fmt.Errorf("sending flush-pkt after haves: %s", err) + } + } + + return nil +} diff --git a/plumbing/protocol/packp/uppackreq_test.go b/plumbing/protocol/packp/uppackreq_test.go new file mode 100644 index 0000000..75b75b4 --- /dev/null +++ b/plumbing/protocol/packp/uppackreq_test.go @@ -0,0 +1,42 @@ +package packp + +import ( + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability" + + . "gopkg.in/check.v1" +) + +type UploadPackRequestSuite struct{} + +var _ = Suite(&UploadPackRequestSuite{}) + +func (s *UploadPackRequestSuite) TestNewUploadPackRequestFromCapabilities(c *C) { + cap := capability.NewList() + cap.Set(capability.Agent, "foo") + + r := NewUploadPackRequestFromCapabilities(cap) + c.Assert(r.Capabilities.String(), Equals, "agent=go-git/4.x") +} + +func (s *UploadPackRequestSuite) TestIsEmpty(c *C) { + r := NewUploadPackRequest() + r.Wants = append(r.Wants, plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c")) + r.Wants = append(r.Wants, plumbing.NewHash("2b41ef280fdb67a9b250678686a0c3e03b0a9989")) + r.Haves = append(r.Haves, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) + + c.Assert(r.IsEmpty(), Equals, false) + + r = NewUploadPackRequest() + r.Wants = append(r.Wants, plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c")) + r.Wants = append(r.Wants, plumbing.NewHash("2b41ef280fdb67a9b250678686a0c3e03b0a9989")) + r.Haves = append(r.Haves, plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c")) + + c.Assert(r.IsEmpty(), Equals, false) + + r = NewUploadPackRequest() + r.Wants = append(r.Wants, plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c")) + r.Haves = append(r.Haves, plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c")) + + c.Assert(r.IsEmpty(), Equals, true) +} diff --git a/plumbing/transport/common.go b/plumbing/transport/common.go index 9715a39..bfc999f 100644 --- a/plumbing/transport/common.go +++ b/plumbing/transport/common.go @@ -21,6 +21,7 @@ import ( "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp" + "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability" ) var ( @@ -124,3 +125,22 @@ func transformSCPLikeIfNeeded(endpoint string) string { return endpoint } + +// UnsupportedCapabilities are the capabilities not supported by any client +// implementation +var UnsupportedCapabilities = []capability.Capability{ + capability.MultiACK, + capability.MultiACKDetailed, + capability.Sideband, + capability.Sideband64k, + capability.ThinPack, +} + +// FilterUnsupportedCapabilities it filter out all the UnsupportedCapabilities +// from a capability.List, the intended usage is on the client implementation +// to filter the capabilities from an AdvRefs message. +func FilterUnsupportedCapabilities(list *capability.List) { + for _, c := range UnsupportedCapabilities { + list.Delete(c) + } +} diff --git a/plumbing/transport/common_test.go b/plumbing/transport/common_test.go index 9ca4459..e9a5efa 100644 --- a/plumbing/transport/common_test.go +++ b/plumbing/transport/common_test.go @@ -3,6 +3,8 @@ package transport import ( "testing" + "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability" + . "gopkg.in/check.v1" ) @@ -29,3 +31,11 @@ func (s *SuiteCommon) TestNewEndpointWrongForgat(c *C) { c.Assert(err, Not(IsNil)) c.Assert(e.Host, Equals, "") } + +func (s *SuiteCommon) TestFilterUnsupportedCapabilities(c *C) { + l := capability.NewList() + l.Set(capability.MultiACK) + + FilterUnsupportedCapabilities(l) + c.Assert(l.Supports(capability.MultiACK), Equals, false) +} diff --git a/plumbing/transport/http/fetch_pack.go b/plumbing/transport/http/fetch_pack.go index 40c3a7f..f250667 100644 --- a/plumbing/transport/http/fetch_pack.go +++ b/plumbing/transport/http/fetch_pack.go @@ -70,6 +70,7 @@ func (s *fetchPackSession) AdvertisedReferences() (*packp.AdvRefs, error) { return nil, err } + transport.FilterUnsupportedCapabilities(ar.Capabilities) return ar, nil } @@ -78,6 +79,10 @@ func (s *fetchPackSession) FetchPack(r *packp.UploadPackRequest) (io.ReadCloser, return nil, transport.ErrEmptyUploadPackRequest } + if err := r.Validate(); err != nil { + return nil, err + } + url := fmt.Sprintf( "%s/%s", s.endpoint.String(), transport.UploadPackServiceName, diff --git a/plumbing/transport/http/fetch_pack_test.go b/plumbing/transport/http/fetch_pack_test.go index 7471208..920b623 100644 --- a/plumbing/transport/http/fetch_pack_test.go +++ b/plumbing/transport/http/fetch_pack_test.go @@ -43,9 +43,9 @@ func (s *FetchPackSuite) TestInfoNotExists(c *C) { func (s *FetchPackSuite) TestuploadPackRequestToReader(c *C) { r := packp.NewUploadPackRequest() - r.Want(plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c")) - r.Want(plumbing.NewHash("2b41ef280fdb67a9b250678686a0c3e03b0a9989")) - r.Have(plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) + r.Wants = append(r.Wants, plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c")) + r.Wants = append(r.Wants, plumbing.NewHash("2b41ef280fdb67a9b250678686a0c3e03b0a9989")) + r.Haves = append(r.Haves, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) sr, err := uploadPackRequestToReader(r) c.Assert(err, IsNil) diff --git a/plumbing/transport/internal/common/common.go b/plumbing/transport/internal/common/common.go index 10e395e..56edab0 100644 --- a/plumbing/transport/internal/common/common.go +++ b/plumbing/transport/internal/common/common.go @@ -162,6 +162,7 @@ func (s *session) AdvertisedReferences() (*packp.AdvRefs, error) { return nil, err } + transport.FilterUnsupportedCapabilities(ar.Capabilities) return ar, nil } @@ -172,6 +173,10 @@ func (s *session) FetchPack(req *packp.UploadPackRequest) (io.ReadCloser, error) return nil, transport.ErrEmptyUploadPackRequest } + if err := req.Validate(); err != nil { + return nil, err + } + if !s.advRefsRun { if _, err := s.AdvertisedReferences(); err != nil { return nil, err diff --git a/plumbing/transport/test/common.go b/plumbing/transport/test/common.go index ed2e141..3b7f05f 100644 --- a/plumbing/transport/test/common.go +++ b/plumbing/transport/test/common.go @@ -43,7 +43,7 @@ func (s *FetchPackSuite) TestInfoNotExists(c *C) { r, err = s.Client.NewFetchPackSession(s.NonExistentEndpoint) c.Assert(err, IsNil) req := packp.NewUploadPackRequest() - req.Want(plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) + req.Wants = append(req.Wants, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) reader, err := r.FetchPack(req) c.Assert(err, Equals, transport.ErrRepositoryNotFound) c.Assert(reader, IsNil) @@ -70,6 +70,16 @@ func (s *FetchPackSuite) TestDefaultBranch(c *C) { c.Assert(symrefs[0], Equals, "HEAD:refs/heads/master") } +func (s *FetchPackSuite) TestAdvertisedReferencesFilterUnsupported(c *C) { + r, err := s.Client.NewFetchPackSession(s.Endpoint) + c.Assert(err, IsNil) + defer func() { c.Assert(r.Close(), IsNil) }() + + info, err := r.AdvertisedReferences() + c.Assert(err, IsNil) + c.Assert(info.Capabilities.Supports(capability.MultiACK), Equals, false) +} + func (s *FetchPackSuite) TestCapabilities(c *C) { r, err := s.Client.NewFetchPackSession(s.Endpoint) c.Assert(err, IsNil) @@ -90,7 +100,7 @@ func (s *FetchPackSuite) TestFullFetchPack(c *C) { c.Assert(info, NotNil) req := packp.NewUploadPackRequest() - req.Want(plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) + req.Wants = append(req.Wants, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) reader, err := r.FetchPack(req) c.Assert(err, IsNil) @@ -104,7 +114,7 @@ func (s *FetchPackSuite) TestFetchPack(c *C) { defer func() { c.Assert(r.Close(), IsNil) }() req := packp.NewUploadPackRequest() - req.Want(plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) + req.Wants = append(req.Wants, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) reader, err := r.FetchPack(req) c.Assert(err, IsNil) @@ -112,14 +122,28 @@ func (s *FetchPackSuite) TestFetchPack(c *C) { s.checkObjectNumber(c, reader, 28) } +func (s *FetchPackSuite) TestFetchPackInvalidReq(c *C) { + r, err := s.Client.NewFetchPackSession(s.Endpoint) + c.Assert(err, IsNil) + defer func() { c.Assert(r.Close(), IsNil) }() + + req := packp.NewUploadPackRequest() + req.Wants = append(req.Wants, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) + req.Capabilities.Set(capability.Sideband) + req.Capabilities.Set(capability.Sideband64k) + + _, err = r.FetchPack(req) + c.Assert(err, NotNil) +} + func (s *FetchPackSuite) TestFetchPackNoChanges(c *C) { r, err := s.Client.NewFetchPackSession(s.Endpoint) c.Assert(err, IsNil) defer func() { c.Assert(r.Close(), IsNil) }() req := packp.NewUploadPackRequest() - req.Want(plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) - req.Have(plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) + req.Wants = append(req.Wants, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) + req.Haves = append(req.Haves, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) reader, err := r.FetchPack(req) c.Assert(err, Equals, transport.ErrEmptyUploadPackRequest) @@ -132,8 +156,8 @@ func (s *FetchPackSuite) TestFetchPackMulti(c *C) { defer func() { c.Assert(r.Close(), IsNil) }() req := packp.NewUploadPackRequest() - req.Want(plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) - req.Want(plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881")) + req.Wants = append(req.Wants, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) + req.Wants = append(req.Wants, plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881")) reader, err := r.FetchPack(req) c.Assert(err, IsNil) @@ -146,7 +170,7 @@ func (s *FetchPackSuite) TestFetchError(c *C) { c.Assert(err, IsNil) req := packp.NewUploadPackRequest() - req.Want(plumbing.NewHash("1111111111111111111111111111111111111111")) + req.Wants = append(req.Wants, plumbing.NewHash("1111111111111111111111111111111111111111")) reader, err := r.FetchPack(req) c.Assert(err, Equals, transport.ErrEmptyUploadPackRequest) @@ -171,11 +171,11 @@ func (r *Remote) getWantedReferences(spec []config.RefSpec) ([]*plumbing.Referen func (r *Remote) buildRequest( s storer.ReferenceStorer, o *FetchOptions, refs []*plumbing.Reference, ) (*packp.UploadPackRequest, error) { - req := packp.NewUploadPackRequest() + req := packp.NewUploadPackRequestFromCapabilities(r.advRefs.Capabilities) req.Depth = packp.DepthCommits(o.Depth) for _, ref := range refs { - req.Want(ref.Hash()) + req.Wants = append(req.Wants, ref.Hash()) } i, err := s.IterReferences() @@ -188,7 +188,7 @@ func (r *Remote) buildRequest( return nil } - req.Have(ref.Hash()) + req.Haves = append(req.Haves, ref.Hash()) return nil }) |