diff options
author | Máximo Cuadros <mcuadros@gmail.com> | 2015-10-23 00:34:40 +0200 |
---|---|---|
committer | Máximo Cuadros <mcuadros@gmail.com> | 2015-10-23 00:34:40 +0200 |
commit | b359f11ea09e642695edcd114b463da4395b10c1 (patch) | |
tree | 6f84c4693429a3815b7523e957801cdb420abb40 | |
parent | 6f43e8933ba3c04072d5d104acc6118aac3e52ee (diff) | |
download | go-git-b359f11ea09e642695edcd114b463da4395b10c1.tar.gz |
clients: supporting diferent protocols
-rw-r--r-- | clients/common.go | 15 | ||||
-rw-r--r-- | clients/common/common.go | 132 | ||||
-rw-r--r-- | clients/common/common_test.go | 37 | ||||
-rw-r--r-- | clients/http/common.go | 28 | ||||
-rw-r--r-- | clients/http/common_test.go | 9 | ||||
-rw-r--r-- | clients/http/git_upload_pack.go | 78 | ||||
-rw-r--r-- | clients/http/git_upload_pack_test.go | 32 |
7 files changed, 331 insertions, 0 deletions
diff --git a/clients/common.go b/clients/common.go new file mode 100644 index 0000000..720de86 --- /dev/null +++ b/clients/common.go @@ -0,0 +1,15 @@ +package clients + +import ( + "gopkg.in/src-d/go-git.v2/clients/common" + "gopkg.in/src-d/go-git.v2/clients/http" +) + +type GitUploadPackService interface { + Connect(url common.Endpoint) error + Info() (*common.GitUploadPackInfo, error) +} + +func NewGitUploadPackService() GitUploadPackService { + return http.NewGitUploadPackService() +} diff --git a/clients/common/common.go b/clients/common/common.go new file mode 100644 index 0000000..3527d7c --- /dev/null +++ b/clients/common/common.go @@ -0,0 +1,132 @@ +package common + +import ( + "fmt" + "net/url" + "strings" + + "gopkg.in/sourcegraph/go-vcsurl.v1" + "gopkg.in/src-d/go-git.v2/pktline" +) + +const GitUploadPackServiceName = "git-upload-pack" + +type Endpoint string + +func NewEndpoint(url string) (Endpoint, error) { + vcs, err := vcsurl.Parse(url) + if err != nil { + return "", err + } + + link := vcs.Link() + if !strings.HasSuffix(link, ".git") { + link += ".git" + } + + return Endpoint(link), nil +} + +func (e Endpoint) Service(name string) string { + return fmt.Sprintf("%s/info/refs?service=%s", e, name) +} + +// Capabilities contains all the server capabilities +// https://github.com/git/git/blob/master/Documentation/technical/protocol-capabilities.txt +type Capabilities map[string][]string + +func parseCapabilities(line string) Capabilities { + values, _ := url.ParseQuery(strings.Replace(line, " ", "&", -1)) + + return Capabilities(values) +} + +// Supports returns true if capability is preent +func (r Capabilities) Supports(capability string) bool { + _, ok := r[capability] + return ok +} + +// Get returns the values for a capability +func (r Capabilities) Get(capability string) []string { + return r[capability] +} + +// SymbolicReference returns the reference for a given symbolic reference +func (r Capabilities) SymbolicReference(sym string) string { + if !r.Supports("symref") { + return "" + } + + for _, symref := range r.Get("symref") { + parts := strings.Split(symref, ":") + if len(parts) != 2 { + continue + } + + if parts[0] == sym { + return parts[1] + } + } + + return "" +} + +type GitUploadPackInfo struct { + Capabilities Capabilities + Branches map[string]string +} + +func NewGitUploadPackInfo(d *pktline.Decoder) (*GitUploadPackInfo, error) { + info := &GitUploadPackInfo{} + if err := info.read(d); err != nil { + return nil, err + } + + return info, nil +} + +func (r *GitUploadPackInfo) read(d *pktline.Decoder) error { + lines, err := d.ReadAll() + if err != nil { + return err + } + + r.Branches = map[string]string{} + for _, line := range lines { + if !r.isValidLine(line) { + continue + } + + if r.Capabilities == nil { + r.Capabilities = parseCapabilities(line) + continue + } + + r.readLine(line) + } + + return nil +} + +func (r *GitUploadPackInfo) isValidLine(line string) bool { + if line[0] == '#' { + return false + } + + return true +} + +func (r *GitUploadPackInfo) readLine(line string) { + commit, branch := r.getCommitAndBranch(line) + r.Branches[branch] = commit +} + +func (r *GitUploadPackInfo) getCommitAndBranch(line string) (string, string) { + parts := strings.Split(strings.Trim(line, " \n"), " ") + if len(parts) != 2 { + return "", "" + } + + return parts[0], parts[1] +} diff --git a/clients/common/common_test.go b/clients/common/common_test.go new file mode 100644 index 0000000..0b9b60a --- /dev/null +++ b/clients/common/common_test.go @@ -0,0 +1,37 @@ +package common + +import ( + "testing" + + . "gopkg.in/check.v1" +) + +func Test(t *testing.T) { TestingT(t) } + +type SuiteCommon struct{} + +var _ = Suite(&SuiteCommon{}) + +func (s *SuiteCommon) TestNewEndpoint(c *C) { + e, err := NewEndpoint("git@github.com:user/repository.git") + c.Assert(err, IsNil) + c.Assert(e, Equals, Endpoint("https://github.com/user/repository.git")) +} + +func (s *SuiteCommon) TestNewEndpointWrongForgat(c *C) { + e, err := NewEndpoint("foo") + c.Assert(err, Not(IsNil)) + c.Assert(e, Equals, Endpoint("")) +} + +func (s *SuiteCommon) TestEndpointService(c *C) { + e, _ := NewEndpoint("git@github.com:user/repository.git") + c.Assert(e.Service("foo"), Equals, "https://github.com/user/repository.git/info/refs?service=foo") +} + +const CapabilitiesFixture = "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEADmulti_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress include-tag multi_ack_detailed no-done symref=HEAD:refs/heads/master agent=git/2:2.4.8~dbussink-fix-enterprise-tokens-compilation-1167-gc7006cf" + +func (s *SuiteCommon) TestCapabilitiesSymbolicReference(c *C) { + cap := parseCapabilities(CapabilitiesFixture) + c.Assert(cap.SymbolicReference("HEAD"), Equals, "refs/heads/master") +} diff --git a/clients/http/common.go b/clients/http/common.go new file mode 100644 index 0000000..a0f012e --- /dev/null +++ b/clients/http/common.go @@ -0,0 +1,28 @@ +package http + +import ( + "fmt" + "net/http" +) + +type HTTPError struct { + Response *http.Response +} + +func NewHTTPError(r *http.Response) *HTTPError { + if r.StatusCode >= 200 && r.StatusCode < 300 { + return nil + } + + return &HTTPError{r} +} + +func (e *HTTPError) StatusCode() int { + return e.Response.StatusCode +} + +func (e *HTTPError) Error() string { + return fmt.Sprintf("Error requesting %q status code: %d", + e.Response.Request.URL, e.Response.StatusCode, + ) +} diff --git a/clients/http/common_test.go b/clients/http/common_test.go new file mode 100644 index 0000000..b672801 --- /dev/null +++ b/clients/http/common_test.go @@ -0,0 +1,9 @@ +package http + +import ( + "testing" + + . "gopkg.in/check.v1" +) + +func Test(t *testing.T) { TestingT(t) } diff --git a/clients/http/git_upload_pack.go b/clients/http/git_upload_pack.go new file mode 100644 index 0000000..96dbdce --- /dev/null +++ b/clients/http/git_upload_pack.go @@ -0,0 +1,78 @@ +package http + +import ( + "io" + "net/http" + "strings" + + "gopkg.in/src-d/go-git.v2/clients/common" + "gopkg.in/src-d/go-git.v2/pktline" +) + +type GitUploadPackService struct { + Client *http.Client + + endpoint common.Endpoint +} + +func NewGitUploadPackService() *GitUploadPackService { + return &GitUploadPackService{ + Client: http.DefaultClient, + } +} + +func (s *GitUploadPackService) Connect(url common.Endpoint) error { + s.endpoint = url + + return nil +} + +func (s *GitUploadPackService) Info() (*common.GitUploadPackInfo, error) { + res, err := s.doRequest("GET", common.GitUploadPackServiceName, nil) + if err != nil { + return nil, err + } + + defer res.Body.Close() + + dec := pktline.NewDecoder(res.Body) + return common.NewGitUploadPackInfo(dec) +} + +func (s *GitUploadPackService) doRequest(method, service string, content *strings.Reader) (*http.Response, error) { + var body io.Reader + if content != nil { + body = content + } + + req, err := http.NewRequest(method, s.endpoint.Service(service), body) + if err != nil { + return nil, err + } + + s.applyHeadersToRequest(req, content) + + res, err := s.Client.Do(req) + if err != nil { + return nil, err + } + + if err := NewHTTPError(res); err != nil { + return nil, err + } + + return res, nil +} + +func (s *GitUploadPackService) applyHeadersToRequest(req *http.Request, content *strings.Reader) { + req.Header.Add("User-Agent", "git/1.0") + req.Header.Add("Host", "github.com") + + if content == nil { + req.Header.Add("Accept", "*/*") + } else { + req.Header.Add("Accept", "application/x-git-upload-pack-result") + req.Header.Add("Content-Type", "application/x-git-upload-pack-request") + req.Header.Add("Content-Length", string(content.Len())) + } +} diff --git a/clients/http/git_upload_pack_test.go b/clients/http/git_upload_pack_test.go new file mode 100644 index 0000000..b478445 --- /dev/null +++ b/clients/http/git_upload_pack_test.go @@ -0,0 +1,32 @@ +package http + +import . "gopkg.in/check.v1" + +type SuiteRemote struct{} + +var _ = Suite(&SuiteRemote{}) + +const RepositoryFixture = "https://github.com/tyba/git-fixture" + +func (s *SuiteRemote) TestConnect(c *C) { + r := NewGitUploadPackService() + c.Assert(r.Connect(RepositoryFixture), IsNil) +} + +func (s *SuiteRemote) TestDefaultBranch(c *C) { + r := NewGitUploadPackService() + c.Assert(r.Connect(RepositoryFixture), IsNil) + + info, err := r.Info() + c.Assert(err, IsNil) + c.Assert(info.Capabilities.SymbolicReference("HEAD"), Equals, "refs/heads/master") +} + +func (s *SuiteRemote) TestCapabilities(c *C) { + r := NewGitUploadPackService() + c.Assert(r.Connect(RepositoryFixture), IsNil) + + info, err := r.Info() + c.Assert(err, IsNil) + c.Assert(info.Capabilities.Get("agent"), HasLen, 1) +} |