aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing
diff options
context:
space:
mode:
authorSantiago M. Mola <santi@mola.io>2017-04-26 17:04:59 +0200
committerSantiago M. Mola <santi@mola.io>2017-04-27 14:09:41 +0200
commit45bdbcbe6fdab5a8a4ed4f1b16c191f400a0f6b6 (patch)
tree5cd0364d068255d361a657963080a78c7ab735d9 /plumbing
parent64cd72debb2a94a49de5ffd3c3a6bfd626df7340 (diff)
downloadgo-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.go6
-rw-r--r--plumbing/transport/common.go131
-rw-r--r--plumbing/transport/common_test.go56
-rw-r--r--plumbing/transport/file/client.go2
-rw-r--r--plumbing/transport/git/common.go14
-rw-r--r--plumbing/transport/http/common.go12
-rw-r--r--plumbing/transport/http/upload_pack.go2
-rw-r--r--plumbing/transport/server/loader.go2
-rw-r--r--plumbing/transport/ssh/auth_method.go13
-rw-r--r--plumbing/transport/ssh/common.go21
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())
}