Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit 9e34f68

Browse files
smolamcuadros
authored andcommitted
plumbing/transport: add common tests and fixes. (#136)
* plumbing/transport: add common tests and fixes. * add common test suite for different transport implementations. * fix different behaviour on error handling for ssh and http. fixes issue #123. * support detecting unexisting repositories with SSH + GitHub/Bitbucket (apparently, there is no standard for all SSH servers). * remove ssh.NewClient (only DefaultClient makes sense at the moment). * make ssh.Client and http.Client private. * utils/ioutil: utilities to work with io interfaces. * * transport: test actual objects fetched, not just packfile size. * * fix doc typo. * * improve UploadPackRequest.IsEmpty
1 parent 8966c04 commit 9e34f68

File tree

17 files changed

+512
-240
lines changed

17 files changed

+512
-240
lines changed

plumbing/format/packp/advrefs/decoder.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ func decodePrefix(d *Decoder) decoderStateFn {
8686
return nil
8787
}
8888

89+
// If the repository is empty, we receive a flush here (SSH).
90+
if isFlush(d.line) {
91+
d.err = ErrEmpty
92+
return nil
93+
}
94+
8995
if isPrefix(d.line) {
9096
tmp := make([]byte, len(d.line))
9197
copy(tmp, d.line)
@@ -117,6 +123,12 @@ func isFlush(payload []byte) bool {
117123
// list-of-refs is comming, and the hash will be followed by the first
118124
// advertised ref.
119125
func decodeFirstHash(p *Decoder) decoderStateFn {
126+
// If the repository is empty, we receive a flush here (HTTP).
127+
if isFlush(p.line) {
128+
p.err = ErrEmpty
129+
return nil
130+
}
131+
120132
if len(p.line) < hashSize {
121133
p.error("cannot read hash, pkt-line too short")
122134
return nil

plumbing/format/packp/advrefs/decoder_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,32 @@ func (s *SuiteDecoder) TestEmpty(c *C) {
2626
c.Assert(err, Equals, advrefs.ErrEmpty)
2727
}
2828

29+
func (s *SuiteDecoder) TestEmptyFlush(c *C) {
30+
ar := advrefs.New()
31+
var buf bytes.Buffer
32+
e := pktline.NewEncoder(&buf)
33+
e.Flush()
34+
35+
d := advrefs.NewDecoder(&buf)
36+
37+
err := d.Decode(ar)
38+
c.Assert(err, Equals, advrefs.ErrEmpty)
39+
}
40+
41+
func (s *SuiteDecoder) TestEmptyPrefixFlush(c *C) {
42+
ar := advrefs.New()
43+
var buf bytes.Buffer
44+
e := pktline.NewEncoder(&buf)
45+
e.EncodeString("# service=git-upload-pack")
46+
e.Flush()
47+
e.Flush()
48+
49+
d := advrefs.NewDecoder(&buf)
50+
51+
err := d.Decode(ar)
52+
c.Assert(err, Equals, advrefs.ErrEmpty)
53+
}
54+
2955
func (s *SuiteDecoder) TestShortForHash(c *C) {
3056
payloads := []string{
3157
"6ecf0ef2c2dffb796",

plumbing/transport/client/client_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ func (s *ClientSuite) TestNewClientHTTP(c *C) {
2222

2323
output, err := NewClient(e)
2424
c.Assert(err, IsNil)
25-
c.Assert(typeAsString(output), Equals, "*http.Client")
25+
c.Assert(typeAsString(output), Equals, "*http.client")
2626

2727
e, err = transport.NewEndpoint("https://github.com/src-d/go-git")
2828
c.Assert(err, IsNil)
2929

3030
output, err = NewClient(e)
3131
c.Assert(err, IsNil)
32-
c.Assert(typeAsString(output), Equals, "*http.Client")
32+
c.Assert(typeAsString(output), Equals, "*http.client")
3333
}
3434

3535
func (s *ClientSuite) TestNewClientSSH(c *C) {
@@ -38,7 +38,7 @@ func (s *ClientSuite) TestNewClientSSH(c *C) {
3838

3939
output, err := NewClient(e)
4040
c.Assert(err, IsNil)
41-
c.Assert(typeAsString(output), Equals, "*ssh.Client")
41+
c.Assert(typeAsString(output), Equals, "*ssh.client")
4242
}
4343

4444
func (s *ClientSuite) TestNewClientUnknown(c *C) {

plumbing/transport/common.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ import (
2424

2525
var (
2626
ErrRepositoryNotFound = errors.New("repository not found")
27+
ErrEmptyRemoteRepository = errors.New("remote repository is empty")
2728
ErrAuthorizationRequired = errors.New("authorization required")
2829
ErrEmptyUploadPackRequest = errors.New("empty git-upload-pack given")
2930
ErrInvalidAuthMethod = errors.New("invalid auth method")
31+
32+
ErrAdvertistedReferencesAlreadyCalled = errors.New("cannot call AdvertisedReference twice")
3033
)
3134

3235
const (

plumbing/transport/fetch_pack.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func (i *UploadPackInfo) Decode(r io.Reader) error {
3333
ar := advrefs.New()
3434
if err := d.Decode(ar); err != nil {
3535
if err == advrefs.ErrEmpty {
36-
return plumbing.NewPermanentError(err)
36+
return err
3737
}
3838
return plumbing.NewUnexpectedError(err)
3939
}
@@ -128,6 +128,28 @@ func (r *UploadPackRequest) Have(h ...plumbing.Hash) {
128128
r.Haves = append(r.Haves, h...)
129129
}
130130

131+
func (r *UploadPackRequest) IsEmpty() bool {
132+
return isSubset(r.Wants, r.Haves)
133+
}
134+
135+
func isSubset(needle []plumbing.Hash, haystack []plumbing.Hash) bool {
136+
for _, h := range needle {
137+
found := false
138+
for _, oh := range haystack {
139+
if h == oh {
140+
found = true
141+
break
142+
}
143+
}
144+
145+
if !found {
146+
return false
147+
}
148+
}
149+
150+
return true
151+
}
152+
131153
func (r *UploadPackRequest) String() string {
132154
b, _ := ioutil.ReadAll(r.Reader())
133155
return string(b)

plumbing/transport/fetch_pack_test.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/base64"
66

77
"gopkg.in/src-d/go-git.v4/plumbing"
8+
"gopkg.in/src-d/go-git.v4/plumbing/format/packp/advrefs"
89

910
. "gopkg.in/check.v1"
1011
)
@@ -58,7 +59,7 @@ func (s *UploadPackSuite) TestUploadPackInfoEmpty(c *C) {
5859

5960
i := NewUploadPackInfo()
6061
err := i.Decode(b)
61-
c.Assert(err, ErrorMatches, "permanent.*empty.*")
62+
c.Assert(err, Equals, advrefs.ErrEmpty)
6263
}
6364

6465
func (s *UploadPackSuite) TestUploadPackEncode(c *C) {
@@ -94,3 +95,25 @@ func (s *UploadPackSuite) TestUploadPackRequest(c *C) {
9495
"0009done\n",
9596
)
9697
}
98+
99+
func (s *UploadPackSuite) TestUploadPackRequest_IsEmpty(c *C) {
100+
r := &UploadPackRequest{}
101+
r.Want(plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c"))
102+
r.Want(plumbing.NewHash("2b41ef280fdb67a9b250678686a0c3e03b0a9989"))
103+
r.Have(plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"))
104+
105+
c.Assert(r.IsEmpty(), Equals, false)
106+
107+
r = &UploadPackRequest{}
108+
r.Want(plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c"))
109+
r.Want(plumbing.NewHash("2b41ef280fdb67a9b250678686a0c3e03b0a9989"))
110+
r.Have(plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c"))
111+
112+
c.Assert(r.IsEmpty(), Equals, false)
113+
114+
r = &UploadPackRequest{}
115+
r.Want(plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c"))
116+
r.Have(plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c"))
117+
118+
c.Assert(r.IsEmpty(), Equals, true)
119+
}

plumbing/transport/http/common.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,38 @@ import (
99
"gopkg.in/src-d/go-git.v4/plumbing/transport"
1010
)
1111

12-
type Client struct {
12+
type client struct {
1313
c *http.Client
1414
}
1515

16+
// DefaultClient is the default HTTP client, which uses `http.DefaultClient`.
1617
var DefaultClient = NewClient(nil)
1718

1819
// NewClient creates a new client with a custom net/http client.
1920
// See `InstallProtocol` to install and override default http client.
2021
// Unless a properly initialized client is given, it will fall back into
2122
// `http.DefaultClient`.
23+
//
24+
// Note that for HTTP client cannot distinguist between private repositories and
25+
// unexistent repositories on GitHub. So it returns `ErrAuthorizationRequired`
26+
// for both.
2227
func NewClient(c *http.Client) transport.Client {
2328
if c == nil {
24-
return &Client{http.DefaultClient}
29+
return &client{http.DefaultClient}
2530
}
2631

27-
return &Client{
32+
return &client{
2833
c: c,
2934
}
3035
}
3136

32-
func (c *Client) NewFetchPackSession(ep transport.Endpoint) (
37+
func (c *client) NewFetchPackSession(ep transport.Endpoint) (
3338
transport.FetchPackSession, error) {
3439

3540
return newFetchPackSession(c.c, ep), nil
3641
}
3742

38-
func (c *Client) NewSendPackSession(ep transport.Endpoint) (
43+
func (c *client) NewSendPackSession(ep transport.Endpoint) (
3944
transport.SendPackSession, error) {
4045

4146
return newSendPackSession(c.c, ep), nil

plumbing/transport/http/common_test.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,20 @@ var _ = Suite(&ClientSuite{})
2020

2121
func (s *ClientSuite) SetUpSuite(c *C) {
2222
var err error
23-
s.Endpoint, err = transport.NewEndpoint("https://github.com/git-fixtures/basic")
23+
s.Endpoint, err = transport.NewEndpoint(
24+
"https://github.com/git-fixtures/basic",
25+
)
2426
c.Assert(err, IsNil)
2527
}
2628

2729
func (s *FetchPackSuite) TestNewClient(c *C) {
28-
roundTripper := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
29-
client := &http.Client{Transport: roundTripper}
30-
r := NewClient(client).(*Client)
31-
32-
c.Assert(r.c, Equals, client)
30+
roundTripper := &http.Transport{
31+
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
32+
}
33+
cl := &http.Client{Transport: roundTripper}
34+
r, ok := NewClient(cl).(*client)
35+
c.Assert(ok, Equals, true)
36+
c.Assert(r.c, Equals, cl)
3337
}
3438

3539
func (s *ClientSuite) TestNewBasicAuth(c *C) {
@@ -54,7 +58,8 @@ func (s *ClientSuite) TestNewErrNotFound(c *C) {
5458
}
5559

5660
func (s *ClientSuite) TestNewHTTPError40x(c *C) {
57-
s.testNewHTTPError(c, http.StatusPaymentRequired, "unexpected client error.*")
61+
s.testNewHTTPError(c, http.StatusPaymentRequired,
62+
"unexpected client error.*")
5863
}
5964

6065
func (s *ClientSuite) testNewHTTPError(c *C, code int, msg string) {

plumbing/transport/http/fetch_pack.go

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
package http
22

33
import (
4-
"bufio"
54
"bytes"
65
"fmt"
76
"io"
87
"net/http"
98
"strings"
109

1110
"gopkg.in/src-d/go-git.v4/plumbing"
11+
"gopkg.in/src-d/go-git.v4/plumbing/format/packp/advrefs"
1212
"gopkg.in/src-d/go-git.v4/plumbing/format/packp/pktline"
1313
"gopkg.in/src-d/go-git.v4/plumbing/transport"
14+
"gopkg.in/src-d/go-git.v4/utils/ioutil"
1415
)
1516

1617
type fetchPackSession struct {
1718
*session
19+
advRefsRun bool
1820
}
1921

2022
func newFetchPackSession(c *http.Client,
@@ -31,6 +33,11 @@ func newFetchPackSession(c *http.Client,
3133

3234
func (s *fetchPackSession) AdvertisedReferences() (*transport.UploadPackInfo,
3335
error) {
36+
if s.advRefsRun {
37+
return nil, transport.ErrAdvertistedReferencesAlreadyCalled
38+
}
39+
40+
defer func() { s.advRefsRun = true }()
3441

3542
url := fmt.Sprintf(
3643
"%s/info/refs?service=%s",
@@ -50,15 +57,28 @@ func (s *fetchPackSession) AdvertisedReferences() (*transport.UploadPackInfo,
5057
}
5158

5259
defer res.Body.Close()
60+
5361
if res.StatusCode == http.StatusUnauthorized {
5462
return nil, transport.ErrAuthorizationRequired
5563
}
5664

5765
i := transport.NewUploadPackInfo()
58-
return i, i.Decode(res.Body)
66+
if err := i.Decode(res.Body); err != nil {
67+
if err == advrefs.ErrEmpty {
68+
err = transport.ErrEmptyRemoteRepository
69+
}
70+
71+
return nil, err
72+
}
73+
74+
return i, nil
5975
}
6076

6177
func (s *fetchPackSession) FetchPack(r *transport.UploadPackRequest) (io.ReadCloser, error) {
78+
if r.IsEmpty() {
79+
return nil, transport.ErrEmptyUploadPackRequest
80+
}
81+
6282
url := fmt.Sprintf(
6383
"%s/%s",
6484
s.endpoint.String(), transport.UploadPackServiceName,
@@ -69,20 +89,21 @@ func (s *fetchPackSession) FetchPack(r *transport.UploadPackRequest) (io.ReadClo
6989
return nil, err
7090
}
7191

72-
reader := newBufferedReadCloser(res.Body)
73-
if _, err := reader.Peek(1); err != nil {
74-
if err == io.ErrUnexpectedEOF {
75-
return nil, transport.ErrEmptyUploadPackRequest
76-
}
92+
reader, err := ioutil.NonEmptyReader(res.Body)
93+
if err == ioutil.ErrEmptyReader || err == io.ErrUnexpectedEOF {
94+
return nil, transport.ErrEmptyUploadPackRequest
95+
}
7796

97+
if err != nil {
7898
return nil, err
7999
}
80100

81-
if err := discardResponseInfo(reader); err != nil {
101+
rc := ioutil.NewReadCloser(reader, res.Body)
102+
if err := discardResponseInfo(rc); err != nil {
82103
return nil, err
83104
}
84105

85-
return reader, nil
106+
return rc, nil
86107
}
87108

88109
// Close does nothing.
@@ -140,16 +161,3 @@ func (s *fetchPackSession) applyHeadersToRequest(req *http.Request, content *str
140161
req.Header.Add("Content-Length", string(content.Len()))
141162
}
142163
}
143-
144-
type bufferedReadCloser struct {
145-
*bufio.Reader
146-
closer io.Closer
147-
}
148-
149-
func newBufferedReadCloser(r io.ReadCloser) *bufferedReadCloser {
150-
return &bufferedReadCloser{bufio.NewReader(r), r}
151-
}
152-
153-
func (r *bufferedReadCloser) Close() error {
154-
return r.closer.Close()
155-
}

0 commit comments

Comments
 (0)