aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMáximo Cuadros <mcuadros@gmail.com>2015-10-23 00:34:40 +0200
committerMáximo Cuadros <mcuadros@gmail.com>2015-10-23 00:34:40 +0200
commitb359f11ea09e642695edcd114b463da4395b10c1 (patch)
tree6f84c4693429a3815b7523e957801cdb420abb40
parent6f43e8933ba3c04072d5d104acc6118aac3e52ee (diff)
downloadgo-git-b359f11ea09e642695edcd114b463da4395b10c1.tar.gz
clients: supporting diferent protocols
-rw-r--r--clients/common.go15
-rw-r--r--clients/common/common.go132
-rw-r--r--clients/common/common_test.go37
-rw-r--r--clients/http/common.go28
-rw-r--r--clients/http/common_test.go9
-rw-r--r--clients/http/git_upload_pack.go78
-rw-r--r--clients/http/git_upload_pack_test.go32
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)
+}