diff --git a/util/git/hosts.go b/util/git/hosts.go index 6c3a3db9..d9c7971b 100644 --- a/util/git/hosts.go +++ b/util/git/hosts.go @@ -18,22 +18,25 @@ type HostHandler interface { } var hostHandlers []struct { - exactHost string - handler HostHandler + host string + handler HostHandler } // RegisterHostHandler allows for the registration of a host handler -// that is triggered when the host of a URL exactly matches the given host, +// that is triggered when the host of a URL matches the given host, // ignoring case. // +// If host starts with a ".", it will be matched as a domain suffix. +// Exact host matches take precedence over suffix matches. +// // This method is not thread-safe, and is expected to be called from init(). -func RegisterHostHandler(exactHost string, handler HostHandler) { +func RegisterHostHandler(host string, handler HostHandler) { hostHandlers = append(hostHandlers, struct { - exactHost string - handler HostHandler + host string + handler HostHandler }{ - exactHost: exactHost, - handler: handler, + host: strings.ToLower(host), + handler: handler, }) } @@ -41,7 +44,12 @@ func RegisterHostHandler(exactHost string, handler HostHandler) { func getHostHandler(host string) HostHandler { host = strings.ToLower(host) for _, h := range hostHandlers { - if h.exactHost == host { + if h.host == host { + return h.handler + } + } + for _, h := range hostHandlers { + if strings.HasPrefix(h.host, ".") && strings.HasSuffix(host, h.host) { return h.handler } } @@ -143,9 +151,14 @@ func (h *StandardHostHandler) Canon(u *url.URL) *url.URL { res.User = nil } + // Always strip trailing slashes first so segment counting and .git suffix + // handling operate on the path without trailing slashes. If h.HasTrailingSlash + // is true, a trailing slash will be re-added at the end. + res.Path = strings.TrimRight(res.Path, "/") + if h.LowerPathSegments != 0 { - // Strip the prefix and trailing slash so that we can count the remaining path segments correctly. - path := strings.TrimRight(res.Path, "/") + // Strip the prefix so that we can count the remaining path segments correctly. + path := res.Path prefix := "" if h.PathPrefix != "" { prefix = "/" + strings.Trim(h.PathPrefix, "/") @@ -184,11 +197,7 @@ func (h *StandardHostHandler) Canon(u *url.URL) *url.URL { } if h.HasTrailingSlash { - if !strings.HasSuffix(res.Path, "/") { - res.Path += "/" - } - } else { - res.Path = strings.TrimSuffix(res.Path, "/") + res.Path += "/" } return &res @@ -232,4 +241,11 @@ func init() { MinPathSegments: 2, MaxPathSegments: 2, }) + + RegisterHostHandler(".googlesource.com", &StandardHostHandler{ + ForceScheme: "https", + StripUser: true, + HasDotGitSuffix: true, + MinPathSegments: 1, + }) } diff --git a/util/git/hosts_test.go b/util/git/hosts_test.go index 3b85879d..fd4175aa 100644 --- a/util/git/hosts_test.go +++ b/util/git/hosts_test.go @@ -12,8 +12,12 @@ func TestGetHostHandler(t *testing.T) { hostHandlers = nil h1 := &StandardHostHandler{} + h2 := &StandardHostHandler{} + h3 := &StandardHostHandler{} RegisterHostHandler("example.com", h1) + RegisterHostHandler(".other.com", h2) + RegisterHostHandler("special.other.com", h3) tests := []struct { host string @@ -22,6 +26,9 @@ func TestGetHostHandler(t *testing.T) { {"example.com", h1}, {"foo.example.com", noop}, {"other.com", noop}, + {"foo.other.com", h2}, + {"bar.foo.other.com", h2}, + {"special.other.com", h3}, } for _, tt := range tests { @@ -179,12 +186,36 @@ func TestStandardHostHandler_Canon(t *testing.T) { url: "https://example.com/foo/bar", want: "https://example.com/foo/bar.git", }, + { + name: "add .git suffix when input has trailing slash", + handler: StandardHostHandler{HasDotGitSuffix: true}, + url: "https://example.com/foo/bar/", + want: "https://example.com/foo/bar.git", + }, + { + name: "keep .git suffix when input has trailing slash", + handler: StandardHostHandler{HasDotGitSuffix: true}, + url: "https://example.com/foo/bar.git/", + want: "https://example.com/foo/bar.git", + }, + { + name: "add .git suffix and keep trailing slash", + handler: StandardHostHandler{HasDotGitSuffix: true, HasTrailingSlash: true}, + url: "https://example.com/foo/bar/", + want: "https://example.com/foo/bar.git/", + }, { name: "has dot git suffix (remove)", handler: StandardHostHandler{HasDotGitSuffix: false}, url: "https://example.com/foo/bar.git", want: "https://example.com/foo/bar", }, + { + name: "remove .git suffix when input has trailing slash", + handler: StandardHostHandler{HasDotGitSuffix: false}, + url: "https://example.com/foo/bar.git/", + want: "https://example.com/foo/bar", + }, { name: "has trailing slash (add)", handler: StandardHostHandler{HasTrailingSlash: true}, diff --git a/util/git/repo.go b/util/git/repo.go index 15f6709c..f8550f89 100644 --- a/util/git/repo.go +++ b/util/git/repo.go @@ -69,8 +69,8 @@ func (r *Repo) ID() string { host = "[" + host + "]" } - path := strings.TrimSuffix(u.EscapedPath(), ".git") - path = strings.TrimSuffix(path, "/") + path := strings.TrimRight(u.EscapedPath(), "/") + path = strings.TrimSuffix(path, ".git") return host + path } diff --git a/util/git/repo_test.go b/util/git/repo_test.go index 7335fefe..9f7deda9 100644 --- a/util/git/repo_test.go +++ b/util/git/repo_test.go @@ -53,6 +53,18 @@ func TestParseRepo(t *testing.T) { input: "https://github.com/only-one", wantErr: ErrInvalidRepo, // defaultHostHandler requires 2 segments }, + { + name: "googlesource trailing slash", + input: "https://chromium.googlesource.com/chromium/src.git/", + wantCanon: "https://chromium.googlesource.com/chromium/src.git", + wantParsed: "https://chromium.googlesource.com/chromium/src.git/", + }, + { + name: "googlesource preserves path case", + input: "https://android.googlesource.com/platform/packages/apps/Settings", + wantCanon: "https://android.googlesource.com/platform/packages/apps/Settings.git", + wantParsed: "https://android.googlesource.com/platform/packages/apps/Settings", + }, } for _, tt := range tests { @@ -114,6 +126,16 @@ func TestRepo_ID(t *testing.T) { input: "https://github.com/user/repo/", want: "github.com/user/repo", }, + { + name: "googlesource .git and trailing slash", + input: "https://chromium.googlesource.com/chromium/src.git/", + want: "chromium.googlesource.com/chromium/src", + }, + { + name: "unregistered host .git and trailing slash", + input: "https://example.com/user/repo.git/", + want: "example.com/user/repo", + }, } for _, tt := range tests {