diff options
25 files changed, 692 insertions, 237 deletions
diff --git a/common_test.go b/common_test.go index 3f5364c..21b4481 100644 --- a/common_test.go +++ b/common_test.go @@ -3,6 +3,7 @@ package git import ( "testing" + billy "gopkg.in/src-d/go-billy.v3" "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/transport" @@ -12,7 +13,7 @@ import ( "github.com/src-d/go-git-fixtures" . "gopkg.in/check.v1" "gopkg.in/src-d/go-billy.v3/memfs" - "gopkg.in/src-d/go-billy.v3/osfs" + "gopkg.in/src-d/go-billy.v3/util" ) func Test(t *testing.T) { TestingT(t) } @@ -41,19 +42,65 @@ func (s *BaseSuite) buildBasicRepository(c *C) { s.Repository = s.NewRepository(f) } +// NewRepository returns a new repository using the .git folder, if the fixture +// is tagged as worktree the filesystem from fixture is used, otherwise a new +// memfs filesystem is used as worktree. func (s *BaseSuite) NewRepository(f *fixtures.Fixture) *Repository { - fs := osfs.New(f.DotGit().Root()) - st, err := filesystem.NewStorage(fs) + var worktree, dotgit billy.Filesystem + if f.Is("worktree") { + r, err := PlainOpen(f.Worktree().Root()) + if err != nil { + panic(err) + } + + return r + } + + dotgit = f.DotGit() + worktree = memfs.New() + + st, err := filesystem.NewStorage(dotgit) + if err != nil { + panic(err) + } + + r, err := Open(st, worktree) + if err != nil { + panic(err) + } + + return r +} + +// NewRepositoryWithEmptyWorktree returns a new repository using the .git folder +// from the fixture but without a empty memfs worktree, the index and the +// modules are deleted from the .git folder. +func (s *BaseSuite) NewRepositoryWithEmptyWorktree(f *fixtures.Fixture) *Repository { + dotgit := f.DotGit() + err := dotgit.Remove("index") if err != nil { panic(err) } - r, err := Open(st, fs) + err = util.RemoveAll(dotgit, "modules") + if err != nil { + panic(err) + } + + worktree := memfs.New() + + st, err := filesystem.NewStorage(dotgit) + if err != nil { + panic(err) + } + + r, err := Open(st, worktree) if err != nil { panic(err) } return r + } func (s *BaseSuite) NewRepositoryFromPackfile(f *fixtures.Fixture) *Repository { diff --git a/plumbing/format/idxfile/decoder.go b/plumbing/format/idxfile/decoder.go index 6618475..fea5f0b 100644 --- a/plumbing/format/idxfile/decoder.go +++ b/plumbing/format/idxfile/decoder.go @@ -1,6 +1,7 @@ package idxfile import ( + "bufio" "bytes" "errors" "io" @@ -19,12 +20,12 @@ var ( // Decoder reads and decodes idx files from an input stream. type Decoder struct { - io.Reader + *bufio.Reader } // NewDecoder builds a new idx stream decoder, that reads from r. func NewDecoder(r io.Reader) *Decoder { - return &Decoder{r} + return &Decoder{bufio.NewReader(r)} } // Decode reads from the stream and decode the content into the Idxfile struct. diff --git a/plumbing/protocol/packp/srvresp.go b/plumbing/protocol/packp/srvresp.go index 0c89e47..b214341 100644 --- a/plumbing/protocol/packp/srvresp.go +++ b/plumbing/protocol/packp/srvresp.go @@ -1,6 +1,7 @@ package packp import ( + "bufio" "bytes" "errors" "fmt" @@ -18,8 +19,8 @@ type ServerResponse struct { } // Decode decodes the response into the struct, isMultiACK should be true, if -// the request was done with multi_ack or multi_ack_detailed capabilities -func (r *ServerResponse) Decode(reader io.Reader, isMultiACK bool) error { +// the request was done with multi_ack or multi_ack_detailed capabilities. +func (r *ServerResponse) Decode(reader *bufio.Reader, isMultiACK bool) error { // TODO: implement support for multi_ack or multi_ack_detailed responses if isMultiACK { return errors.New("multi_ack and multi_ack_detailed are not supported") @@ -34,7 +35,15 @@ func (r *ServerResponse) Decode(reader io.Reader, isMultiACK bool) error { return err } - if !isMultiACK { + // we need to detect when the end of a response header and the begining + // of a packfile header happend, some requests to the git daemon + // produces a duplicate ACK header even when multi_ack is not supported. + stop, err := r.stopReading(reader) + if err != nil { + return err + } + + if stop { break } } @@ -42,6 +51,40 @@ func (r *ServerResponse) Decode(reader io.Reader, isMultiACK bool) error { return s.Err() } +// stopReading detects when a valid command such as ACK or NAK is found to be +// read in the buffer without moving the read pointer. +func (r *ServerResponse) stopReading(reader *bufio.Reader) (bool, error) { + ahead, err := reader.Peek(7) + if err == io.EOF { + return true, nil + } + + if err != nil { + return false, err + } + + if len(ahead) > 4 && r.isValidCommand(ahead[0:3]) { + return false, nil + } + + if len(ahead) == 7 && r.isValidCommand(ahead[4:]) { + return false, nil + } + + return true, nil +} + +func (r *ServerResponse) isValidCommand(b []byte) bool { + commands := [][]byte{ack, nak} + for _, c := range commands { + if bytes.Compare(b, c) == 0 { + return true + } + } + + return false +} + func (r *ServerResponse) decodeLine(line []byte) error { if len(line) == 0 { return fmt.Errorf("unexpected flush") diff --git a/plumbing/protocol/packp/srvresp_test.go b/plumbing/protocol/packp/srvresp_test.go index 6078855..c8ef520 100644 --- a/plumbing/protocol/packp/srvresp_test.go +++ b/plumbing/protocol/packp/srvresp_test.go @@ -1,6 +1,7 @@ package packp import ( + "bufio" "bytes" "gopkg.in/src-d/go-git.v4/plumbing" @@ -16,7 +17,7 @@ func (s *ServerResponseSuite) TestDecodeNAK(c *C) { raw := "0008NAK\n" sr := &ServerResponse{} - err := sr.Decode(bytes.NewBufferString(raw), false) + err := sr.Decode(bufio.NewReader(bytes.NewBufferString(raw)), false) c.Assert(err, IsNil) c.Assert(sr.ACKs, HasLen, 0) @@ -26,23 +27,53 @@ func (s *ServerResponseSuite) TestDecodeACK(c *C) { raw := "0031ACK 6ecf0ef2c2dffb796033e5a02219af86ec6584e5\n" sr := &ServerResponse{} - err := sr.Decode(bytes.NewBufferString(raw), false) + err := sr.Decode(bufio.NewReader(bytes.NewBufferString(raw)), false) c.Assert(err, IsNil) c.Assert(sr.ACKs, HasLen, 1) c.Assert(sr.ACKs[0], Equals, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) } +func (s *ServerResponseSuite) TestDecodeMultipleACK(c *C) { + raw := "" + + "0031ACK 1111111111111111111111111111111111111111\n" + + "0031ACK 6ecf0ef2c2dffb796033e5a02219af86ec6584e5\n" + + "00080PACK\n" + + sr := &ServerResponse{} + err := sr.Decode(bufio.NewReader(bytes.NewBufferString(raw)), false) + c.Assert(err, IsNil) + + c.Assert(sr.ACKs, HasLen, 2) + c.Assert(sr.ACKs[0], Equals, plumbing.NewHash("1111111111111111111111111111111111111111")) + c.Assert(sr.ACKs[1], Equals, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) +} + +func (s *ServerResponseSuite) TestDecodeMultipleACKWithSideband(c *C) { + raw := "" + + "0031ACK 1111111111111111111111111111111111111111\n" + + "0031ACK 6ecf0ef2c2dffb796033e5a02219af86ec6584e5\n" + + "00080aaaa\n" + + sr := &ServerResponse{} + err := sr.Decode(bufio.NewReader(bytes.NewBufferString(raw)), false) + c.Assert(err, IsNil) + + c.Assert(sr.ACKs, HasLen, 2) + c.Assert(sr.ACKs[0], Equals, plumbing.NewHash("1111111111111111111111111111111111111111")) + c.Assert(sr.ACKs[1], Equals, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) +} + func (s *ServerResponseSuite) TestDecodeMalformed(c *C) { raw := "0029ACK 6ecf0ef2c2dffb796033e5a02219af86ec6584e\n" sr := &ServerResponse{} - err := sr.Decode(bytes.NewBufferString(raw), false) + err := sr.Decode(bufio.NewReader(bytes.NewBufferString(raw)), false) c.Assert(err, NotNil) } func (s *ServerResponseSuite) TestDecodeMultiACK(c *C) { sr := &ServerResponse{} - err := sr.Decode(bytes.NewBuffer(nil), true) + err := sr.Decode(bufio.NewReader(bytes.NewBuffer(nil)), true) c.Assert(err, NotNil) } diff --git a/plumbing/protocol/packp/updreq.go b/plumbing/protocol/packp/updreq.go index 0624930..b246613 100644 --- a/plumbing/protocol/packp/updreq.go +++ b/plumbing/protocol/packp/updreq.go @@ -42,6 +42,7 @@ func NewReferenceUpdateRequest() *ReferenceUpdateRequest { // - report-status // - ofs-delta // - ref-delta +// - delete-refs // It leaves up to the user to add the following capabilities later: // - atomic // - ofs-delta diff --git a/plumbing/protocol/packp/uppackresp.go b/plumbing/protocol/packp/uppackresp.go index ac456f3..c18e159 100644 --- a/plumbing/protocol/packp/uppackresp.go +++ b/plumbing/protocol/packp/uppackresp.go @@ -4,6 +4,8 @@ import ( "errors" "io" + "bufio" + "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability" "gopkg.in/src-d/go-git.v4/utils/ioutil" ) @@ -51,18 +53,20 @@ func NewUploadPackResponseWithPackfile(req *UploadPackRequest, // Decode decodes all the responses sent by upload-pack service into the struct // and prepares it to read the packfile using the Read method func (r *UploadPackResponse) Decode(reader io.ReadCloser) error { + buf := bufio.NewReader(reader) + if r.isShallow { - if err := r.ShallowUpdate.Decode(reader); err != nil { + if err := r.ShallowUpdate.Decode(buf); err != nil { return err } } - if err := r.ServerResponse.Decode(reader, r.isMultiACK); err != nil { + if err := r.ServerResponse.Decode(buf, r.isMultiACK); err != nil { return err } // now the reader is ready to read the packfile content - r.r = reader + r.r = ioutil.NewReadCloser(buf, reader) return nil } diff --git a/plumbing/protocol/packp/uppackresp_test.go b/plumbing/protocol/packp/uppackresp_test.go index c27fdda..789444d 100644 --- a/plumbing/protocol/packp/uppackresp_test.go +++ b/plumbing/protocol/packp/uppackresp_test.go @@ -15,7 +15,7 @@ type UploadPackResponseSuite struct{} var _ = Suite(&UploadPackResponseSuite{}) func (s *UploadPackResponseSuite) TestDecodeNAK(c *C) { - raw := "0008NAK\n[PACK]" + raw := "0008NAK\nPACK" req := NewUploadPackRequest() res := NewUploadPackResponse(req) @@ -26,11 +26,11 @@ func (s *UploadPackResponseSuite) TestDecodeNAK(c *C) { pack, err := ioutil.ReadAll(res) c.Assert(err, IsNil) - c.Assert(pack, DeepEquals, []byte("[PACK]")) + c.Assert(pack, DeepEquals, []byte("PACK")) } func (s *UploadPackResponseSuite) TestDecodeDepth(c *C) { - raw := "00000008NAK\n[PACK]" + raw := "00000008NAK\nPACK" req := NewUploadPackRequest() req.Depth = DepthCommits(1) @@ -43,11 +43,11 @@ func (s *UploadPackResponseSuite) TestDecodeDepth(c *C) { pack, err := ioutil.ReadAll(res) c.Assert(err, IsNil) - c.Assert(pack, DeepEquals, []byte("[PACK]")) + c.Assert(pack, DeepEquals, []byte("PACK")) } func (s *UploadPackResponseSuite) TestDecodeMalformed(c *C) { - raw := "00000008ACK\n[PACK]" + raw := "00000008ACK\nPACK" req := NewUploadPackRequest() req.Depth = DepthCommits(1) @@ -96,7 +96,7 @@ func (s *UploadPackResponseSuite) TestEncodeNAK(c *C) { } func (s *UploadPackResponseSuite) TestEncodeDepth(c *C) { - pf := ioutil.NopCloser(bytes.NewBuffer([]byte("[PACK]"))) + pf := ioutil.NopCloser(bytes.NewBuffer([]byte("PACK"))) req := NewUploadPackRequest() req.Depth = DepthCommits(1) @@ -106,7 +106,7 @@ func (s *UploadPackResponseSuite) TestEncodeDepth(c *C) { b := bytes.NewBuffer(nil) c.Assert(res.Encode(b), IsNil) - expected := "00000008NAK\n[PACK]" + expected := "00000008NAK\nPACK" c.Assert(string(b.Bytes()), Equals, expected) } diff --git a/plumbing/reference.go b/plumbing/reference.go index 8fa103e..5d477b9 100644 --- a/plumbing/reference.go +++ b/plumbing/reference.go @@ -15,15 +15,16 @@ const ( symrefPrefix = "ref: " ) -var ( - refPrefixes = []string{ - refHeadPrefix, - refTagPrefix, - refRemotePrefix, - refNotePrefix, - refPrefix, - } -) +// refRevParseRules are a set of rules to parse references into short names. +// These are the same rules as used by git in shorten_unambiguous_ref. +// See: https://github.com/git/git/blob/e0aaa1b6532cfce93d87af9bc813fb2e7a7ce9d7/refs.c#L417 +var refRevParseRules = []string{ + "refs/%s", + "refs/tags/%s", + "refs/heads/%s", + "refs/remotes/%s", + "refs/remotes/%s/HEAD", +} var ( ErrReferenceNotFound = errors.New("reference not found") @@ -60,17 +61,16 @@ func (r ReferenceName) String() string { // Short returns the short name of a ReferenceName func (r ReferenceName) Short() string { - return r.removeRefPrefix() -} - -// Instead of hardcoding a number of components, we should remove the prefixes -// refHeadPrefix, refTagPrefix, refRemotePrefix, refNotePrefix and refPrefix -func (r ReferenceName) removeRefPrefix() string { s := string(r) - for _, prefix := range refPrefixes { - s = strings.TrimPrefix(s, prefix) + res := s + for _, format := range refRevParseRules { + _, err := fmt.Sscanf(s, format, &res) + if err == nil { + continue + } } - return s + + return res } const ( diff --git a/plumbing/reference_test.go b/plumbing/reference_test.go index 6a695f4..97c8772 100644 --- a/plumbing/reference_test.go +++ b/plumbing/reference_test.go @@ -23,6 +23,11 @@ func (s *ReferenceSuite) TestReferenceNameWithSlash(c *C) { c.Assert(r.Short(), Equals, "origin/feature/AllowSlashes") } +func (s *ReferenceSuite) TestReferenceNameNote(c *C) { + r := ReferenceName("refs/notes/foo") + c.Assert(r.Short(), Equals, "notes/foo") +} + func (s *ReferenceSuite) TestNewReferenceFromStrings(c *C) { r := NewReferenceFromStrings("refs/heads/v4", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5") c.Assert(r.Type(), Equals, HashReference) diff --git a/plumbing/transport/file/client.go b/plumbing/transport/file/client.go index a199b01..b6d60c1 100644 --- a/plumbing/transport/file/client.go +++ b/plumbing/transport/file/client.go @@ -46,8 +46,9 @@ func (r *runner) Command(cmd string, ep transport.Endpoint, auth transport.AuthM } type command struct { - cmd *exec.Cmd - closed bool + cmd *exec.Cmd + stderrCloser io.Closer + closed bool } func (c *command) Start() error { @@ -55,7 +56,12 @@ func (c *command) Start() error { } func (c *command) StderrPipe() (io.Reader, error) { - return c.cmd.StderrPipe() + // Pipe returned by Command.StderrPipe has a race with Read + Command.Wait. + // We use an io.Pipe and close it after the command finishes. + r, w := io.Pipe() + c.cmd.Stderr = w + c.stderrCloser = r + return r, nil } func (c *command) StdinPipe() (io.WriteCloser, error) { @@ -72,7 +78,11 @@ func (c *command) Close() error { return nil } - defer func() { c.closed = true }() + defer func() { + c.closed = true + _ = c.stderrCloser.Close() + }() + err := c.cmd.Wait() if _, ok := err.(*os.PathError); ok { return nil 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) diff --git a/plumbing/transport/internal/common/common.go b/plumbing/transport/internal/common/common.go index c1e1518..04db770 100644 --- a/plumbing/transport/internal/common/common.go +++ b/plumbing/transport/internal/common/common.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + stdioutil "io/ioutil" "strings" "time" @@ -22,7 +23,6 @@ import ( const ( readErrorSecondsTimeout = 10 - errLinesBuffer = 1000 ) var ( @@ -96,7 +96,7 @@ type session struct { advRefs *packp.AdvRefs packRun bool finished bool - errLines chan string + firstErrLine chan string } func (c *client) newSession(s string, ep transport.Endpoint, auth transport.AuthMethod) (*session, error) { @@ -128,26 +128,29 @@ func (c *client) newSession(s string, ep transport.Endpoint, auth transport.Auth Stdin: stdin, Stdout: stdout, Command: cmd, - errLines: c.listenErrors(stderr), + firstErrLine: c.listenFirstError(stderr), isReceivePack: s == transport.ReceivePackServiceName, }, nil } -func (c *client) listenErrors(r io.Reader) chan string { +func (c *client) listenFirstError(r io.Reader) chan string { if r == nil { return nil } - errLines := make(chan string, errLinesBuffer) + errLine := make(chan string, 1) go func() { s := bufio.NewScanner(r) - for s.Scan() { - line := string(s.Bytes()) - errLines <- line + if s.Scan() { + errLine <- s.Text() + } else { + close(errLine) } + + _, _ = io.Copy(stdioutil.Discard, r) }() - return errLines + return errLine } // AdvertisedReferences retrieves the advertised references from the server. @@ -296,13 +299,10 @@ func (s *session) finish() error { return nil } -func (s *session) Close() error { - if err := s.finish(); err != nil { - _ = s.Command.Close() - return err - } - - return s.Command.Close() +func (s *session) Close() (err error) { + defer ioutil.CheckClose(s.Command, &err) + err = s.finish() + return } func (s *session) checkNotFoundError() error { @@ -312,7 +312,7 @@ func (s *session) checkNotFoundError() error { select { case <-t.C: return ErrTimeoutExceeded - case line, ok := <-s.errLines: + case line, ok := <-s.firstErrLine: if !ok { return nil } diff --git a/plumbing/transport/server/receive_pack_test.go b/plumbing/transport/server/receive_pack_test.go index 73ba60b..54c2fba 100644 --- a/plumbing/transport/server/receive_pack_test.go +++ b/plumbing/transport/server/receive_pack_test.go @@ -27,11 +27,6 @@ func (s *ReceivePackSuite) TearDownTest(c *C) { s.Suite.TearDownSuite(c) } -// TODO -func (s *ReceivePackSuite) TestSendPackAddDeleteReference(c *C) { - c.Skip("delete reference not supported yet") -} - // Overwritten, server returns error earlier. func (s *ReceivePackSuite) TestAdvertisedReferencesNotExists(c *C) { r, err := s.Client.NewReceivePackSession(s.NonExistentEndpoint, s.EmptyAuth) diff --git a/plumbing/transport/server/server.go b/plumbing/transport/server/server.go index 89fce5f..7c78afe 100644 --- a/plumbing/transport/server/server.go +++ b/plumbing/transport/server/server.go @@ -26,7 +26,19 @@ type server struct { // NewServer returns a transport.Transport implementing a git server, // independent of transport. Each transport must wrap this. func NewServer(loader Loader) transport.Transport { - return &server{loader, &handler{}} + return &server{ + loader, + &handler{asClient: false}, + } +} + +// NewClient returns a transport.Transport implementing a client with an +// embedded server. +func NewClient(loader Loader) transport.Transport { + return &server{ + loader, + &handler{asClient: true}, + } } func (s *server) NewUploadPackSession(ep transport.Endpoint, auth transport.AuthMethod) (transport.UploadPackSession, error) { @@ -47,24 +59,27 @@ func (s *server) NewReceivePackSession(ep transport.Endpoint, auth transport.Aut return s.handler.NewReceivePackSession(sto) } -type handler struct{} +type handler struct { + asClient bool +} func (h *handler) NewUploadPackSession(s storer.Storer) (transport.UploadPackSession, error) { return &upSession{ - session: session{storer: s}, + session: session{storer: s, asClient: h.asClient}, }, nil } func (h *handler) NewReceivePackSession(s storer.Storer) (transport.ReceivePackSession, error) { return &rpSession{ - session: session{storer: s}, + session: session{storer: s, asClient: h.asClient}, cmdStatus: map[plumbing.ReferenceName]error{}, }, nil } type session struct { - storer storer.Storer - caps *capability.List + storer storer.Storer + caps *capability.List + asClient bool } func (s *session) Close() error { @@ -107,6 +122,10 @@ func (s *upSession) AdvertisedReferences() (*packp.AdvRefs, error) { return nil, err } + if s.asClient && len(ar.References) == 0 { + return nil, transport.ErrEmptyRemoteRepository + } + return ar, nil } @@ -225,31 +244,11 @@ func (s *rpSession) ReceivePack(req *packp.ReferenceUpdateRequest) (*packp.Repor return s.reportStatus(), err } - updatedRefs := s.updatedReferences(req) - - if s.caps.Supports(capability.Atomic) && s.firstErr != nil { - //TODO: add support for 'atomic' once we have reference - // transactions, currently we do not announce it. - rs := s.reportStatus() - for _, cs := range rs.CommandStatuses { - if cs.Error() == nil { - cs.Status = "" - } - } - } - - for name, ref := range updatedRefs { - //TODO: add support for 'delete-refs' once we can delete - // references, currently we do not announce it. - err := s.storer.SetReference(ref) - s.setStatus(name, err) - } - + s.updateReferences(req) return s.reportStatus(), s.firstErr } -func (s *rpSession) updatedReferences(req *packp.ReferenceUpdateRequest) map[plumbing.ReferenceName]*plumbing.Reference { - refs := map[plumbing.ReferenceName]*plumbing.Reference{} +func (s *rpSession) updateReferences(req *packp.ReferenceUpdateRequest) { for _, cmd := range req.Commands { exists, err := referenceExists(s.storer, cmd.Name) if err != nil { @@ -265,19 +264,16 @@ func (s *rpSession) updatedReferences(req *packp.ReferenceUpdateRequest) map[plu } ref := plumbing.NewHashReference(cmd.Name, cmd.New) - refs[ref.Name()] = ref + err := s.storer.SetReference(ref) + s.setStatus(cmd.Name, err) case packp.Delete: if !exists { s.setStatus(cmd.Name, ErrUpdateReference) continue } - if !s.caps.Supports(capability.DeleteRefs) { - s.setStatus(cmd.Name, fmt.Errorf("delete not supported")) - continue - } - - refs[cmd.Name] = nil + err := s.storer.RemoveReference(cmd.Name) + s.setStatus(cmd.Name, err) case packp.Update: if !exists { s.setStatus(cmd.Name, ErrUpdateReference) @@ -290,11 +286,10 @@ func (s *rpSession) updatedReferences(req *packp.ReferenceUpdateRequest) map[plu } ref := plumbing.NewHashReference(cmd.Name, cmd.New) - refs[ref.Name()] = ref + err := s.storer.SetReference(ref) + s.setStatus(cmd.Name, err) } } - - return refs } func (s *rpSession) failAtomicUpdate() (*packp.ReportStatus, error) { @@ -368,6 +363,10 @@ func (*rpSession) setSupportedCapabilities(c *capability.List) error { return err } + if err := c.Set(capability.DeleteRefs); err != nil { + return err + } + return c.Set(capability.ReportStatus) } diff --git a/plumbing/transport/server/server_test.go b/plumbing/transport/server/server_test.go index 0f7201c..7912768 100644 --- a/plumbing/transport/server/server_test.go +++ b/plumbing/transport/server/server_test.go @@ -20,12 +20,18 @@ type BaseSuite struct { loader server.MapLoader client transport.Transport clientBackup transport.Transport + asClient bool } func (s *BaseSuite) SetUpSuite(c *C) { s.Suite.SetUpSuite(c) s.loader = server.MapLoader{} - s.client = server.NewServer(s.loader) + if s.asClient { + s.client = server.NewClient(s.loader) + } else { + s.client = server.NewServer(s.loader) + } + s.clientBackup = client.Protocols["file"] client.Protocols["file"] = s.client } diff --git a/plumbing/transport/server/upload_pack_test.go b/plumbing/transport/server/upload_pack_test.go index 137f887..bd2b791 100644 --- a/plumbing/transport/server/upload_pack_test.go +++ b/plumbing/transport/server/upload_pack_test.go @@ -38,3 +38,20 @@ func (s *UploadPackSuite) TestAdvertisedReferencesNotExists(c *C) { c.Assert(err, Equals, transport.ErrRepositoryNotFound) c.Assert(r, IsNil) } + +// Tests server with `asClient = true`. This is recommended when using a server +// registered directly with `client.InstallProtocol`. +type ClientLikeUploadPackSuite struct { + UploadPackSuite +} + +var _ = Suite(&ClientLikeUploadPackSuite{}) + +func (s *ClientLikeUploadPackSuite) SetUpSuite(c *C) { + s.asClient = true + s.UploadPackSuite.SetUpSuite(c) +} + +func (s *ClientLikeUploadPackSuite) TestAdvertisedReferencesEmpty(c *C) { + s.UploadPackSuite.UploadPackSuite.TestAdvertisedReferencesEmpty(c) +} diff --git a/plumbing/transport/ssh/common.go b/plumbing/transport/ssh/common.go index d53fc12..6d1c51b 100644 --- a/plumbing/transport/ssh/common.go +++ b/plumbing/transport/ssh/common.go @@ -3,6 +3,7 @@ package ssh import ( "fmt" + "reflect" "gopkg.in/src-d/go-git.v4/plumbing/transport" "gopkg.in/src-d/go-git.v4/plumbing/transport/internal/common" @@ -11,7 +12,12 @@ import ( ) // DefaultClient is the default SSH client. -var DefaultClient = common.NewClient(&runner{}) +var DefaultClient = NewClient(nil) + +// NewClient creates a new SSH client with an optional *ssh.ClientConfig. +func NewClient(config *ssh.ClientConfig) transport.Transport { + return common.NewClient(&runner{config: config}) +} // DefaultAuthBuilder is the function used to create a default AuthMethod, when // the user doesn't provide any. @@ -21,10 +27,12 @@ var DefaultAuthBuilder = func(user string) (AuthMethod, error) { const DefaultPort = 22 -type runner struct{} +type runner struct { + config *ssh.ClientConfig +} func (r *runner) Command(cmd string, ep transport.Endpoint, auth transport.AuthMethod) (common.Command, error) { - c := &command{command: cmd, endpoint: ep} + c := &command{command: cmd, endpoint: ep, config: r.config} if auth != nil { c.setAuth(auth) } @@ -42,6 +50,7 @@ type command struct { endpoint transport.Endpoint client *ssh.Client auth AuthMethod + config *ssh.ClientConfig } func (c *command) setAuth(auth transport.AuthMethod) error { @@ -95,6 +104,8 @@ func (c *command) connect() error { return err } + overrideConfig(c.config, config) + c.client, err = ssh.Dial("tcp", c.getHostWithPort(), config) if err != nil { return err @@ -129,3 +140,25 @@ func (c *command) setAuthFromEndpoint() error { func endpointToCommand(cmd string, ep transport.Endpoint) string { return fmt.Sprintf("%s '%s'", cmd, ep.Path()) } + +func overrideConfig(overrides *ssh.ClientConfig, c *ssh.ClientConfig) { + if overrides == nil { + return + } + + vo := reflect.ValueOf(*overrides) + vc := reflect.ValueOf(*c) + for i := 0; i < vc.Type().NumField(); i++ { + vcf := vc.Field(i) + vof := vo.Field(i) + if isZeroValue(vcf) { + vcf.Set(vof) + } + } + + *c = vc.Interface().(ssh.ClientConfig) +} + +func isZeroValue(v reflect.Value) bool { + return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface()) +} diff --git a/plumbing/transport/test/receive_pack.go b/plumbing/transport/test/receive_pack.go index bb1c58a..15172c8 100644 --- a/plumbing/transport/test/receive_pack.go +++ b/plumbing/transport/test/receive_pack.go @@ -308,6 +308,10 @@ func (s *ReceivePackSuite) testSendPackDeleteReference(c *C) { req.Capabilities.Set(capability.ReportStatus) } + if !ar.Capabilities.Supports(capability.DeleteRefs) { + c.Fatal("capability delete-refs not supported") + } + c.Assert(r.Close(), IsNil) s.receivePack(c, s.Endpoint, req, nil, false) @@ -21,7 +21,10 @@ import ( "gopkg.in/src-d/go-git.v4/utils/ioutil" ) -var NoErrAlreadyUpToDate = errors.New("already up-to-date") +var ( + NoErrAlreadyUpToDate = errors.New("already up-to-date") + ErrDeleteRefNotSupported = errors.New("server does not support delete-refs") +) // Remote represents a connection to a remote repository. type Remote struct { @@ -56,7 +59,6 @@ func (r *Remote) Fetch(o *FetchOptions) error { // Push performs a push to the remote. Returns NoErrAlreadyUpToDate if the // remote was already up-to-date. func (r *Remote) Push(o *PushOptions) (err error) { - // TODO: Support deletes. // TODO: Sideband support if o.RemoteName == "" { @@ -88,6 +90,18 @@ func (r *Remote) Push(o *PushOptions) (err error) { return err } + isDelete := false + for _, rs := range o.RefSpecs { + if rs.IsDelete() { + isDelete = true + break + } + } + + if isDelete && !ar.Capabilities.Supports(capability.DeleteRefs) { + return ErrDeleteRefNotSupported + } + req := packp.NewReferenceUpdateRequestFromCapabilities(ar.Capabilities) if err := r.addReferencesToUpdate(o.RefSpecs, remoteRefs, req); err != nil { return err @@ -238,24 +252,60 @@ func (r *Remote) addReferencesToUpdate(refspecs []config.RefSpec, req *packp.ReferenceUpdateRequest) error { for _, rs := range refspecs { - iter, err := r.s.IterReferences() - if err != nil { - return err - } - - err = iter.ForEach(func(ref *plumbing.Reference) error { - return r.addReferenceIfRefSpecMatches( - rs, remoteRefs, ref, req, - ) - }) - if err != nil { - return err + if rs.IsDelete() { + if err := r.deleteReferences(rs, remoteRefs, req); err != nil { + return err + } + } else { + if err := r.addOrUpdateReferences(rs, remoteRefs, req); err != nil { + return err + } } } return nil } +func (r *Remote) addOrUpdateReferences(rs config.RefSpec, + remoteRefs storer.ReferenceStorer, req *packp.ReferenceUpdateRequest) error { + iter, err := r.s.IterReferences() + if err != nil { + return err + } + + return iter.ForEach(func(ref *plumbing.Reference) error { + return r.addReferenceIfRefSpecMatches( + rs, remoteRefs, ref, req, + ) + }) +} + +func (r *Remote) deleteReferences(rs config.RefSpec, + remoteRefs storer.ReferenceStorer, req *packp.ReferenceUpdateRequest) error { + iter, err := remoteRefs.IterReferences() + if err != nil { + return err + } + + return iter.ForEach(func(ref *plumbing.Reference) error { + if ref.Type() != plumbing.HashReference { + return nil + } + + if rs.Dst("") != ref.Name() { + return nil + } + + cmd := &packp.Command{ + Name: ref.Name(), + Old: ref.Hash(), + New: plumbing.ZeroHash, + } + req.Commands = append(req.Commands, cmd) + return nil + }) +} + func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec, remoteRefs storer.ReferenceStorer, localRef *plumbing.Reference, req *packp.ReferenceUpdateRequest) error { diff --git a/remote_test.go b/remote_test.go index 4297b81..c12d5d5 100644 --- a/remote_test.go +++ b/remote_test.go @@ -5,6 +5,8 @@ import ( "io" "io/ioutil" "os" + "path/filepath" + "strings" "github.com/src-d/go-git-fixtures" "gopkg.in/src-d/go-git.v4/config" @@ -334,6 +336,32 @@ func (s *RemoteSuite) TestPushNoErrAlreadyUpToDate(c *C) { c.Assert(err, Equals, NoErrAlreadyUpToDate) } +func (s *RemoteSuite) TestPushDeleteReference(c *C) { + f := fixtures.Basic().One() + sto, err := filesystem.NewStorage(f.DotGit()) + c.Assert(err, IsNil) + + dstFs := f.DotGit() + dstSto, err := filesystem.NewStorage(dstFs) + c.Assert(err, IsNil) + prepareRepo(c, dstFs.Root()) + + url := dstFs.Root() + r := newRemote(sto, &config.RemoteConfig{ + Name: DefaultRemoteName, + URL: url, + }) + + rs := config.RefSpec(":refs/heads/branch") + err = r.Push(&PushOptions{ + RefSpecs: []config.RefSpec{rs}, + }) + c.Assert(err, IsNil) + + _, err = dstSto.Reference(plumbing.ReferenceName("refs/heads/branch")) + c.Assert(err, Equals, plumbing.ErrReferenceNotFound) +} + func (s *RemoteSuite) TestPushRejectNonFastForward(c *C) { f := fixtures.Basic().One() sto, err := filesystem.NewStorage(f.DotGit()) @@ -470,3 +498,22 @@ func (s *RemoteSuite) TestPushWrongRemoteName(c *C) { }) c.Assert(err, ErrorMatches, ".*remote names don't match.*") } + +const bareConfig = `[core] +repositoryformatversion = 0 +filemode = true +bare = 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/repository.go b/repository.go index a60f3ef..17f0dff 100644 --- a/repository.go +++ b/repository.go @@ -22,7 +22,6 @@ import ( ) var ( - ErrObjectNotFound = errors.New("object not found") ErrInvalidReference = errors.New("invalid reference, should be a tag or a branch") ErrRepositoryNotExists = errors.New("repository not exists") ErrRepositoryAlreadyExists = errors.New("repository already exists") @@ -804,10 +803,6 @@ func (r *Repository) TagObjects() (*object.TagIter, error) { func (r *Repository) Object(t plumbing.ObjectType, h plumbing.Hash) (object.Object, error) { obj, err := r.Storer.EncodedObject(t, h) if err != nil { - if err == plumbing.ErrObjectNotFound { - return nil, ErrObjectNotFound - } - return nil, err } diff --git a/worktree_test.go b/worktree_test.go index 875f8d5..864e19e 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -25,9 +25,8 @@ type WorktreeSuite struct { var _ = Suite(&WorktreeSuite{}) func (s *WorktreeSuite) SetUpTest(c *C) { - s.buildBasicRepository(c) - // the index is removed if not the Repository will be not clean - c.Assert(s.Repository.Storer.SetIndex(&index.Index{Version: 2}), IsNil) + f := fixtures.Basic().One() + s.Repository = s.NewRepositoryWithEmptyWorktree(f) } func (s *WorktreeSuite) TestCheckout(c *C) { @@ -87,13 +86,9 @@ func (s *WorktreeSuite) TestCheckoutSymlink(c *C) { func (s *WorktreeSuite) TestCheckoutSubmodule(c *C) { url := "https://github.com/git-fixtures/submodule.git" - w := &Worktree{ - r: s.NewRepository(fixtures.ByURL(url).One()), - fs: memfs.New(), - } + r := s.NewRepositoryWithEmptyWorktree(fixtures.ByURL(url).One()) - // we delete the index, since the fixture comes with a real index - err := w.r.Storer.SetIndex(&index.Index{Version: 2}) + w, err := r.Worktree() c.Assert(err, IsNil) err = w.Checkout(&CheckoutOptions{}) @@ -106,16 +101,11 @@ func (s *WorktreeSuite) TestCheckoutSubmodule(c *C) { func (s *WorktreeSuite) TestCheckoutSubmoduleInitialized(c *C) { url := "https://github.com/git-fixtures/submodule.git" - w := &Worktree{ - r: s.NewRepository(fixtures.ByURL(url).One()), - fs: memfs.New(), - } + r := s.NewRepository(fixtures.ByURL(url).One()) - err := w.r.Storer.SetIndex(&index.Index{Version: 2}) + w, err := r.Worktree() c.Assert(err, IsNil) - err = w.Checkout(&CheckoutOptions{}) - c.Assert(err, IsNil) sub, err := w.Submodules() c.Assert(err, IsNil) @@ -228,15 +218,8 @@ func (s *WorktreeSuite) TestCheckoutChange(c *C) { func (s *WorktreeSuite) TestCheckoutTag(c *C) { f := fixtures.ByTag("tags").One() - - fs := memfs.New() - w := &Worktree{ - r: s.NewRepository(f), - fs: fs, - } - - // we delete the index, since the fixture comes with a real index - err := w.r.Storer.SetIndex(&index.Index{Version: 2}) + r := s.NewRepositoryWithEmptyWorktree(f) + w, err := r.Worktree() c.Assert(err, IsNil) err = w.Checkout(&CheckoutOptions{}) @@ -282,14 +265,9 @@ func (s *WorktreeSuite) TestCheckoutBisectSubmodules(c *C) { // checking every commit over the previous commit func (s *WorktreeSuite) testCheckoutBisect(c *C, url string) { f := fixtures.ByURL(url).One() + r := s.NewRepositoryWithEmptyWorktree(f) - w := &Worktree{ - r: s.NewRepository(f), - fs: memfs.New(), - } - - // we delete the index, since the fixture comes with a real index - err := w.r.Storer.SetIndex(&index.Index{Version: 2}) + w, err := r.Worktree() c.Assert(err, IsNil) iter, err := w.r.Log(&LogOptions{}) |