diff options
author | Máximo Cuadros <mcuadros@gmail.com> | 2017-07-05 03:09:08 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-07-05 03:09:08 -0700 |
commit | 5354ebc084d5300cd8d371a837a0d9dab408c561 (patch) | |
tree | b7a06e810b6a4acb47b27bdd39b0ded51c89031e | |
parent | ce6f5b7c82fc6c2c4d41880ed6b26f921dd9c1c3 (diff) | |
parent | 10bf320dce4b2a80394b24db19fad89e1da953f7 (diff) | |
download | go-git-5354ebc084d5300cd8d371a837a0d9dab408c561.tar.gz |
Merge pull request #432 from ajnavarro/feature/http-push
transport: http push
-rw-r--r-- | plumbing/transport/http/common.go | 73 | ||||
-rw-r--r-- | plumbing/transport/http/receive_pack.go | 75 | ||||
-rw-r--r-- | plumbing/transport/http/receive_pack_test.go | 122 | ||||
-rw-r--r-- | plumbing/transport/http/upload_pack.go | 73 |
4 files changed, 266 insertions, 77 deletions
diff --git a/plumbing/transport/http/common.go b/plumbing/transport/http/common.go index 04b6121..6b40d42 100644 --- a/plumbing/transport/http/common.go +++ b/plumbing/transport/http/common.go @@ -2,14 +2,69 @@ package http import ( + "bytes" "fmt" "net/http" + "strconv" "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/transport" ) +// it requires a bytes.Buffer, because we need to know the length +func applyHeadersToRequest(req *http.Request, content *bytes.Buffer, host string, requestType string) { + req.Header.Add("User-Agent", "git/1.0") + req.Header.Add("Host", host) // host:port + + if content == nil { + req.Header.Add("Accept", "*/*") + return + } + + req.Header.Add("Accept", fmt.Sprintf("application/x-%s-result", requestType)) + req.Header.Add("Content-Type", fmt.Sprintf("application/x-%s-request", requestType)) + req.Header.Add("Content-Length", strconv.Itoa(content.Len())) +} + +func advertisedReferences(s *session, serviceName string) (*packp.AdvRefs, error) { + url := fmt.Sprintf( + "%s/info/refs?service=%s", + s.endpoint.String(), serviceName, + ) + + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, err + } + + s.applyAuthToRequest(req) + applyHeadersToRequest(req, nil, s.endpoint.Host(), serviceName) + res, err := s.client.Do(req) + if err != nil { + return nil, err + } + + if err := NewErr(res); err != nil { + _ = res.Body.Close() + return nil, err + } + + ar := packp.NewAdvRefs() + if err := ar.Decode(res.Body); err != nil { + if err == packp.ErrEmptyAdvRefs { + err = transport.ErrEmptyRemoteRepository + } + + return nil, err + } + + transport.FilterUnsupportedCapabilities(ar.Capabilities) + s.advRefs = ar + + return ar, nil +} + type client struct { c *http.Client } @@ -54,6 +109,24 @@ type session struct { advRefs *packp.AdvRefs } +func newSession(c *http.Client, ep transport.Endpoint, auth transport.AuthMethod) (*session, error) { + s := &session{ + auth: basicAuthFromEndpoint(ep), + client: c, + endpoint: ep, + } + if auth != nil { + a, ok := auth.(AuthMethod) + if !ok { + return nil, transport.ErrInvalidAuthMethod + } + + s.auth = a + } + + return s, nil +} + func (*session) Close() error { return nil } diff --git a/plumbing/transport/http/receive_pack.go b/plumbing/transport/http/receive_pack.go index 7a37049..b8489a7 100644 --- a/plumbing/transport/http/receive_pack.go +++ b/plumbing/transport/http/receive_pack.go @@ -1,30 +1,89 @@ package http import ( - "errors" + "bytes" + "fmt" + "io" "net/http" + "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/transport" + "gopkg.in/src-d/go-git.v4/utils/ioutil" ) -var errReceivePackNotSupported = errors.New("receive-pack not supported yet") - type rpSession struct { *session } func newReceivePackSession(c *http.Client, ep transport.Endpoint, auth transport.AuthMethod) (transport.ReceivePackSession, error) { - return &rpSession{&session{}}, nil + s, err := newSession(c, ep, auth) + return &rpSession{s}, err } func (s *rpSession) AdvertisedReferences() (*packp.AdvRefs, error) { - - return nil, errReceivePackNotSupported + return advertisedReferences(s.session, transport.ReceivePackServiceName) } -func (s *rpSession) ReceivePack(*packp.ReferenceUpdateRequest) ( +func (s *rpSession) ReceivePack(req *packp.ReferenceUpdateRequest) ( *packp.ReportStatus, error) { + url := fmt.Sprintf( + "%s/%s", + s.endpoint.String(), transport.ReceivePackServiceName, + ) + + buf := bytes.NewBuffer(nil) + if err := req.Encode(buf); err != nil { + return nil, err + } + + res, err := s.doRequest(http.MethodPost, url, buf) + if err != nil { + return nil, err + } + + r, err := ioutil.NonEmptyReader(res.Body) + if err == ioutil.ErrEmptyReader { + return nil, nil + } + + if err != nil { + return nil, err + } + + rc := ioutil.NewReadCloser(r, res.Body) + + report := packp.NewReportStatus() + if err := report.Decode(rc); err != nil { + return nil, err + } + + return report, report.Error() +} + +func (s *rpSession) doRequest(method, url string, content *bytes.Buffer) (*http.Response, error) { + var body io.Reader + if content != nil { + body = content + } + + req, err := http.NewRequest(method, url, body) + if err != nil { + return nil, plumbing.NewPermanentError(err) + } + + applyHeadersToRequest(req, content, s.endpoint.Host(), transport.ReceivePackServiceName) + s.applyAuthToRequest(req) + + res, err := s.client.Do(req) + if err != nil { + return nil, plumbing.NewUnexpectedError(err) + } + + if err := NewErr(res); err != nil { + _ = res.Body.Close() + return nil, err + } - return nil, errReceivePackNotSupported + return res, nil } diff --git a/plumbing/transport/http/receive_pack_test.go b/plumbing/transport/http/receive_pack_test.go new file mode 100644 index 0000000..d870e5d --- /dev/null +++ b/plumbing/transport/http/receive_pack_test.go @@ -0,0 +1,122 @@ +package http + +import ( + "fmt" + "io" + "io/ioutil" + "log" + "net" + "net/http" + "net/http/cgi" + "os" + "os/exec" + "path/filepath" + "strings" + + "gopkg.in/src-d/go-git.v4/plumbing/transport" + "gopkg.in/src-d/go-git.v4/plumbing/transport/test" + + "github.com/src-d/go-git-fixtures" + . "gopkg.in/check.v1" +) + +type ReceivePackSuite struct { + test.ReceivePackSuite + fixtures.Suite + + base string +} + +var _ = Suite(&ReceivePackSuite{}) + +func (s *ReceivePackSuite) SetUpTest(c *C) { + s.ReceivePackSuite.Client = DefaultClient + + port, err := freePort() + c.Assert(err, IsNil) + + base, err := ioutil.TempDir(os.TempDir(), "go-git-http-backend-test") + c.Assert(err, IsNil) + s.base = base + + host := fmt.Sprintf("localhost_%d", port) + interpolatedBase := filepath.Join(base, host) + err = os.MkdirAll(interpolatedBase, 0755) + c.Assert(err, IsNil) + + dotgit := fixtures.Basic().One().DotGit().Root() + prepareRepo(c, dotgit) + err = os.Rename(dotgit, filepath.Join(interpolatedBase, "basic.git")) + c.Assert(err, IsNil) + + ep, err := transport.NewEndpoint(fmt.Sprintf("http://localhost:%d/basic.git", port)) + c.Assert(err, IsNil) + s.ReceivePackSuite.Endpoint = ep + + dotgit = fixtures.ByTag("empty").One().DotGit().Root() + prepareRepo(c, dotgit) + err = os.Rename(dotgit, filepath.Join(interpolatedBase, "empty.git")) + c.Assert(err, IsNil) + + ep, err = transport.NewEndpoint(fmt.Sprintf("http://localhost:%d/empty.git", port)) + c.Assert(err, IsNil) + s.ReceivePackSuite.EmptyEndpoint = ep + + ep, err = transport.NewEndpoint(fmt.Sprintf("http://localhost:%d/non-existent.git", port)) + c.Assert(err, IsNil) + s.ReceivePackSuite.NonExistentEndpoint = ep + + cmd := exec.Command("git", "--exec-path") + out, err := cmd.CombinedOutput() + c.Assert(err, IsNil) + p := filepath.Join(strings.Trim(string(out), "\n"), "git-http-backend") + + h := &cgi.Handler{ + Path: p, + Env: []string{"GIT_HTTP_EXPORT_ALL=true", fmt.Sprintf("GIT_PROJECT_ROOT=%s", interpolatedBase)}, + } + + go func() { + log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), h)) + }() +} + +func (s *ReceivePackSuite) TearDownTest(c *C) { + err := os.RemoveAll(s.base) + c.Assert(err, IsNil) +} + +func freePort() (int, error) { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + return 0, err + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return 0, err + } + + return l.Addr().(*net.TCPAddr).Port, l.Close() +} + +const bareConfig = `[core] +repositoryformatversion = 0 +filemode = true +bare = true +[http] +receivepack = true` + +func prepareRepo(c *C, path string) { + // git-receive-pack refuses to update refs/heads/master on non-bare repo + // so we ensure bare repo config. + config := filepath.Join(path, "config") + if _, err := os.Stat(config); err == nil { + f, err := os.OpenFile(config, os.O_TRUNC|os.O_WRONLY, 0) + c.Assert(err, IsNil) + content := strings.NewReader(bareConfig) + _, err = io.Copy(f, content) + c.Assert(err, IsNil) + c.Assert(f.Close(), IsNil) + } +} diff --git a/plumbing/transport/http/upload_pack.go b/plumbing/transport/http/upload_pack.go index 2d1ea45..b1181b6 100644 --- a/plumbing/transport/http/upload_pack.go +++ b/plumbing/transport/http/upload_pack.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "net/http" - "strconv" "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/format/pktline" @@ -20,62 +19,13 @@ type upSession struct { } func newUploadPackSession(c *http.Client, ep transport.Endpoint, auth transport.AuthMethod) (transport.UploadPackSession, error) { - s := &session{ - auth: basicAuthFromEndpoint(ep), - client: c, - endpoint: ep, - } - if auth != nil { - a, ok := auth.(AuthMethod) - if !ok { - return nil, transport.ErrInvalidAuthMethod - } + s, err := newSession(c, ep, auth) - s.auth = a - } - - return &upSession{session: s}, nil + return &upSession{s}, err } func (s *upSession) AdvertisedReferences() (*packp.AdvRefs, error) { - if s.advRefs != nil { - return s.advRefs, nil - } - - url := fmt.Sprintf( - "%s/info/refs?service=%s", - s.endpoint.String(), transport.UploadPackServiceName, - ) - - req, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - return nil, err - } - - s.applyAuthToRequest(req) - s.applyHeadersToRequest(req, nil) - res, err := s.client.Do(req) - if err != nil { - return nil, err - } - - if err := NewErr(res); err != nil { - _ = res.Body.Close() - return nil, err - } - - ar := packp.NewAdvRefs() - if err := ar.Decode(res.Body); err != nil { - if err == packp.ErrEmptyAdvRefs { - err = transport.ErrEmptyRemoteRepository - } - - return nil, err - } - - transport.FilterUnsupportedCapabilities(ar.Capabilities) - s.advRefs = ar - return ar, nil + return advertisedReferences(s.session, transport.UploadPackServiceName) } func (s *upSession) UploadPack(req *packp.UploadPackRequest) (*packp.UploadPackResponse, error) { @@ -131,7 +81,7 @@ func (s *upSession) doRequest(method, url string, content *bytes.Buffer) (*http. return nil, plumbing.NewPermanentError(err) } - s.applyHeadersToRequest(req, content) + applyHeadersToRequest(req, content, s.endpoint.Host(), transport.UploadPackServiceName) s.applyAuthToRequest(req) res, err := s.client.Do(req) @@ -147,21 +97,6 @@ func (s *upSession) doRequest(method, url string, content *bytes.Buffer) (*http. return res, nil } -// it requires a bytes.Buffer, because we need to know the length -func (s *upSession) applyHeadersToRequest(req *http.Request, content *bytes.Buffer) { - req.Header.Add("User-Agent", "git/1.0") - req.Header.Add("Host", s.endpoint.Host()) // host:port - - if content == nil { - req.Header.Add("Accept", "*/*") - return - } - - 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", strconv.Itoa(content.Len())) -} - func uploadPackRequestToReader(req *packp.UploadPackRequest) (*bytes.Buffer, error) { buf := bytes.NewBuffer(nil) e := pktline.NewEncoder(buf) |