diff options
Diffstat (limited to 'plumbing/transport')
-rw-r--r-- | plumbing/transport/common.go | 9 | ||||
-rw-r--r-- | plumbing/transport/file/common.go | 4 | ||||
-rw-r--r-- | plumbing/transport/file/common_test.go | 31 | ||||
-rw-r--r-- | plumbing/transport/file/fetch_pack_test.go | 8 | ||||
-rw-r--r-- | plumbing/transport/file/send_pack_test.go | 81 | ||||
-rw-r--r-- | plumbing/transport/http/send_pack.go | 5 | ||||
-rw-r--r-- | plumbing/transport/internal/common/common.go | 55 | ||||
-rw-r--r-- | plumbing/transport/test/fetch_pack.go (renamed from plumbing/transport/test/common.go) | 0 | ||||
-rw-r--r-- | plumbing/transport/test/send_pack.go | 285 |
9 files changed, 463 insertions, 15 deletions
diff --git a/plumbing/transport/common.go b/plumbing/transport/common.go index 41813bf..83fd5a3 100644 --- a/plumbing/transport/common.go +++ b/plumbing/transport/common.go @@ -49,6 +49,8 @@ type Session interface { SetAuth(auth AuthMethod) error // AdvertisedReferences retrieves the advertised references for a // repository. + // If the repository does not exist, returns ErrRepositoryNotFound. + // If the repository exists, but is empty, returns ErrEmptyRemoteRepository. AdvertisedReferences() (*packp.AdvRefs, error) io.Closer } @@ -75,10 +77,9 @@ type FetchPackSession interface { // In that order. type SendPackSession interface { Session - // UpdateReferences sends an update references request and returns a - // writer to be used for packfile writing. - //TODO: Complete signature. - SendPack() (io.WriteCloser, error) + // UpdateReferences sends an update references request and a packfile + // reader and returns a ReportStatus and error. + SendPack(*packp.ReferenceUpdateRequest) (*packp.ReportStatus, error) } type Endpoint url.URL diff --git a/plumbing/transport/file/common.go b/plumbing/transport/file/common.go index 8697121..e7d18b2 100644 --- a/plumbing/transport/file/common.go +++ b/plumbing/transport/file/common.go @@ -69,6 +69,10 @@ func (c *command) StdoutPipe() (io.Reader, error) { // Close waits for the command to exit. func (c *command) Close() error { + if c.closed { + return nil + } + return c.cmd.Process.Kill() } diff --git a/plumbing/transport/file/common_test.go b/plumbing/transport/file/common_test.go index 94ca4c9..220df3d 100644 --- a/plumbing/transport/file/common_test.go +++ b/plumbing/transport/file/common_test.go @@ -1,9 +1,40 @@ package file import ( + "fmt" + "io" + "os" + "strings" "testing" + "gopkg.in/src-d/go-git.v4/plumbing/transport" + . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } + +const bareConfig = `[core] +repositoryformatversion = 0 +filemode = true +bare = true` + +func prepareRepo(c *C, path string) transport.Endpoint { + url := fmt.Sprintf("file://%s", path) + ep, err := transport.NewEndpoint(url) + c.Assert(err, IsNil) + + // git-receive-pack refuses to update refs/heads/master on non-bare repo + // so we ensure bare repo config. + config := fmt.Sprintf("%s/config", path) + 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) + } + + return ep +} diff --git a/plumbing/transport/file/fetch_pack_test.go b/plumbing/transport/file/fetch_pack_test.go index 7a23285..25e3fef 100644 --- a/plumbing/transport/file/fetch_pack_test.go +++ b/plumbing/transport/file/fetch_pack_test.go @@ -76,3 +76,11 @@ func (s *FetchPackSuite) TestMalformedInputNoErrors(c *C) { c.Assert(err, NotNil) c.Assert(ar, IsNil) } + +func (s *FetchPackSuite) TestNonExistentCommand(c *C) { + cmd := "/non-existent-git" + client := NewClient(cmd, cmd) + session, err := client.NewFetchPackSession(s.Endpoint) + c.Assert(err, ErrorMatches, ".*no such file or directory.*") + c.Assert(session, IsNil) +} diff --git a/plumbing/transport/file/send_pack_test.go b/plumbing/transport/file/send_pack_test.go new file mode 100644 index 0000000..fc7ea35 --- /dev/null +++ b/plumbing/transport/file/send_pack_test.go @@ -0,0 +1,81 @@ +package file + +import ( + "os" + "os/exec" + + "gopkg.in/src-d/go-git.v4/fixtures" + "gopkg.in/src-d/go-git.v4/plumbing/transport/test" + + . "gopkg.in/check.v1" +) + +type SendPackSuite struct { + fixtures.Suite + test.SendPackSuite +} + +var _ = Suite(&SendPackSuite{}) + +func (s *SendPackSuite) SetUpSuite(c *C) { + s.Suite.SetUpSuite(c) + + if err := exec.Command("git", "--version").Run(); err != nil { + c.Skip("git command not found") + } + + s.SendPackSuite.Client = DefaultClient +} + +func (s *SendPackSuite) SetUpTest(c *C) { + fixture := fixtures.Basic().One() + path := fixture.DotGit().Base() + s.Endpoint = prepareRepo(c, path) + + fixture = fixtures.ByTag("empty").One() + path = fixture.DotGit().Base() + s.EmptyEndpoint = prepareRepo(c, path) + + s.NonExistentEndpoint = prepareRepo(c, "/non-existent") +} + +func (s *SendPackSuite) TearDownTest(c *C) { + s.Suite.TearDownSuite(c) +} + +// TODO: fix test +func (s *SendPackSuite) TestCommandNoOutput(c *C) { + c.Skip("failing test") + + if _, err := os.Stat("/bin/true"); os.IsNotExist(err) { + c.Skip("/bin/true not found") + } + + client := NewClient("true", "true") + session, err := client.NewSendPackSession(s.Endpoint) + c.Assert(err, IsNil) + ar, err := session.AdvertisedReferences() + c.Assert(err, IsNil) + c.Assert(ar, IsNil) +} + +func (s *SendPackSuite) TestMalformedInputNoErrors(c *C) { + if _, err := os.Stat("/usr/bin/yes"); os.IsNotExist(err) { + c.Skip("/usr/bin/yes not found") + } + + client := NewClient("yes", "yes") + session, err := client.NewSendPackSession(s.Endpoint) + c.Assert(err, IsNil) + ar, err := session.AdvertisedReferences() + c.Assert(err, NotNil) + c.Assert(ar, IsNil) +} + +func (s *SendPackSuite) TestNonExistentCommand(c *C) { + cmd := "/non-existent-git" + client := NewClient(cmd, cmd) + session, err := client.NewSendPackSession(s.Endpoint) + c.Assert(err, ErrorMatches, ".*no such file or directory.*") + c.Assert(session, IsNil) +} diff --git a/plumbing/transport/http/send_pack.go b/plumbing/transport/http/send_pack.go index 5e3b2bb..43464ae 100644 --- a/plumbing/transport/http/send_pack.go +++ b/plumbing/transport/http/send_pack.go @@ -2,7 +2,6 @@ package http import ( "errors" - "io" "net/http" "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp" @@ -24,6 +23,8 @@ func (s *sendPackSession) AdvertisedReferences() (*packp.AdvRefs, error) { return nil, errSendPackNotSupported } -func (s *sendPackSession) SendPack() (io.WriteCloser, error) { +func (s *sendPackSession) SendPack(*packp.ReferenceUpdateRequest) ( + *packp.ReportStatus, error) { + return nil, errSendPackNotSupported } diff --git a/plumbing/transport/internal/common/common.go b/plumbing/transport/internal/common/common.go index 12f4995..f0e1691 100644 --- a/plumbing/transport/internal/common/common.go +++ b/plumbing/transport/internal/common/common.go @@ -15,6 +15,7 @@ import ( "gopkg.in/src-d/go-git.v4/plumbing/format/pktline" "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp" + "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability" "gopkg.in/src-d/go-git.v4/plumbing/transport" "gopkg.in/src-d/go-git.v4/utils/ioutil" ) @@ -91,7 +92,7 @@ func (c *client) NewFetchPackSession(ep transport.Endpoint) ( func (c *client) NewSendPackSession(ep transport.Endpoint) ( transport.SendPackSession, error) { - return nil, errors.New("git send-pack not supported") + return c.newSession(transport.ReceivePackServiceName, ep) } type session struct { @@ -99,10 +100,11 @@ type session struct { Stdout io.Reader Command Command - advRefs *packp.AdvRefs - packRun bool - finished bool - errLines chan string + isReceivePack bool + advRefs *packp.AdvRefs + packRun bool + finished bool + errLines chan string } func (c *client) newSession(s string, ep transport.Endpoint) (*session, error) { @@ -140,10 +142,11 @@ func (c *client) newSession(s string, ep transport.Endpoint) (*session, error) { }() return &session{ - Stdin: stdin, - Stdout: stdout, - Command: cmd, - errLines: errLines, + Stdin: stdin, + Stdout: stdout, + Command: cmd, + errLines: errLines, + isReceivePack: s == transport.ReceivePackServiceName, }, nil } @@ -174,6 +177,11 @@ func (s *session) AdvertisedReferences() (*packp.AdvRefs, error) { // advertised-references message. But valid. That is, it // includes at least a flush. if err == packp.ErrEmptyAdvRefs { + // Empty repositories are valid for git-receive-pack. + if s.isReceivePack { + return ar, nil + } + if err := s.finish(); err != nil { return nil, err } @@ -229,6 +237,35 @@ func (s *session) FetchPack(req *packp.UploadPackRequest) (*packp.UploadPackResp return DecodeUploadPackResponse(rc, req) } +func (s *session) SendPack(req *packp.ReferenceUpdateRequest) (*packp.ReportStatus, error) { + if _, err := s.AdvertisedReferences(); err != nil { + return nil, err + } + + s.packRun = true + + if err := req.Encode(s.Stdin); err != nil { + return nil, err + } + + if !req.Capabilities.Supports(capability.ReportStatus) { + // If we have neither report-status or sideband, we can only + // check return value error. + return nil, s.Command.Wait() + } + + report := packp.NewReportStatus() + if err := report.Decode(s.Stdout); err != nil { + return nil, err + } + + if !report.Ok() { + return report, fmt.Errorf("report status: %s", report.UnpackStatus) + } + + return report, s.Command.Wait() +} + func (s *session) finish() error { if s.finished { return nil diff --git a/plumbing/transport/test/common.go b/plumbing/transport/test/fetch_pack.go index 2984154..2984154 100644 --- a/plumbing/transport/test/common.go +++ b/plumbing/transport/test/fetch_pack.go diff --git a/plumbing/transport/test/send_pack.go b/plumbing/transport/test/send_pack.go new file mode 100644 index 0000000..94a1150 --- /dev/null +++ b/plumbing/transport/test/send_pack.go @@ -0,0 +1,285 @@ +// Package test implements common test suite for different transport +// implementations. +// +package test + +import ( + "bytes" + "io" + "io/ioutil" + + "gopkg.in/src-d/go-git.v4/fixtures" + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/format/packfile" + "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp" + "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability" + "gopkg.in/src-d/go-git.v4/plumbing/transport" + "gopkg.in/src-d/go-git.v4/storage/memory" + + . "gopkg.in/check.v1" +) + +type SendPackSuite struct { + Endpoint transport.Endpoint + EmptyEndpoint transport.Endpoint + NonExistentEndpoint transport.Endpoint + Client transport.Client +} + +func (s *SendPackSuite) TestInfoEmpty(c *C) { + r, err := s.Client.NewSendPackSession(s.EmptyEndpoint) + c.Assert(err, IsNil) + defer func() { c.Assert(r.Close(), IsNil) }() + info, err := r.AdvertisedReferences() + c.Assert(err, IsNil) + c.Assert(info.Head, IsNil) +} + +func (s *SendPackSuite) TestInfoNotExists(c *C) { + r, err := s.Client.NewSendPackSession(s.NonExistentEndpoint) + c.Assert(err, IsNil) + defer func() { c.Assert(r.Close(), IsNil) }() + info, err := r.AdvertisedReferences() + c.Assert(err, Equals, transport.ErrRepositoryNotFound) + c.Assert(info, IsNil) + + r, err = s.Client.NewSendPackSession(s.NonExistentEndpoint) + c.Assert(err, IsNil) + req := packp.NewReferenceUpdateRequest() + req.Commands = []*packp.Command{ + {"master", plumbing.ZeroHash, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")}, + } + + writer, err := r.SendPack(req) + c.Assert(err, Equals, transport.ErrRepositoryNotFound) + c.Assert(writer, IsNil) +} + +func (s *SendPackSuite) TestCallAdvertisedReferenceTwice(c *C) { + r, err := s.Client.NewSendPackSession(s.Endpoint) + c.Assert(err, IsNil) + ar1, err := r.AdvertisedReferences() + c.Assert(err, IsNil) + c.Assert(ar1, NotNil) + ar2, err := r.AdvertisedReferences() + c.Assert(err, IsNil) + c.Assert(ar2, DeepEquals, ar1) +} + +func (s *SendPackSuite) TestDefaultBranch(c *C) { + r, err := s.Client.NewSendPackSession(s.Endpoint) + c.Assert(err, IsNil) + defer func() { c.Assert(r.Close(), IsNil) }() + + info, err := r.AdvertisedReferences() + c.Assert(err, IsNil) + ref, ok := info.References["refs/heads/master"] + c.Assert(ok, Equals, true) + c.Assert(ref, Equals, fixtures.Basic().One().Head) +} + +func (s *SendPackSuite) TestCapabilities(c *C) { + r, err := s.Client.NewSendPackSession(s.Endpoint) + c.Assert(err, IsNil) + defer func() { c.Assert(r.Close(), IsNil) }() + + info, err := r.AdvertisedReferences() + c.Assert(err, IsNil) + c.Assert(info.Capabilities.Get("agent"), HasLen, 1) +} + +func (s *SendPackSuite) TestFullSendPackOnEmpty(c *C) { + endpoint := s.EmptyEndpoint + full := true + fixture := fixtures.Basic().ByTag("packfile").One() + req := packp.NewReferenceUpdateRequest() + req.Commands = []*packp.Command{ + {"refs/heads/master", plumbing.ZeroHash, fixture.Head}, + } + s.sendPack(c, endpoint, req, fixture, full) + s.checkRemoteHead(c, endpoint, fixture.Head) +} + +func (s *SendPackSuite) TestSendPackOnEmpty(c *C) { + endpoint := s.EmptyEndpoint + full := false + fixture := fixtures.Basic().ByTag("packfile").One() + req := packp.NewReferenceUpdateRequest() + req.Commands = []*packp.Command{ + {"refs/heads/master", plumbing.ZeroHash, fixture.Head}, + } + s.sendPack(c, endpoint, req, fixture, full) + s.checkRemoteHead(c, endpoint, fixture.Head) +} + +func (s *SendPackSuite) TestSendPackOnEmptyWithReportStatus(c *C) { + endpoint := s.EmptyEndpoint + full := false + fixture := fixtures.Basic().ByTag("packfile").One() + req := packp.NewReferenceUpdateRequest() + req.Commands = []*packp.Command{ + {"refs/heads/master", plumbing.ZeroHash, fixture.Head}, + } + req.Capabilities.Set(capability.ReportStatus) + s.sendPack(c, endpoint, req, fixture, full) + s.checkRemoteHead(c, endpoint, fixture.Head) +} + +func (s *SendPackSuite) TestFullSendPackOnNonEmpty(c *C) { + endpoint := s.Endpoint + full := true + fixture := fixtures.Basic().ByTag("packfile").One() + req := packp.NewReferenceUpdateRequest() + req.Commands = []*packp.Command{ + {"refs/heads/master", plumbing.ZeroHash, fixture.Head}, + } + s.sendPack(c, endpoint, req, fixture, full) + s.checkRemoteHead(c, endpoint, fixture.Head) +} + +func (s *SendPackSuite) TestSendPackOnNonEmpty(c *C) { + endpoint := s.Endpoint + full := false + fixture := fixtures.Basic().ByTag("packfile").One() + req := packp.NewReferenceUpdateRequest() + req.Commands = []*packp.Command{ + {"refs/heads/master", plumbing.ZeroHash, fixture.Head}, + } + s.sendPack(c, endpoint, req, fixture, full) + s.checkRemoteHead(c, endpoint, fixture.Head) +} + +func (s *SendPackSuite) TestSendPackOnNonEmptyWithReportStatus(c *C) { + endpoint := s.Endpoint + full := false + fixture := fixtures.Basic().ByTag("packfile").One() + req := packp.NewReferenceUpdateRequest() + req.Commands = []*packp.Command{ + {"refs/heads/master", plumbing.ZeroHash, fixture.Head}, + } + req.Capabilities.Set(capability.ReportStatus) + + s.sendPack(c, endpoint, req, fixture, full) + s.checkRemoteHead(c, endpoint, fixture.Head) +} + +func (s *SendPackSuite) sendPack(c *C, ep transport.Endpoint, + req *packp.ReferenceUpdateRequest, fixture *fixtures.Fixture, + callAdvertisedReferences bool) { + + url := "" + if fixture != nil { + url = fixture.URL + } + comment := Commentf( + "failed with ep=%s fixture=%s callAdvertisedReferences=%s", + ep.String(), url, callAdvertisedReferences, + ) + + r, err := s.Client.NewSendPackSession(ep) + c.Assert(err, IsNil, comment) + defer func() { c.Assert(r.Close(), IsNil, comment) }() + + if callAdvertisedReferences { + info, err := r.AdvertisedReferences() + c.Assert(err, IsNil, comment) + c.Assert(info, NotNil, comment) + } + + if fixture != nil { + c.Assert(fixture.Packfile(), NotNil) + req.Packfile = fixture.Packfile() + } else { + req.Packfile = s.emptyPackfile() + } + + report, err := r.SendPack(req) + c.Assert(err, IsNil, comment) + if req.Capabilities.Supports(capability.ReportStatus) { + c.Assert(report, NotNil, comment) + c.Assert(report.Ok(), Equals, true, comment) + } else { + c.Assert(report, IsNil, comment) + } +} + +func (s *SendPackSuite) checkRemoteHead(c *C, ep transport.Endpoint, head plumbing.Hash) { + s.checkRemoteReference(c, ep, "refs/heads/master", head) +} + +func (s *SendPackSuite) checkRemoteReference(c *C, ep transport.Endpoint, + refName string, head plumbing.Hash) { + + r, err := s.Client.NewFetchPackSession(ep) + c.Assert(err, IsNil) + defer func() { c.Assert(r.Close(), IsNil) }() + ar, err := r.AdvertisedReferences() + c.Assert(err, IsNil, Commentf("endpoint: %s", ep.String())) + ref, ok := ar.References[refName] + if head == plumbing.ZeroHash { + c.Assert(ok, Equals, false) + } else { + c.Assert(ok, Equals, true) + c.Assert(ref, DeepEquals, head) + } +} + +func (s *SendPackSuite) TestSendPackAddDeleteReference(c *C) { + s.testSendPackAddReference(c) + s.testSendPackDeleteReference(c) +} + +func (s *SendPackSuite) testSendPackAddReference(c *C) { + r, err := s.Client.NewSendPackSession(s.Endpoint) + c.Assert(err, IsNil) + defer func() { c.Assert(r.Close(), IsNil) }() + + fixture := fixtures.Basic().ByTag("packfile").One() + + ar, err := r.AdvertisedReferences() + c.Assert(err, IsNil) + + req := packp.NewReferenceUpdateRequest() + req.Commands = []*packp.Command{ + {"refs/heads/newbranch", plumbing.ZeroHash, fixture.Head}, + } + if ar.Capabilities.Supports(capability.ReportStatus) { + req.Capabilities.Set(capability.ReportStatus) + } + + s.sendPack(c, s.Endpoint, req, nil, false) + s.checkRemoteReference(c, s.Endpoint, "refs/heads/newbranch", fixture.Head) +} + +func (s *SendPackSuite) testSendPackDeleteReference(c *C) { + r, err := s.Client.NewSendPackSession(s.Endpoint) + c.Assert(err, IsNil) + defer func() { c.Assert(r.Close(), IsNil) }() + + fixture := fixtures.Basic().ByTag("packfile").One() + + ar, err := r.AdvertisedReferences() + c.Assert(err, IsNil) + + req := packp.NewReferenceUpdateRequest() + req.Commands = []*packp.Command{ + {"refs/heads/newbranch", fixture.Head, plumbing.ZeroHash}, + } + if ar.Capabilities.Supports(capability.ReportStatus) { + req.Capabilities.Set(capability.ReportStatus) + } + + s.sendPack(c, s.Endpoint, req, nil, false) + s.checkRemoteReference(c, s.Endpoint, "refs/heads/newbranch", plumbing.ZeroHash) +} + +func (s *SendPackSuite) emptyPackfile() io.ReadCloser { + var buf bytes.Buffer + e := packfile.NewEncoder(&buf, memory.NewStorage()) + _, err := e.Encode(nil) + if err != nil { + panic(err) + } + + return ioutil.NopCloser(&buf) +} |