diff options
author | Máximo Cuadros <mcuadros@gmail.com> | 2017-05-04 10:17:15 +0200 |
---|---|---|
committer | Máximo Cuadros <mcuadros@gmail.com> | 2017-05-04 10:17:15 +0200 |
commit | b8b61e74469e0d2662e7d690eee14893f91fe259 (patch) | |
tree | 1933fe53200d98ad2c557d034609408641ce2900 | |
parent | 40fa5882a2c73f8c075403b7ec85870f04deda07 (diff) | |
parent | e80cdbabb92a1ec35ffad536f52d3ff04b548fd1 (diff) | |
download | go-git-b8b61e74469e0d2662e7d690eee14893f91fe259.tar.gz |
Merge branch 'master' of github.com:src-d/go-git into commit
25 files changed, 663 insertions, 163 deletions
diff --git a/_examples/common_test.go b/_examples/common_test.go index 5543eaf..ff01ba3 100644 --- a/_examples/common_test.go +++ b/_examples/common_test.go @@ -2,7 +2,6 @@ package examples import ( "flag" - "fmt" "go/build" "io/ioutil" "os" @@ -96,7 +95,7 @@ func createBareRepository(dir string) string { func setEmptyRemote(dir string) string { remote := createBareRepository(tempFolder()) - setRemote(dir, fmt.Sprintf("file://%s", remote)) + setRemote(dir, remote) return dir } diff --git a/_examples/storage/README.md b/_examples/storage/README.md index 02b68da..b7207ee 100644 --- a/_examples/storage/README.md +++ b/_examples/storage/README.md @@ -1,4 +1,4 @@ -#go-git + aerospike: a git repository backed by a database +# go-git + aerospike: a git repository backed by a database <img src="https://upload.wikimedia.org/wikipedia/en/2/2b/Aerospike_logo.png" align="right"/> This is an example of a [go-git](https://github.com/src-d/go-git) repository backed by [Aerospike](http://www.aerospike.com/). diff --git a/common_test.go b/common_test.go index bd60385..e1a2a0f 100644 --- a/common_test.go +++ b/common_test.go @@ -1,7 +1,6 @@ package git import ( - "fmt" "testing" "gopkg.in/src-d/go-git.v4/plumbing" @@ -95,8 +94,7 @@ func (s *BaseSuite) GetBasicLocalRepositoryURL() string { } func (s *BaseSuite) GetLocalRepositoryURL(f *fixtures.Fixture) string { - path := f.DotGit().Base() - return fmt.Sprintf("file://%s", path) + return f.DotGit().Base() } type SuiteCommon struct{} diff --git a/plumbing/transport/client/client.go b/plumbing/transport/client/client.go index a398a74..76c1469 100644 --- a/plumbing/transport/client/client.go +++ b/plumbing/transport/client/client.go @@ -35,13 +35,13 @@ func InstallProtocol(scheme string, c transport.Transport) { // http://, https://, ssh:// and file://. // See `InstallProtocol` to add or modify protocols. func NewClient(endpoint transport.Endpoint) (transport.Transport, error) { - f, ok := Protocols[endpoint.Scheme] + f, ok := Protocols[endpoint.Protocol()] if !ok { - return nil, fmt.Errorf("unsupported scheme %q", endpoint.Scheme) + return nil, fmt.Errorf("unsupported scheme %q", endpoint.Protocol()) } if f == nil { - return nil, fmt.Errorf("malformed client for scheme %q, client is defined as nil", endpoint.Scheme) + return nil, fmt.Errorf("malformed client for scheme %q, client is defined as nil", endpoint.Protocol()) } return f, nil diff --git a/plumbing/transport/common.go b/plumbing/transport/common.go index d3cfe21..d6594ca 100644 --- a/plumbing/transport/common.go +++ b/plumbing/transport/common.go @@ -18,6 +18,7 @@ import ( "io" "net/url" "regexp" + "strconv" "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp" @@ -28,7 +29,7 @@ var ( ErrRepositoryNotFound = errors.New("repository not found") ErrEmptyRemoteRepository = errors.New("remote repository is empty") ErrAuthenticationRequired = errors.New("authentication required") - ErrAuthorizationFailed = errors.New("authorization failed") + ErrAuthorizationFailed = errors.New("authorization failed") ErrEmptyUploadPackRequest = errors.New("empty git-upload-pack given") ErrInvalidAuthMethod = errors.New("invalid auth method") ErrAlreadyConnected = errors.New("session already established") @@ -88,42 +89,159 @@ type ReceivePackSession interface { ReceivePack(*packp.ReferenceUpdateRequest) (*packp.ReportStatus, error) } -type Endpoint url.URL - -var ( - isSchemeRegExp = regexp.MustCompile("^[^:]+://") - scpLikeUrlRegExp = regexp.MustCompile("^(?P<user>[^@]+@)?(?P<host>[^:]+):/?(?P<path>.+)$") -) +// Endpoint represents a Git URL in any supported protocol. +type Endpoint interface { + // Protocol returns the protocol (e.g. git, https, file). It should never + // return the empty string. + Protocol() string + // User returns the user or an empty string if none is given. + User() string + // Password returns the password or an empty string if none is given. + Password() string + // Host returns the host or an empty string if none is given. + Host() string + // Port returns the port or 0 if there is no port or a default should be + // used. + Port() int + // Path returns the repository path. + Path() string + // String returns a string representation of the Git URL. + String() string +} func NewEndpoint(endpoint string) (Endpoint, error) { - endpoint = transformSCPLikeIfNeeded(endpoint) + if e, ok := parseSCPLike(endpoint); ok { + return e, nil + } + + if e, ok := parseFile(endpoint); ok { + return e, nil + } u, err := url.Parse(endpoint) if err != nil { - return Endpoint{}, plumbing.NewPermanentError(err) + return nil, plumbing.NewPermanentError(err) } if !u.IsAbs() { - return Endpoint{}, plumbing.NewPermanentError(fmt.Errorf( + return nil, plumbing.NewPermanentError(fmt.Errorf( "invalid endpoint: %s", endpoint, )) } - return Endpoint(*u), nil + return urlEndpoint{u}, nil +} + +type urlEndpoint struct { + *url.URL +} + +func (e urlEndpoint) Protocol() string { return e.URL.Scheme } +func (e urlEndpoint) Host() string { return e.URL.Hostname() } + +func (e urlEndpoint) User() string { + if e.URL.User == nil { + return "" + } + + return e.URL.User.Username() +} + +func (e urlEndpoint) Password() string { + if e.URL.User == nil { + return "" + } + + p, _ := e.URL.User.Password() + return p +} + +func (e urlEndpoint) Port() int { + p := e.URL.Port() + if p == "" { + return 0 + } + + i, err := strconv.Atoi(e.URL.Port()) + if err != nil { + return 0 + } + + return i +} + +func (e urlEndpoint) Path() string { + var res string = e.URL.Path + if e.URL.RawQuery != "" { + res += "?" + e.URL.RawQuery + } + + if e.URL.Fragment != "" { + res += "#" + e.URL.Fragment + } + + return res +} + +type scpEndpoint struct { + user string + host string + path string } -func (e *Endpoint) String() string { - u := url.URL(*e) - return u.String() +func (e *scpEndpoint) Protocol() string { return "ssh" } +func (e *scpEndpoint) User() string { return e.user } +func (e *scpEndpoint) Password() string { return "" } +func (e *scpEndpoint) Host() string { return e.host } +func (e *scpEndpoint) Port() int { return 22 } +func (e *scpEndpoint) Path() string { return e.path } + +func (e *scpEndpoint) String() string { + var user string + if e.user != "" { + user = fmt.Sprintf("%s@", e.user) + } + + return fmt.Sprintf("%s%s:%s", user, e.host, e.path) +} + +type fileEndpoint struct { + path string +} + +func (e *fileEndpoint) Protocol() string { return "file" } +func (e *fileEndpoint) User() string { return "" } +func (e *fileEndpoint) Password() string { return "" } +func (e *fileEndpoint) Host() string { return "" } +func (e *fileEndpoint) Port() int { return 0 } +func (e *fileEndpoint) Path() string { return e.path } +func (e *fileEndpoint) String() string { return e.path } + +var ( + isSchemeRegExp = regexp.MustCompile(`^[^:]+://`) + scpLikeUrlRegExp = regexp.MustCompile(`^(?:(?P<user>[^@]+)@)?(?P<host>[^:\s]+):(?P<path>[^\\].*)$`) +) + +func parseSCPLike(endpoint string) (Endpoint, bool) { + if isSchemeRegExp.MatchString(endpoint) || !scpLikeUrlRegExp.MatchString(endpoint) { + return nil, false + } + + m := scpLikeUrlRegExp.FindStringSubmatch(endpoint) + return &scpEndpoint{ + user: m[1], + host: m[2], + path: m[3], + }, true } -func transformSCPLikeIfNeeded(endpoint string) string { - if !isSchemeRegExp.MatchString(endpoint) && scpLikeUrlRegExp.MatchString(endpoint) { - m := scpLikeUrlRegExp.FindStringSubmatch(endpoint) - return fmt.Sprintf("ssh://%s%s/%s", m[1], m[2], m[3]) +func parseFile(endpoint string) (Endpoint, bool) { + if isSchemeRegExp.MatchString(endpoint) { + return nil, false } - return endpoint + path := endpoint + return &fileEndpoint{path}, true } // UnsupportedCapabilities are the capabilities not supported by any client diff --git a/plumbing/transport/common_test.go b/plumbing/transport/common_test.go index e9a5efa..ec617bd 100644 --- a/plumbing/transport/common_test.go +++ b/plumbing/transport/common_test.go @@ -14,22 +14,118 @@ type SuiteCommon struct{} var _ = Suite(&SuiteCommon{}) -func (s *SuiteCommon) TestNewEndpoint(c *C) { +func (s *SuiteCommon) TestNewEndpointHTTP(c *C) { + e, err := NewEndpoint("http://git:pass@github.com/user/repository.git?foo#bar") + c.Assert(err, IsNil) + c.Assert(e.Protocol(), Equals, "http") + c.Assert(e.User(), Equals, "git") + c.Assert(e.Password(), Equals, "pass") + c.Assert(e.Host(), Equals, "github.com") + c.Assert(e.Port(), Equals, 0) + c.Assert(e.Path(), Equals, "/user/repository.git?foo#bar") + c.Assert(e.String(), Equals, "http://git:pass@github.com/user/repository.git?foo#bar") +} + +func (s *SuiteCommon) TestNewEndpointSSH(c *C) { e, err := NewEndpoint("ssh://git@github.com/user/repository.git") c.Assert(err, IsNil) + c.Assert(e.Protocol(), Equals, "ssh") + c.Assert(e.User(), Equals, "git") + c.Assert(e.Password(), Equals, "") + c.Assert(e.Host(), Equals, "github.com") + c.Assert(e.Port(), Equals, 0) + c.Assert(e.Path(), Equals, "/user/repository.git") c.Assert(e.String(), Equals, "ssh://git@github.com/user/repository.git") } +func (s *SuiteCommon) TestNewEndpointSSHNoUser(c *C) { + e, err := NewEndpoint("ssh://github.com/user/repository.git") + c.Assert(err, IsNil) + c.Assert(e.Protocol(), Equals, "ssh") + c.Assert(e.User(), Equals, "") + c.Assert(e.Password(), Equals, "") + c.Assert(e.Host(), Equals, "github.com") + c.Assert(e.Port(), Equals, 0) + c.Assert(e.Path(), Equals, "/user/repository.git") + c.Assert(e.String(), Equals, "ssh://github.com/user/repository.git") +} + +func (s *SuiteCommon) TestNewEndpointSSHWithPort(c *C) { + e, err := NewEndpoint("ssh://git@github.com:777/user/repository.git") + c.Assert(err, IsNil) + c.Assert(e.Protocol(), Equals, "ssh") + c.Assert(e.User(), Equals, "git") + c.Assert(e.Password(), Equals, "") + c.Assert(e.Host(), Equals, "github.com") + c.Assert(e.Port(), Equals, 777) + c.Assert(e.Path(), Equals, "/user/repository.git") + c.Assert(e.String(), Equals, "ssh://git@github.com:777/user/repository.git") +} + func (s *SuiteCommon) TestNewEndpointSCPLike(c *C) { e, err := NewEndpoint("git@github.com:user/repository.git") c.Assert(err, IsNil) - c.Assert(e.String(), Equals, "ssh://git@github.com/user/repository.git") + c.Assert(e.Protocol(), Equals, "ssh") + c.Assert(e.User(), Equals, "git") + c.Assert(e.Password(), Equals, "") + c.Assert(e.Host(), Equals, "github.com") + c.Assert(e.Port(), Equals, 22) + c.Assert(e.Path(), Equals, "user/repository.git") + c.Assert(e.String(), Equals, "git@github.com:user/repository.git") +} + +func (s *SuiteCommon) TestNewEndpointFileAbs(c *C) { + e, err := NewEndpoint("/foo.git") + c.Assert(err, IsNil) + c.Assert(e.Protocol(), Equals, "file") + c.Assert(e.User(), Equals, "") + c.Assert(e.Password(), Equals, "") + c.Assert(e.Host(), Equals, "") + c.Assert(e.Port(), Equals, 0) + c.Assert(e.Path(), Equals, "/foo.git") + c.Assert(e.String(), Equals, "/foo.git") +} + +func (s *SuiteCommon) TestNewEndpointFileRel(c *C) { + e, err := NewEndpoint("foo.git") + c.Assert(err, IsNil) + c.Assert(e.Protocol(), Equals, "file") + c.Assert(e.User(), Equals, "") + c.Assert(e.Password(), Equals, "") + c.Assert(e.Host(), Equals, "") + c.Assert(e.Port(), Equals, 0) + c.Assert(e.Path(), Equals, "foo.git") + c.Assert(e.String(), Equals, "foo.git") +} + +func (s *SuiteCommon) TestNewEndpointFileWindows(c *C) { + e, err := NewEndpoint("C:\\foo.git") + c.Assert(err, IsNil) + c.Assert(e.Protocol(), Equals, "file") + c.Assert(e.User(), Equals, "") + c.Assert(e.Password(), Equals, "") + c.Assert(e.Host(), Equals, "") + c.Assert(e.Port(), Equals, 0) + c.Assert(e.Path(), Equals, "C:\\foo.git") + c.Assert(e.String(), Equals, "C:\\foo.git") +} + +func (s *SuiteCommon) TestNewEndpointFileURL(c *C) { + e, err := NewEndpoint("file:///foo.git") + c.Assert(err, IsNil) + c.Assert(e.Protocol(), Equals, "file") + c.Assert(e.User(), Equals, "") + c.Assert(e.Password(), Equals, "") + c.Assert(e.Host(), Equals, "") + c.Assert(e.Port(), Equals, 0) + c.Assert(e.Path(), Equals, "/foo.git") + c.Assert(e.String(), Equals, "file:///foo.git") } -func (s *SuiteCommon) TestNewEndpointWrongForgat(c *C) { - e, err := NewEndpoint("foo") - c.Assert(err, Not(IsNil)) - c.Assert(e.Host, Equals, "") +func (s *SuiteCommon) TestNewEndpointInvalidURL(c *C) { + e, err := NewEndpoint("http://\\") + c.Assert(err, NotNil) + c.Assert(e, IsNil) } func (s *SuiteCommon) TestFilterUnsupportedCapabilities(c *C) { diff --git a/plumbing/transport/file/client.go b/plumbing/transport/file/client.go index 58e60db..d2a57d0 100644 --- a/plumbing/transport/file/client.go +++ b/plumbing/transport/file/client.go @@ -41,7 +41,7 @@ func (r *runner) Command(cmd string, ep transport.Endpoint, auth transport.AuthM return nil, err } - return &command{cmd: exec.Command(cmd, ep.Path)}, nil + return &command{cmd: exec.Command(cmd, ep.Path())}, nil } type command struct { diff --git a/plumbing/transport/file/client_test.go b/plumbing/transport/file/client_test.go index 220df3d..030175e 100644 --- a/plumbing/transport/file/client_test.go +++ b/plumbing/transport/file/client_test.go @@ -1,9 +1,9 @@ package file import ( - "fmt" "io" "os" + "path/filepath" "strings" "testing" @@ -20,13 +20,12 @@ filemode = true bare = true` func prepareRepo(c *C, path string) transport.Endpoint { - url := fmt.Sprintf("file://%s", path) - ep, err := transport.NewEndpoint(url) + ep, err := transport.NewEndpoint(path) 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) + 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) diff --git a/plumbing/transport/file/server.go b/plumbing/transport/file/server.go index 74085c2..61dd42d 100644 --- a/plumbing/transport/file/server.go +++ b/plumbing/transport/file/server.go @@ -14,7 +14,7 @@ import ( // and error. This is meant to be used when implementing a git-upload-pack // command. func ServeUploadPack(path string) error { - ep, err := transport.NewEndpoint(fmt.Sprintf("file://%s", path)) + ep, err := transport.NewEndpoint(path) if err != nil { return err } @@ -32,7 +32,7 @@ func ServeUploadPack(path string) error { // input and error. This is meant to be used when implementing a // git-receive-pack command. func ServeReceivePack(path string) error { - ep, err := transport.NewEndpoint(fmt.Sprintf("file://%s", path)) + ep, err := transport.NewEndpoint(path) if err != nil { return err } diff --git a/plumbing/transport/file/server_test.go b/plumbing/transport/file/server_test.go index a7b4e34..176d6ee 100644 --- a/plumbing/transport/file/server_test.go +++ b/plumbing/transport/file/server_test.go @@ -1,7 +1,6 @@ package file import ( - "fmt" "os" "os/exec" @@ -15,7 +14,6 @@ type ServerSuite struct { RemoteName string SrcPath string DstPath string - DstURL string } var _ = Suite(&ServerSuite{}) @@ -30,9 +28,8 @@ func (s *ServerSuite) SetUpSuite(c *C) { fixture = fixtures.ByTag("empty").One() s.DstPath = fixture.DotGit().Base() - s.DstURL = fmt.Sprintf("file://%s", s.DstPath) - cmd := exec.Command("git", "remote", "add", s.RemoteName, s.DstURL) + cmd := exec.Command("git", "remote", "add", s.RemoteName, s.DstPath) cmd.Dir = s.SrcPath c.Assert(cmd.Run(), IsNil) } diff --git a/plumbing/transport/file/upload_pack_test.go b/plumbing/transport/file/upload_pack_test.go index e5915f0..f013683 100644 --- a/plumbing/transport/file/upload_pack_test.go +++ b/plumbing/transport/file/upload_pack_test.go @@ -1,8 +1,8 @@ package file import ( - "fmt" "os" + "path/filepath" "github.com/src-d/go-git-fixtures" "gopkg.in/src-d/go-git.v4/plumbing/transport" @@ -25,20 +25,18 @@ func (s *UploadPackSuite) SetUpSuite(c *C) { fixture := fixtures.Basic().One() path := fixture.DotGit().Base() - url := fmt.Sprintf("file://%s", path) - ep, err := transport.NewEndpoint(url) + ep, err := transport.NewEndpoint(path) c.Assert(err, IsNil) s.Endpoint = ep fixture = fixtures.ByTag("empty").One() path = fixture.DotGit().Base() - url = fmt.Sprintf("file://%s", path) - ep, err = transport.NewEndpoint(url) + ep, err = transport.NewEndpoint(path) c.Assert(err, IsNil) s.EmptyEndpoint = ep - url = fmt.Sprintf("file://%s/%s", fixtures.DataFolder, "non-existent") - ep, err = transport.NewEndpoint(url) + path = filepath.Join(fixtures.DataFolder, "non-existent") + ep, err = transport.NewEndpoint(path) c.Assert(err, IsNil) s.NonExistentEndpoint = ep } diff --git a/plumbing/transport/git/common.go b/plumbing/transport/git/common.go index 24134c1..753f125 100644 --- a/plumbing/transport/git/common.go +++ b/plumbing/transport/git/common.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "net" - "strings" "gopkg.in/src-d/go-git.v4/plumbing/format/pktline" "gopkg.in/src-d/go-git.v4/plumbing/transport" @@ -16,6 +15,8 @@ import ( // DefaultClient is the default git client. var DefaultClient = common.NewClient(&runner{}) +const DefaultPort = 9418 + type runner struct{} // Command returns a new Command for the given cmd in the given Endpoint @@ -62,12 +63,13 @@ func (c *command) connect() error { } func (c *command) getHostWithPort() string { - host := c.endpoint.Host - if strings.Index(c.endpoint.Host, ":") == -1 { - host += ":9418" + host := c.endpoint.Host() + port := c.endpoint.Port() + if port <= 0 { + port = DefaultPort } - return host + return fmt.Sprintf("%s:%d", host, port) } // StderrPipe git protocol doesn't have any dedicated error channel @@ -88,7 +90,7 @@ func (c *command) StdoutPipe() (io.Reader, error) { } func endpointToCommand(cmd string, ep transport.Endpoint) string { - return fmt.Sprintf("%s %s%chost=%s%c", cmd, ep.Path, 0, ep.Host, 0) + return fmt.Sprintf("%s %s%chost=%s%c", cmd, ep.Path(), 0, ep.Host(), 0) } // Wait no-op function, required by the interface diff --git a/plumbing/transport/http/common.go b/plumbing/transport/http/common.go index 930e8eb..04b6121 100644 --- a/plumbing/transport/http/common.go +++ b/plumbing/transport/http/common.go @@ -73,18 +73,12 @@ type AuthMethod interface { } func basicAuthFromEndpoint(ep transport.Endpoint) *BasicAuth { - info := ep.User - if info == nil { + u := ep.User() + if u == "" { return nil } - p, ok := info.Password() - if !ok { - return nil - } - - u := info.Username() - return NewBasicAuth(u, p) + return NewBasicAuth(u, ep.Password()) } // BasicAuth represent a HTTP basic auth diff --git a/plumbing/transport/http/upload_pack.go b/plumbing/transport/http/upload_pack.go index 8f73789..2d1ea45 100644 --- a/plumbing/transport/http/upload_pack.go +++ b/plumbing/transport/http/upload_pack.go @@ -150,7 +150,7 @@ func (s *upSession) doRequest(method, url string, content *bytes.Buffer) (*http. // 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) + req.Header.Add("Host", s.endpoint.Host()) // host:port if content == nil { req.Header.Add("Accept", "*/*") diff --git a/plumbing/transport/server/loader.go b/plumbing/transport/server/loader.go index cfe9f04..b51a795 100644 --- a/plumbing/transport/server/loader.go +++ b/plumbing/transport/server/loader.go @@ -34,7 +34,7 @@ func NewFilesystemLoader(base billy.Filesystem) Loader { // storer for it. Returns transport.ErrRepositoryNotFound if a repository does // not exist in the given path. func (l *fsLoader) Load(ep transport.Endpoint) (storer.Storer, error) { - fs := l.base.Dir(ep.Path) + fs := l.base.Dir(ep.Path()) if _, err := fs.Stat("config"); err != nil { return nil, transport.ErrRepositoryNotFound } diff --git a/plumbing/transport/server/loader_test.go b/plumbing/transport/server/loader_test.go index b4a8c37..5f32cb0 100644 --- a/plumbing/transport/server/loader_test.go +++ b/plumbing/transport/server/loader_test.go @@ -1,7 +1,6 @@ package server import ( - "fmt" "os/exec" "path/filepath" @@ -33,7 +32,7 @@ func (s *LoaderSuite) endpoint(c *C, url string) transport.Endpoint { } func (s *LoaderSuite) TestLoadNonExistent(c *C) { - sto, err := DefaultLoader.Load(s.endpoint(c, "file:///does-not-exist")) + sto, err := DefaultLoader.Load(s.endpoint(c, "does-not-exist")) c.Assert(err, Equals, transport.ErrRepositoryNotFound) c.Assert(sto, IsNil) } @@ -45,13 +44,13 @@ func (s *LoaderSuite) TestLoadNonExistentIgnoreHost(c *C) { } func (s *LoaderSuite) TestLoad(c *C) { - sto, err := DefaultLoader.Load(s.endpoint(c, fmt.Sprintf("file://%s", s.RepoPath))) + sto, err := DefaultLoader.Load(s.endpoint(c, s.RepoPath)) c.Assert(err, IsNil) c.Assert(sto, NotNil) } func (s *LoaderSuite) TestLoadIgnoreHost(c *C) { - sto, err := DefaultLoader.Load(s.endpoint(c, fmt.Sprintf("file://%s", s.RepoPath))) + sto, err := DefaultLoader.Load(s.endpoint(c, s.RepoPath)) c.Assert(err, IsNil) c.Assert(sto, NotNil) } diff --git a/plumbing/transport/ssh/auth_method.go b/plumbing/transport/ssh/auth_method.go index a3e1ad1..84dfe14 100644 --- a/plumbing/transport/ssh/auth_method.go +++ b/plumbing/transport/ssh/auth_method.go @@ -179,9 +179,14 @@ type PublicKeysCallback struct { // NewSSHAgentAuth returns a PublicKeysCallback based on a SSH agent, it opens // a pipe with the SSH agent and uses the pipe as the implementer of the public // key callback function. -func NewSSHAgentAuth(user string) (AuthMethod, error) { - if user == "" { - user = DefaultUsername +func NewSSHAgentAuth(u string) (AuthMethod, error) { + if u == "" { + usr, err := user.Current() + if err != nil { + return nil, fmt.Errorf("error getting current user: %q", err) + } + + u = usr.Username } sshAgentAddr := os.Getenv("SSH_AUTH_SOCK") @@ -195,7 +200,7 @@ func NewSSHAgentAuth(user string) (AuthMethod, error) { } return &PublicKeysCallback{ - User: user, + User: u, Callback: agent.NewClient(pipe).Signers, }, nil } diff --git a/plumbing/transport/ssh/common.go b/plumbing/transport/ssh/common.go index 7b44a91..d53fc12 100644 --- a/plumbing/transport/ssh/common.go +++ b/plumbing/transport/ssh/common.go @@ -3,7 +3,6 @@ package ssh import ( "fmt" - "strings" "gopkg.in/src-d/go-git.v4/plumbing/transport" "gopkg.in/src-d/go-git.v4/plumbing/transport/internal/common" @@ -20,6 +19,8 @@ var DefaultAuthBuilder = func(user string) (AuthMethod, error) { return NewSSHAgentAuth(user) } +const DefaultPort = 22 + type runner struct{} func (r *runner) Command(cmd string, ep transport.Endpoint, auth transport.AuthMethod) (common.Command, error) { @@ -110,25 +111,21 @@ func (c *command) connect() error { } func (c *command) getHostWithPort() string { - host := c.endpoint.Host - if strings.Index(c.endpoint.Host, ":") == -1 { - host += ":22" + host := c.endpoint.Host() + port := c.endpoint.Port() + if port <= 0 { + port = DefaultPort } - return host + return fmt.Sprintf("%s:%d", host, port) } func (c *command) setAuthFromEndpoint() error { - var u string - if info := c.endpoint.User; info != nil { - u = info.Username() - } - var err error - c.auth, err = DefaultAuthBuilder(u) + c.auth, err = DefaultAuthBuilder(c.endpoint.User()) return err } func endpointToCommand(cmd string, ep transport.Endpoint) string { - return fmt.Sprintf("%s '%s'", cmd, ep.Path) + return fmt.Sprintf("%s '%s'", cmd, ep.Path()) } diff --git a/references_test.go b/references_test.go index 3cd0b97..050f169 100644 --- a/references_test.go +++ b/references_test.go @@ -295,7 +295,7 @@ func (s *ReferencesSuite) TestObjectNotFoundError(c *C) { url := fixtures.ByURL("https://github.com/git-fixtures/basic.git").One().DotGit().Base() storer := memory.NewStorage() r, err := Clone(storer, nil, &CloneOptions{ - URL: "file://" + url, + URL: url, }) c.Assert(err, IsNil) @@ -8,6 +8,7 @@ import ( "gopkg.in/src-d/go-git.v4/config" "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/object" "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/protocol/packp/sideband" @@ -56,9 +57,7 @@ func (r *Remote) Fetch(o *FetchOptions) error { // remote was already up-to-date. func (r *Remote) Push(o *PushOptions) (err error) { // TODO: Support deletes. - // TODO: Support pushing tags. - // TODO: Check if force update is given, otherwise reject non-fast forward. - // TODO: Sideband suppor + // TODO: Sideband support if o.RemoteName == "" { o.RemoteName = r.c.Name @@ -196,12 +195,12 @@ func newSendPackSession(url string, auth transport.AuthMethod) (transport.Receiv func newClient(url string) (transport.Transport, transport.Endpoint, error) { ep, err := transport.NewEndpoint(url) if err != nil { - return nil, transport.Endpoint{}, err + return nil, nil, err } c, err := client.NewClient(ep) if err != nil { - return nil, transport.Endpoint{}, err + return nil, nil, err } return c, ep, err @@ -265,37 +264,35 @@ func (r *Remote) addReferenceIfRefSpecMatches(rs config.RefSpec, return nil } - dstName := rs.Dst(localRef.Name()) - oldHash := plumbing.ZeroHash - newHash := localRef.Hash() - - iter, err := remoteRefs.IterReferences() - if err != nil { - return err + cmd := &packp.Command{ + Name: rs.Dst(localRef.Name()), + Old: plumbing.ZeroHash, + New: localRef.Hash(), } - err = iter.ForEach(func(remoteRef *plumbing.Reference) error { + remoteRef, err := remoteRefs.Reference(cmd.Name) + if err == nil { if remoteRef.Type() != plumbing.HashReference { + //TODO: check actual git behavior here return nil } - if dstName != remoteRef.Name() { - return nil - } + cmd.Old = remoteRef.Hash() + } else if err != plumbing.ErrReferenceNotFound { + return err + } - oldHash = remoteRef.Hash() + if cmd.Old == cmd.New { return nil - }) + } - if oldHash == newHash { - return nil + if !rs.IsForceUpdate() { + if err := checkFastForwardUpdate(r.s, remoteRefs, cmd); err != nil { + return err + } } - req.Commands = append(req.Commands, &packp.Command{ - Name: dstName, - Old: oldHash, - New: newHash, - }) + req.Commands = append(req.Commands, cmd) return nil } @@ -390,6 +387,50 @@ func objectExists(s storer.EncodedObjectStorer, h plumbing.Hash) (bool, error) { return true, err } +func checkFastForwardUpdate(s storer.EncodedObjectStorer, remoteRefs storer.ReferenceStorer, cmd *packp.Command) error { + if cmd.Old == plumbing.ZeroHash { + _, err := remoteRefs.Reference(cmd.Name) + if err == plumbing.ErrReferenceNotFound { + return nil + } + + if err != nil { + return err + } + + return fmt.Errorf("non-fast-forward update: %s", cmd.Name.String()) + } + + ff, err := isFastForward(s, cmd.Old, cmd.New) + if err != nil { + return err + } + + if !ff { + return fmt.Errorf("non-fast-forward update: %s", cmd.Name.String()) + } + + return nil +} + +func isFastForward(s storer.EncodedObjectStorer, old, new plumbing.Hash) (bool, error) { + c, err := object.GetCommit(s, new) + if err != nil { + return false, err + } + + found := false + iter := object.NewCommitPreIterator(c) + return found, iter.ForEach(func(c *object.Commit) error { + if c.Hash != old { + return nil + } + + found = true + return storer.ErrStop + }) +} + func (r *Remote) newUploadPackRequest(o *FetchOptions, ar *packp.AdvRefs) (*packp.UploadPackRequest, error) { diff --git a/remote_test.go b/remote_test.go index 2cd80cf..8b2f71c 100644 --- a/remote_test.go +++ b/remote_test.go @@ -2,7 +2,6 @@ package git import ( "bytes" - "fmt" "io" "io/ioutil" "os" @@ -26,9 +25,9 @@ type RemoteSuite struct { var _ = Suite(&RemoteSuite{}) func (s *RemoteSuite) TestFetchInvalidEndpoint(c *C) { - r := newRemote(nil, &config.RemoteConfig{Name: "foo", URL: "qux"}) - err := r.Fetch(&FetchOptions{}) - c.Assert(err, ErrorMatches, ".*invalid endpoint.*") + r := newRemote(nil, &config.RemoteConfig{Name: "foo", URL: "http://\\"}) + err := r.Fetch(&FetchOptions{RemoteName: "foo"}) + c.Assert(err, ErrorMatches, ".*invalid character.*") } func (s *RemoteSuite) TestFetchNonExistentEndpoint(c *C) { @@ -215,7 +214,7 @@ func (s *RemoteSuite) TestPushToEmptyRepository(c *C) { c.Assert(err, IsNil) dstFs := fixtures.ByTag("empty").One().DotGit() - url := fmt.Sprintf("file://%s", dstFs.Base()) + url := dstFs.Base() r := newRemote(sto, &config.RemoteConfig{ Name: DefaultRemoteName, @@ -249,11 +248,56 @@ func (s *RemoteSuite) TestPushToEmptyRepository(c *C) { c.Assert(err, IsNil) } +func (s *RemoteSuite) TestPushTags(c *C) { + srcFs := fixtures.ByURL("https://github.com/git-fixtures/tags.git").One().DotGit() + sto, err := filesystem.NewStorage(srcFs) + c.Assert(err, IsNil) + + dstFs := fixtures.ByTag("empty").One().DotGit() + url := dstFs.Base() + + r := newRemote(sto, &config.RemoteConfig{ + Name: DefaultRemoteName, + URL: url, + }) + + rs := config.RefSpec("refs/tags/*:refs/tags/*") + err = r.Push(&PushOptions{ + RefSpecs: []config.RefSpec{rs}, + }) + c.Assert(err, IsNil) + + dstSto, err := filesystem.NewStorage(dstFs) + c.Assert(err, IsNil) + dstRepo, err := Open(dstSto, nil) + c.Assert(err, IsNil) + + ref, err := dstRepo.Storer.Reference(plumbing.ReferenceName("refs/tags/lightweight-tag")) + c.Assert(err, IsNil) + c.Assert(ref, DeepEquals, plumbing.NewReferenceFromStrings("refs/tags/lightweight-tag", "f7b877701fbf855b44c0a9e86f3fdce2c298b07f")) + + ref, err = dstRepo.Storer.Reference(plumbing.ReferenceName("refs/tags/annotated-tag")) + c.Assert(err, IsNil) + c.Assert(ref, DeepEquals, plumbing.NewReferenceFromStrings("refs/tags/annotated-tag", "b742a2a9fa0afcfa9a6fad080980fbc26b007c69")) + + ref, err = dstRepo.Storer.Reference(plumbing.ReferenceName("refs/tags/commit-tag")) + c.Assert(err, IsNil) + c.Assert(ref, DeepEquals, plumbing.NewReferenceFromStrings("refs/tags/commit-tag", "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc")) + + ref, err = dstRepo.Storer.Reference(plumbing.ReferenceName("refs/tags/blob-tag")) + c.Assert(err, IsNil) + c.Assert(ref, DeepEquals, plumbing.NewReferenceFromStrings("refs/tags/blob-tag", "fe6cb94756faa81e5ed9240f9191b833db5f40ae")) + + ref, err = dstRepo.Storer.Reference(plumbing.ReferenceName("refs/tags/tree-tag")) + c.Assert(err, IsNil) + c.Assert(ref, DeepEquals, plumbing.NewReferenceFromStrings("refs/tags/tree-tag", "152175bf7e5580299fa1f0ba41ef6474cc043b70")) +} + func (s *RemoteSuite) TestPushNoErrAlreadyUpToDate(c *C) { f := fixtures.Basic().One() sto, err := filesystem.NewStorage(f.DotGit()) c.Assert(err, IsNil) - url := fmt.Sprintf("file://%s", f.DotGit().Base()) + url := f.DotGit().Base() r := newRemote(sto, &config.RemoteConfig{ Name: DefaultRemoteName, URL: url, @@ -266,10 +310,97 @@ func (s *RemoteSuite) TestPushNoErrAlreadyUpToDate(c *C) { c.Assert(err, Equals, NoErrAlreadyUpToDate) } +func (s *RemoteSuite) TestPushRejectNonFastForward(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) + + url := dstFs.Base() + r := newRemote(sto, &config.RemoteConfig{ + Name: DefaultRemoteName, + URL: url, + }) + + oldRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch")) + c.Assert(err, IsNil) + c.Assert(oldRef, NotNil) + + err = r.Push(&PushOptions{RefSpecs: []config.RefSpec{ + config.RefSpec("refs/heads/master:refs/heads/branch"), + }}) + c.Assert(err, ErrorMatches, "non-fast-forward update: refs/heads/branch") + + newRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch")) + c.Assert(err, IsNil) + c.Assert(newRef, DeepEquals, oldRef) +} + +func (s *RemoteSuite) TestPushForce(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) + + url := dstFs.Base() + r := newRemote(sto, &config.RemoteConfig{ + Name: DefaultRemoteName, + URL: url, + }) + + oldRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch")) + c.Assert(err, IsNil) + c.Assert(oldRef, NotNil) + + err = r.Push(&PushOptions{RefSpecs: []config.RefSpec{ + config.RefSpec("+refs/heads/master:refs/heads/branch"), + }}) + c.Assert(err, IsNil) + + newRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch")) + c.Assert(err, IsNil) + c.Assert(newRef, Not(DeepEquals), oldRef) +} + +func (s *RemoteSuite) TestPushNewReference(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) + + url := dstFs.Base() + r := newRemote(sto, &config.RemoteConfig{ + Name: DefaultRemoteName, + URL: url, + }) + + oldRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch")) + c.Assert(err, IsNil) + c.Assert(oldRef, NotNil) + + err = r.Push(&PushOptions{RefSpecs: []config.RefSpec{ + config.RefSpec("refs/heads/branch:refs/heads/branch2"), + }}) + c.Assert(err, IsNil) + + newRef, err := dstSto.Reference(plumbing.ReferenceName("refs/heads/branch2")) + c.Assert(err, IsNil) + c.Assert(newRef.Hash(), Equals, oldRef.Hash()) +} + func (s *RemoteSuite) TestPushInvalidEndpoint(c *C) { - r := newRemote(nil, &config.RemoteConfig{Name: "foo", URL: "qux"}) - err := r.Push(&PushOptions{}) - c.Assert(err, ErrorMatches, ".*invalid endpoint.*") + r := newRemote(nil, &config.RemoteConfig{Name: "foo", URL: "http://\\"}) + err := r.Push(&PushOptions{RemoteName: "foo"}) + c.Assert(err, ErrorMatches, ".*invalid character.*") } func (s *RemoteSuite) TestPushNonExistentEndpoint(c *C) { @@ -294,7 +425,7 @@ func (s *RemoteSuite) TestPushInvalidFetchOptions(c *C) { func (s *RemoteSuite) TestPushInvalidRefSpec(c *C) { r := newRemote(nil, &config.RemoteConfig{ Name: DefaultRemoteName, - URL: "file:///some-url", + URL: "some-url", }) rs := config.RefSpec("^*$**") @@ -307,7 +438,7 @@ func (s *RemoteSuite) TestPushInvalidRefSpec(c *C) { func (s *RemoteSuite) TestPushWrongRemoteName(c *C) { r := newRemote(nil, &config.RemoteConfig{ Name: DefaultRemoteName, - URL: "file:///some-url", + URL: "some-url", }) err := r.Push(&PushOptions{ diff --git a/repository.go b/repository.go index bb59afe..8a7b348 100644 --- a/repository.go +++ b/repository.go @@ -3,8 +3,10 @@ package git import ( "errors" "fmt" + stdioutil "io/ioutil" "os" "path/filepath" + "strings" "gopkg.in/src-d/go-git.v4/config" "gopkg.in/src-d/go-git.v4/internal/revision" @@ -13,6 +15,7 @@ import ( "gopkg.in/src-d/go-git.v4/plumbing/storer" "gopkg.in/src-d/go-git.v4/storage" "gopkg.in/src-d/go-git.v4/storage/filesystem" + "gopkg.in/src-d/go-git.v4/utils/ioutil" "gopkg.in/src-d/go-billy.v2" "gopkg.in/src-d/go-billy.v2/osfs" @@ -193,26 +196,69 @@ func PlainInit(path string, isBare bool) (*Repository, error) { // repository is bare or a normal one. If the path doesn't contain a valid // repository ErrRepositoryNotExists is returned func PlainOpen(path string) (*Repository, error) { - var wt, dot billy.Filesystem + dot, wt, err := dotGitToFilesystems(path) + if err != nil { + return nil, err + } + + s, err := filesystem.NewStorage(dot) + if err != nil { + return nil, err + } + return Open(s, wt) +} + +func dotGitToFilesystems(path string) (dot, wt billy.Filesystem, err error) { fs := osfs.New(path) - if _, err := fs.Stat(".git"); err != nil { + fi, err := fs.Stat(".git") + if err != nil { if !os.IsNotExist(err) { - return nil, err + return nil, nil, err } - dot = fs - } else { - wt = fs - dot = fs.Dir(".git") + return fs, nil, nil } - s, err := filesystem.NewStorage(dot) + if fi.IsDir() { + return fs.Dir(".git"), fs, nil + } + + dot, err = dotGitFileToFilesystem(fs) + if err != nil { + return nil, nil, err + } + + return dot, fs, nil +} + +func dotGitFileToFilesystem(fs billy.Filesystem) (billy.Filesystem, error) { + var err error + + f, err := fs.Open(".git") if err != nil { return nil, err } + defer ioutil.CheckClose(f, &err) - return Open(s, wt) + b, err := stdioutil.ReadAll(f) + if err != nil { + return nil, err + } + + line := string(b) + const prefix = "gitdir: " + if !strings.HasPrefix(line, prefix) { + return nil, fmt.Errorf(".git file has no %s prefix", prefix) + } + + gitdir := line[len(prefix):] + gitdir = strings.TrimSpace(gitdir) + if filepath.IsAbs(gitdir) { + return osfs.New(gitdir), nil + } + + return fs.Dir(gitdir), err } // PlainClone a repository into the path with the given options, isBare defines diff --git a/repository_test.go b/repository_test.go index 77bfde2..deb3f58 100644 --- a/repository_test.go +++ b/repository_test.go @@ -272,6 +272,93 @@ func (s *RepositorySuite) TestPlainOpenNotBare(c *C) { c.Assert(r, IsNil) } +func (s *RepositorySuite) testPlainOpenGitFile(c *C, f func(string, string) string) { + dir, err := ioutil.TempDir("", "plain-open") + c.Assert(err, IsNil) + defer os.RemoveAll(dir) + + r, err := PlainInit(dir, true) + c.Assert(err, IsNil) + c.Assert(r, NotNil) + + altDir, err := ioutil.TempDir("", "plain-open") + c.Assert(err, IsNil) + defer os.RemoveAll(altDir) + + err = ioutil.WriteFile(filepath.Join(altDir, ".git"), []byte(f(dir, altDir)), 0644) + c.Assert(err, IsNil) + + r, err = PlainOpen(altDir) + c.Assert(err, IsNil) + c.Assert(r, NotNil) +} + +func (s *RepositorySuite) TestPlainOpenBareAbsoluteGitDirFile(c *C) { + s.testPlainOpenGitFile(c, func(dir, altDir string) string { + return fmt.Sprintf("gitdir: %s\n", dir) + }) +} + +func (s *RepositorySuite) TestPlainOpenBareAbsoluteGitDirFileNoEOL(c *C) { + s.testPlainOpenGitFile(c, func(dir, altDir string) string { + return fmt.Sprintf("gitdir: %s", dir) + }) +} + +func (s *RepositorySuite) TestPlainOpenBareRelativeGitDirFile(c *C) { + s.testPlainOpenGitFile(c, func(dir, altDir string) string { + dir, err := filepath.Rel(altDir, dir) + c.Assert(err, IsNil) + return fmt.Sprintf("gitdir: %s\n", dir) + }) +} + +func (s *RepositorySuite) TestPlainOpenBareRelativeGitDirFileNoEOL(c *C) { + s.testPlainOpenGitFile(c, func(dir, altDir string) string { + dir, err := filepath.Rel(altDir, dir) + c.Assert(err, IsNil) + return fmt.Sprintf("gitdir: %s\n", dir) + }) +} + +func (s *RepositorySuite) TestPlainOpenBareRelativeGitDirFileTrailingGarbage(c *C) { + dir, err := ioutil.TempDir("", "plain-open") + c.Assert(err, IsNil) + defer os.RemoveAll(dir) + + r, err := PlainInit(dir, true) + c.Assert(err, IsNil) + c.Assert(r, NotNil) + + altDir, err := ioutil.TempDir("", "plain-open") + c.Assert(err, IsNil) + err = ioutil.WriteFile(filepath.Join(altDir, ".git"), []byte(fmt.Sprintf("gitdir: %s\nTRAILING", dir)), 0644) + c.Assert(err, IsNil) + + r, err = PlainOpen(altDir) + c.Assert(err, Equals, ErrRepositoryNotExists) + c.Assert(r, IsNil) +} + +func (s *RepositorySuite) TestPlainOpenBareRelativeGitDirFileBadPrefix(c *C) { + dir, err := ioutil.TempDir("", "plain-open") + c.Assert(err, IsNil) + defer os.RemoveAll(dir) + + r, err := PlainInit(dir, true) + c.Assert(err, IsNil) + c.Assert(r, NotNil) + + altDir, err := ioutil.TempDir("", "plain-open") + c.Assert(err, IsNil) + err = ioutil.WriteFile(filepath.Join(altDir, ".git"), []byte(fmt.Sprintf("xgitdir: %s\n", dir)), 0644) + c.Assert(err, IsNil) + + r, err = PlainOpen(altDir) + c.Assert(err, ErrorMatches, ".*gitdir.*") + c.Assert(r, IsNil) +} + func (s *RepositorySuite) TestPlainOpenNotExists(c *C) { r, err := PlainOpen("/not-exists/") c.Assert(err, Equals, ErrRepositoryNotExists) @@ -301,7 +388,7 @@ func (s *RepositorySuite) TestPlainCloneWithRecurseSubmodules(c *C) { path := fixtures.ByTag("submodule").One().Worktree().Base() r, err := PlainClone(dir, false, &CloneOptions{ - URL: fmt.Sprintf("file://%s", path), + URL: path, RecurseSubmodules: DefaultSubmoduleRecursionDepth, }) @@ -591,7 +678,7 @@ func (s *RepositorySuite) TestPullProgressWithRecursion(c *C) { r, _ := PlainInit(dir, false) r.CreateRemote(&config.RemoteConfig{ Name: DefaultRemoteName, - URL: fmt.Sprintf("file://%s", path), + URL: path, }) err = r.Pull(&PullOptions{ @@ -607,7 +694,7 @@ func (s *RepositorySuite) TestPullAdd(c *C) { path := fixtures.Basic().ByTag("worktree").One().Worktree().Base() r, err := Clone(memory.NewStorage(), nil, &CloneOptions{ - URL: fmt.Sprintf("file://%s", filepath.Join(path, ".git")), + URL: filepath.Join(path, ".git"), }) c.Assert(err, IsNil) @@ -642,7 +729,7 @@ func (s *RepositorySuite) TestPushToEmptyRepository(c *C) { c.Assert(err, IsNil) dstFs := fixtures.ByTag("empty").One().DotGit() - url := fmt.Sprintf("file://%s", dstFs.Base()) + url := dstFs.Base() r, err := Open(sto, srcFs) c.Assert(err, IsNil) diff --git a/submodule_test.go b/submodule_test.go index 6e06191..fdbe4a8 100644 --- a/submodule_test.go +++ b/submodule_test.go @@ -1,15 +1,14 @@ package git import ( - "fmt" "io/ioutil" "os" "path/filepath" "github.com/src-d/go-git-fixtures" + "gopkg.in/src-d/go-git.v4/plumbing" . "gopkg.in/check.v1" - "gopkg.in/src-d/go-git.v4/plumbing" ) type SubmoduleSuite struct { @@ -27,7 +26,7 @@ func (s *SubmoduleSuite) SetUpTest(c *C) { c.Assert(err, IsNil) r, err := PlainClone(filepath.Join(dir, "worktree"), false, &CloneOptions{ - URL: fmt.Sprintf("file://%s", path), + URL: path, }) c.Assert(err, IsNil) diff --git a/worktree_status.go b/worktree_status.go index 46953c7..632f102 100644 --- a/worktree_status.go +++ b/worktree_status.go @@ -233,42 +233,36 @@ func (w *Worktree) addOrUpdateFileToIndex(filename string, h plumbing.Hash) erro return err } - _, err = idx.Entry(filename) - if err == index.ErrEntryNotFound { - err = w.doAddFileToIndex(idx, filename) - } - - if err != nil { + e, err := idx.Entry(filename) + if err != nil && err != index.ErrEntryNotFound { return err } - err = w.doUpdateFileToIndex(idx, filename, h) - if err != nil { - return err + if err == index.ErrEntryNotFound { + if err := w.doAddFileToIndex(idx, filename, h); err != nil { + return err + } + } else { + if err := w.doUpdateFileToIndex(e, filename, h); err != nil { + return err + } } - return w.r.Storer.SetIndex(idx) } -func (w *Worktree) doAddFileToIndex(idx *index.Index, filename string) error { - idx.Entries = append(idx.Entries, &index.Entry{ - Name: filename, - }) +func (w *Worktree) doAddFileToIndex(idx *index.Index, filename string, h plumbing.Hash) error { + e := &index.Entry{Name: filename} + idx.Entries = append(idx.Entries, e) - return nil + return w.doUpdateFileToIndex(e, filename, h) } -func (w *Worktree) doUpdateFileToIndex(idx *index.Index, filename string, h plumbing.Hash) error { +func (w *Worktree) doUpdateFileToIndex(e *index.Entry, filename string, h plumbing.Hash) error { info, err := w.fs.Stat(filename) if err != nil { return err } - e, err := idx.Entry(filename) - if err != nil { - return err - } - e.Hash = h e.ModifiedAt = info.ModTime() e.Mode, err = filemode.NewFromOSFileMode(info.Mode()) |