diff --git a/api/internal/git/repospec.go b/api/internal/git/repospec.go index d08d842e8..b82c5f7d8 100644 --- a/api/internal/git/repospec.go +++ b/api/internal/git/repospec.go @@ -244,9 +244,10 @@ func extractHost(n string) (string, string) { // Now that we have extracted a valid scheme+username, we can parse host itself. - // The file protocol specifies an absolute path to a local git repo. There is no host. + // The file protocol specifies an absolute path to a local git repo. + // Everything after the scheme (including any 'username' we found) is actually part of that path. if scheme == "file://" { - return scheme, n + return scheme, username + n } sepIndex := findPathSeparator(n, acceptSCP) host, rest := n[:sepIndex+1], n[sepIndex+1:] @@ -300,8 +301,12 @@ func validateUsernameAndScheme(username, scheme string, acceptSCPStyle bool) err case "ssh://": // usernames are optional for ssh protocol return nil - case "https://", "http://", "file://": - // usernames are not supported by http or file protocols + case "file://": + // everything following the scheme in the file protocol is a path on the local filesystem, + // which may contain arbitrary characters (theoretically including `@`, which we'd mistake for a username) + return nil + case "https://", "http://": + // usernames are not supported by the http protocol if username != "" { return fmt.Errorf("username %q specified, but %s does not support usernames", username, scheme) } diff --git a/api/internal/git/repospec_test.go b/api/internal/git/repospec_test.go index 6f79e9822..a3f01e88c 100644 --- a/api/internal/git/repospec_test.go +++ b/api/internal/git/repospec_test.go @@ -128,10 +128,6 @@ func TestNewRepoSpecFromUrlErrors(t *testing.T) { "https://git@foo.com/path/to/repo", "url lacks host", }, - "username unsupported with file": { - "file://git@/path/to/repo", - "url lacks repoPath", - }, } for name, testCase := range badData { @@ -512,6 +508,19 @@ func TestNewRepoSpecFromUrl_Smoke(t *testing.T) { GitSuffix: ".git", }, }, + { + name: "username-like filepath with file protocol", + input: "file://git@home/path/to/repository.git//path?ref=branch", + cloneSpec: "file://git@home/path/to/repository.git", + absPath: notCloned.Join("path"), + repoSpec: RepoSpec{ + Host: "file://", + RepoPath: "git@home/path/to/repository", + KustRootPath: "/path", + Ref: "branch", + GitSuffix: ".git", + }, + }, { name: "unsupported protocol after username (invalid and will be rejected by git)", input: "git@scp://github.com/org/repo.git//path",