Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit 45bdbcb

Browse files
committed
transport: make Endpoint an interface, fixes #362
* add internal *url.URL implementation for regular URLs. * add internal implementation for SCP-like URLs.
1 parent 64cd72d commit 45bdbcb

File tree

11 files changed

+201
-62
lines changed

11 files changed

+201
-62
lines changed

plumbing/transport/client/client.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,13 @@ func InstallProtocol(scheme string, c transport.Transport) {
3535
// http://, https://, ssh:// and file://.
3636
// See `InstallProtocol` to add or modify protocols.
3737
func NewClient(endpoint transport.Endpoint) (transport.Transport, error) {
38-
f, ok := Protocols[endpoint.Scheme]
38+
f, ok := Protocols[endpoint.Protocol()]
3939
if !ok {
40-
return nil, fmt.Errorf("unsupported scheme %q", endpoint.Scheme)
40+
return nil, fmt.Errorf("unsupported scheme %q", endpoint.Protocol())
4141
}
4242

4343
if f == nil {
44-
return nil, fmt.Errorf("malformed client for scheme %q, client is defined as nil", endpoint.Scheme)
44+
return nil, fmt.Errorf("malformed client for scheme %q, client is defined as nil", endpoint.Protocol())
4545
}
4646

4747
return f, nil

plumbing/transport/common.go

Lines changed: 112 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"io"
1919
"net/url"
2020
"regexp"
21+
"strconv"
2122

2223
"gopkg.in/src-d/go-git.v4/plumbing"
2324
"gopkg.in/src-d/go-git.v4/plumbing/protocol/packp"
@@ -28,7 +29,7 @@ var (
2829
ErrRepositoryNotFound = errors.New("repository not found")
2930
ErrEmptyRemoteRepository = errors.New("remote repository is empty")
3031
ErrAuthenticationRequired = errors.New("authentication required")
31-
ErrAuthorizationFailed = errors.New("authorization failed")
32+
ErrAuthorizationFailed = errors.New("authorization failed")
3233
ErrEmptyUploadPackRequest = errors.New("empty git-upload-pack given")
3334
ErrInvalidAuthMethod = errors.New("invalid auth method")
3435
ErrAlreadyConnected = errors.New("session already established")
@@ -88,42 +89,134 @@ type ReceivePackSession interface {
8889
ReceivePack(*packp.ReferenceUpdateRequest) (*packp.ReportStatus, error)
8990
}
9091

91-
type Endpoint url.URL
92-
93-
var (
94-
isSchemeRegExp = regexp.MustCompile("^[^:]+://")
95-
scpLikeUrlRegExp = regexp.MustCompile("^(?P<user>[^@]+@)?(?P<host>[^:]+):/?(?P<path>.+)$")
96-
)
92+
// Endpoint represents a Git URL in any supported protocol.
93+
type Endpoint interface {
94+
// Protocol returns the protocol (e.g. git, https, file). It should never
95+
// return the empty string.
96+
Protocol() string
97+
// User returns the user or an empty string if none is given.
98+
User() string
99+
// Password returns the password or an empty string if none is given.
100+
Password() string
101+
// Host returns the host or an empty string if none is given.
102+
Host() string
103+
// Port returns the port or 0 if there is no port or a default should be
104+
// used.
105+
Port() int
106+
// Path returns the repository path.
107+
Path() string
108+
// String returns a string representation of the Git URL.
109+
String() string
110+
}
97111

98112
func NewEndpoint(endpoint string) (Endpoint, error) {
99-
endpoint = transformSCPLikeIfNeeded(endpoint)
113+
if e, ok := parseSCPLike(endpoint); ok {
114+
return e, nil
115+
}
100116

101117
u, err := url.Parse(endpoint)
102118
if err != nil {
103-
return Endpoint{}, plumbing.NewPermanentError(err)
119+
return nil, plumbing.NewPermanentError(err)
104120
}
105121

106122
if !u.IsAbs() {
107-
return Endpoint{}, plumbing.NewPermanentError(fmt.Errorf(
123+
return nil, plumbing.NewPermanentError(fmt.Errorf(
108124
"invalid endpoint: %s", endpoint,
109125
))
110126
}
111127

112-
return Endpoint(*u), nil
128+
return urlEndpoint{u}, nil
129+
}
130+
131+
type urlEndpoint struct {
132+
*url.URL
133+
}
134+
135+
func (e urlEndpoint) Protocol() string { return e.URL.Scheme }
136+
func (e urlEndpoint) Host() string { return e.URL.Hostname() }
137+
138+
func (e urlEndpoint) User() string {
139+
if e.URL.User == nil {
140+
return ""
141+
}
142+
143+
return e.URL.User.Username()
144+
}
145+
146+
func (e urlEndpoint) Password() string {
147+
if e.URL.User == nil {
148+
return ""
149+
}
150+
151+
p, _ := e.URL.User.Password()
152+
return p
153+
}
154+
155+
func (e urlEndpoint) Port() int {
156+
p := e.URL.Port()
157+
if p == "" {
158+
return 0
159+
}
160+
161+
i, err := strconv.Atoi(e.URL.Port())
162+
if err != nil {
163+
return 0
164+
}
165+
166+
return i
113167
}
114168

115-
func (e *Endpoint) String() string {
116-
u := url.URL(*e)
117-
return u.String()
169+
func (e urlEndpoint) Path() string {
170+
var res string = e.URL.Path
171+
if e.URL.RawQuery != "" {
172+
res += "?" + e.URL.RawQuery
173+
}
174+
175+
if e.URL.Fragment != "" {
176+
res += "#" + e.URL.Fragment
177+
}
178+
179+
return res
118180
}
119181

120-
func transformSCPLikeIfNeeded(endpoint string) string {
121-
if !isSchemeRegExp.MatchString(endpoint) && scpLikeUrlRegExp.MatchString(endpoint) {
122-
m := scpLikeUrlRegExp.FindStringSubmatch(endpoint)
123-
return fmt.Sprintf("ssh://%s%s/%s", m[1], m[2], m[3])
182+
type scpEndpoint struct {
183+
user string
184+
host string
185+
path string
186+
}
187+
188+
func (e *scpEndpoint) Protocol() string { return "ssh" }
189+
func (e *scpEndpoint) User() string { return e.user }
190+
func (e *scpEndpoint) Password() string { return "" }
191+
func (e *scpEndpoint) Host() string { return e.host }
192+
func (e *scpEndpoint) Port() int { return 22 }
193+
func (e *scpEndpoint) Path() string { return e.path }
194+
195+
func (e *scpEndpoint) String() string {
196+
var user string
197+
if e.user != "" {
198+
user = fmt.Sprintf("%s@", e.user)
199+
}
200+
201+
return fmt.Sprintf("%s%s:%s", user, e.host, e.path)
202+
}
203+
204+
var (
205+
isSchemeRegExp = regexp.MustCompile("^[^:]+://")
206+
scpLikeUrlRegExp = regexp.MustCompile("^(?:(?P<user>[^@]+)@)?(?P<host>[^:]+):/?(?P<path>.+)$")
207+
)
208+
209+
func parseSCPLike(endpoint string) (Endpoint, bool) {
210+
if isSchemeRegExp.MatchString(endpoint) || !scpLikeUrlRegExp.MatchString(endpoint) {
211+
return nil, false
124212
}
125213

126-
return endpoint
214+
m := scpLikeUrlRegExp.FindStringSubmatch(endpoint)
215+
return &scpEndpoint{
216+
user: m[1],
217+
host: m[2],
218+
path: m[3],
219+
}, true
127220
}
128221

129222
// UnsupportedCapabilities are the capabilities not supported by any client

plumbing/transport/common_test.go

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,70 @@ type SuiteCommon struct{}
1414

1515
var _ = Suite(&SuiteCommon{})
1616

17-
func (s *SuiteCommon) TestNewEndpoint(c *C) {
17+
func (s *SuiteCommon) TestNewEndpointHTTP(c *C) {
18+
e, err := NewEndpoint("http://git:[email protected]/user/repository.git?foo#bar")
19+
c.Assert(err, IsNil)
20+
c.Assert(e.Protocol(), Equals, "http")
21+
c.Assert(e.User(), Equals, "git")
22+
c.Assert(e.Password(), Equals, "pass")
23+
c.Assert(e.Host(), Equals, "github.com")
24+
c.Assert(e.Port(), Equals, 0)
25+
c.Assert(e.Path(), Equals, "/user/repository.git?foo#bar")
26+
c.Assert(e.String(), Equals, "http://git:[email protected]/user/repository.git?foo#bar")
27+
}
28+
29+
func (s *SuiteCommon) TestNewEndpointSSH(c *C) {
1830
e, err := NewEndpoint("ssh://[email protected]/user/repository.git")
1931
c.Assert(err, IsNil)
32+
c.Assert(e.Protocol(), Equals, "ssh")
33+
c.Assert(e.User(), Equals, "git")
34+
c.Assert(e.Password(), Equals, "")
35+
c.Assert(e.Host(), Equals, "github.com")
36+
c.Assert(e.Port(), Equals, 0)
37+
c.Assert(e.Path(), Equals, "/user/repository.git")
2038
c.Assert(e.String(), Equals, "ssh://[email protected]/user/repository.git")
2139
}
2240

41+
func (s *SuiteCommon) TestNewEndpointSSHNoUser(c *C) {
42+
e, err := NewEndpoint("ssh://github.com/user/repository.git")
43+
c.Assert(err, IsNil)
44+
c.Assert(e.Protocol(), Equals, "ssh")
45+
c.Assert(e.User(), Equals, "")
46+
c.Assert(e.Password(), Equals, "")
47+
c.Assert(e.Host(), Equals, "github.com")
48+
c.Assert(e.Port(), Equals, 0)
49+
c.Assert(e.Path(), Equals, "/user/repository.git")
50+
c.Assert(e.String(), Equals, "ssh://github.com/user/repository.git")
51+
}
52+
53+
func (s *SuiteCommon) TestNewEndpointSSHWithPort(c *C) {
54+
e, err := NewEndpoint("ssh://[email protected]:777/user/repository.git")
55+
c.Assert(err, IsNil)
56+
c.Assert(e.Protocol(), Equals, "ssh")
57+
c.Assert(e.User(), Equals, "git")
58+
c.Assert(e.Password(), Equals, "")
59+
c.Assert(e.Host(), Equals, "github.com")
60+
c.Assert(e.Port(), Equals, 777)
61+
c.Assert(e.Path(), Equals, "/user/repository.git")
62+
c.Assert(e.String(), Equals, "ssh://[email protected]:777/user/repository.git")
63+
}
64+
2365
func (s *SuiteCommon) TestNewEndpointSCPLike(c *C) {
2466
e, err := NewEndpoint("[email protected]:user/repository.git")
2567
c.Assert(err, IsNil)
26-
c.Assert(e.String(), Equals, "ssh://[email protected]/user/repository.git")
68+
c.Assert(e.Protocol(), Equals, "ssh")
69+
c.Assert(e.User(), Equals, "git")
70+
c.Assert(e.Password(), Equals, "")
71+
c.Assert(e.Host(), Equals, "github.com")
72+
c.Assert(e.Port(), Equals, 22)
73+
c.Assert(e.Path(), Equals, "user/repository.git")
74+
c.Assert(e.String(), Equals, "[email protected]:user/repository.git")
2775
}
2876

2977
func (s *SuiteCommon) TestNewEndpointWrongForgat(c *C) {
3078
e, err := NewEndpoint("foo")
31-
c.Assert(err, Not(IsNil))
32-
c.Assert(e.Host, Equals, "")
79+
c.Assert(err, NotNil)
80+
c.Assert(e, IsNil)
3381
}
3482

3583
func (s *SuiteCommon) TestFilterUnsupportedCapabilities(c *C) {

plumbing/transport/file/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func (r *runner) Command(cmd string, ep transport.Endpoint, auth transport.AuthM
4141
return nil, err
4242
}
4343

44-
return &command{cmd: exec.Command(cmd, ep.Path)}, nil
44+
return &command{cmd: exec.Command(cmd, ep.Path())}, nil
4545
}
4646

4747
type command struct {

plumbing/transport/git/common.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"io"
77
"net"
8-
"strings"
98

109
"gopkg.in/src-d/go-git.v4/plumbing/format/pktline"
1110
"gopkg.in/src-d/go-git.v4/plumbing/transport"
@@ -16,6 +15,8 @@ import (
1615
// DefaultClient is the default git client.
1716
var DefaultClient = common.NewClient(&runner{})
1817

18+
const DefaultPort = 9418
19+
1920
type runner struct{}
2021

2122
// Command returns a new Command for the given cmd in the given Endpoint
@@ -62,12 +63,13 @@ func (c *command) connect() error {
6263
}
6364

6465
func (c *command) getHostWithPort() string {
65-
host := c.endpoint.Host
66-
if strings.Index(c.endpoint.Host, ":") == -1 {
67-
host += ":9418"
66+
host := c.endpoint.Host()
67+
port := c.endpoint.Port()
68+
if port <= 0 {
69+
port = DefaultPort
6870
}
6971

70-
return host
72+
return fmt.Sprintf("%s:%d", host, port)
7173
}
7274

7375
// StderrPipe git protocol doesn't have any dedicated error channel
@@ -88,7 +90,7 @@ func (c *command) StdoutPipe() (io.Reader, error) {
8890
}
8991

9092
func endpointToCommand(cmd string, ep transport.Endpoint) string {
91-
return fmt.Sprintf("%s %s%chost=%s%c", cmd, ep.Path, 0, ep.Host, 0)
93+
return fmt.Sprintf("%s %s%chost=%s%c", cmd, ep.Path(), 0, ep.Host(), 0)
9294
}
9395

9496
// Wait no-op function, required by the interface

plumbing/transport/http/common.go

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,12 @@ type AuthMethod interface {
7373
}
7474

7575
func basicAuthFromEndpoint(ep transport.Endpoint) *BasicAuth {
76-
info := ep.User
77-
if info == nil {
76+
u := ep.User()
77+
if u == "" {
7878
return nil
7979
}
8080

81-
p, ok := info.Password()
82-
if !ok {
83-
return nil
84-
}
85-
86-
u := info.Username()
87-
return NewBasicAuth(u, p)
81+
return NewBasicAuth(u, ep.Password())
8882
}
8983

9084
// BasicAuth represent a HTTP basic auth

plumbing/transport/http/upload_pack.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ func (s *upSession) doRequest(method, url string, content *bytes.Buffer) (*http.
150150
// it requires a bytes.Buffer, because we need to know the length
151151
func (s *upSession) applyHeadersToRequest(req *http.Request, content *bytes.Buffer) {
152152
req.Header.Add("User-Agent", "git/1.0")
153-
req.Header.Add("Host", s.endpoint.Host)
153+
req.Header.Add("Host", s.endpoint.Host()) // host:port
154154

155155
if content == nil {
156156
req.Header.Add("Accept", "*/*")

plumbing/transport/server/loader.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func NewFilesystemLoader(base billy.Filesystem) Loader {
3434
// storer for it. Returns transport.ErrRepositoryNotFound if a repository does
3535
// not exist in the given path.
3636
func (l *fsLoader) Load(ep transport.Endpoint) (storer.Storer, error) {
37-
fs := l.base.Dir(ep.Path)
37+
fs := l.base.Dir(ep.Path())
3838
if _, err := fs.Stat("config"); err != nil {
3939
return nil, transport.ErrRepositoryNotFound
4040
}

plumbing/transport/ssh/auth_method.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,14 @@ type PublicKeysCallback struct {
179179
// NewSSHAgentAuth returns a PublicKeysCallback based on a SSH agent, it opens
180180
// a pipe with the SSH agent and uses the pipe as the implementer of the public
181181
// key callback function.
182-
func NewSSHAgentAuth(user string) (AuthMethod, error) {
183-
if user == "" {
184-
user = DefaultUsername
182+
func NewSSHAgentAuth(u string) (AuthMethod, error) {
183+
if u == "" {
184+
usr, err := user.Current()
185+
if err != nil {
186+
return nil, fmt.Errorf("error getting current user: %q", err)
187+
}
188+
189+
u = usr.Username
185190
}
186191

187192
sshAgentAddr := os.Getenv("SSH_AUTH_SOCK")
@@ -195,7 +200,7 @@ func NewSSHAgentAuth(user string) (AuthMethod, error) {
195200
}
196201

197202
return &PublicKeysCallback{
198-
User: user,
203+
User: u,
199204
Callback: agent.NewClient(pipe).Signers,
200205
}, nil
201206
}

0 commit comments

Comments
 (0)