diff options
author | Santiago M. Mola <santi@mola.io> | 2017-04-26 17:04:59 +0200 |
---|---|---|
committer | Santiago M. Mola <santi@mola.io> | 2017-04-27 14:09:41 +0200 |
commit | 45bdbcbe6fdab5a8a4ed4f1b16c191f400a0f6b6 (patch) | |
tree | 5cd0364d068255d361a657963080a78c7ab735d9 /plumbing | |
parent | 64cd72debb2a94a49de5ffd3c3a6bfd626df7340 (diff) | |
download | go-git-45bdbcbe6fdab5a8a4ed4f1b16c191f400a0f6b6.tar.gz |
transport: make Endpoint an interface, fixes #362
* add internal *url.URL implementation for regular URLs.
* add internal implementation for SCP-like URLs.
Diffstat (limited to 'plumbing')
-rw-r--r-- | plumbing/transport/client/client.go | 6 | ||||
-rw-r--r-- | plumbing/transport/common.go | 131 | ||||
-rw-r--r-- | plumbing/transport/common_test.go | 56 | ||||
-rw-r--r-- | plumbing/transport/file/client.go | 2 | ||||
-rw-r--r-- | plumbing/transport/git/common.go | 14 | ||||
-rw-r--r-- | plumbing/transport/http/common.go | 12 | ||||
-rw-r--r-- | plumbing/transport/http/upload_pack.go | 2 | ||||
-rw-r--r-- | plumbing/transport/server/loader.go | 2 | ||||
-rw-r--r-- | plumbing/transport/ssh/auth_method.go | 13 | ||||
-rw-r--r-- | plumbing/transport/ssh/common.go | 21 |
10 files changed, 199 insertions, 60 deletions
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..0ff9e89 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,134 @@ 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 + } 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 *Endpoint) String() string { - u := url.URL(*e) - return u.String() +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 } -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]) +type scpEndpoint struct { + user string + host string + path 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) +} + +var ( + isSchemeRegExp = regexp.MustCompile("^[^:]+://") + scpLikeUrlRegExp = regexp.MustCompile("^(?:(?P<user>[^@]+)@)?(?P<host>[^:]+):/?(?P<path>.+)$") +) + +func parseSCPLike(endpoint string) (Endpoint, bool) { + if isSchemeRegExp.MatchString(endpoint) || !scpLikeUrlRegExp.MatchString(endpoint) { + return nil, false } - return endpoint + m := scpLikeUrlRegExp.FindStringSubmatch(endpoint) + return &scpEndpoint{ + user: m[1], + host: m[2], + path: m[3], + }, 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..ce41045 100644 --- a/plumbing/transport/common_test.go +++ b/plumbing/transport/common_test.go @@ -14,22 +14,70 @@ 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) TestNewEndpointWrongForgat(c *C) { e, err := NewEndpoint("foo") - c.Assert(err, Not(IsNil)) - c.Assert(e.Host, Equals, "") + 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/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/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()) } |