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

transport: http push #432

Merged
merged 3 commits into from
Jul 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions plumbing/transport/http/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
75 changes: 67 additions & 8 deletions plumbing/transport/http/receive_pack.go
Original file line number Diff line number Diff line change
@@ -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
}
122 changes: 122 additions & 0 deletions plumbing/transport/http/receive_pack_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading