diff --git a/cmd/kratos/internal/base/repo.go b/cmd/kratos/internal/base/repo.go index 55b12ac0d..bdcb3f010 100644 --- a/cmd/kratos/internal/base/repo.go +++ b/cmd/kratos/internal/base/repo.go @@ -3,13 +3,15 @@ package base import ( "context" "fmt" - stdurl "net/url" + "net" "os" "os/exec" "path" "strings" ) +var unExpandVarPath = []string{"~", ".", ".."} + // Repo is git repository manager. type Repo struct { url string @@ -18,27 +20,21 @@ type Repo struct { } func repoDir(url string) string { - if !strings.Contains(url, "//") { - url = "//" + url - } - if strings.HasPrefix(url, "//git@") { - url = "ssh:" + url - } else if strings.HasPrefix(url, "//") { - url = "https:" + url + vcsURL, err := ParseVCSUrl(url) + if err != nil { + return url } - u, err := stdurl.Parse(url) - if err == nil { - url = fmt.Sprintf("%s://%s%s", u.Scheme, u.Hostname(), u.Path) + // check host contains port + host, _, err := net.SplitHostPort(vcsURL.Host) + if err != nil { + host = vcsURL.Host } - var start int - start = strings.Index(url, "//") - if start == -1 { - start = strings.Index(url, ":") + 1 - } else { - start += 2 + for _, p := range unExpandVarPath { + host = strings.TrimLeft(host, p) } - end := strings.LastIndex(url, "/") - return url[start:end] + dir := path.Base(path.Dir(vcsURL.Path)) + url = fmt.Sprintf("%s/%s", host, dir) + return url } // NewRepo new a repository manager. diff --git a/cmd/kratos/internal/base/repo_test.go b/cmd/kratos/internal/base/repo_test.go index 15ed33c8c..dfcdbb93c 100644 --- a/cmd/kratos/internal/base/repo_test.go +++ b/cmd/kratos/internal/base/repo_test.go @@ -7,7 +7,37 @@ import ( ) func TestRepo(t *testing.T) { - os.RemoveAll("/tmp/test_repo") + urls := []string{ + // ssh://[user@]host.xz[:port]/path/to/repo.git/ + "ssh://git@github.com:7875/go-kratos/kratos.git", + // git://host.xz[:port]/path/to/repo.git/ + "git://github.com:7875/go-kratos/kratos.git", + // http[s]://host.xz[:port]/path/to/repo.git/ + "https://github.com:7875/go-kratos/kratos.git", + // ftp[s]://host.xz[:port]/path/to/repo.git/ + "ftps://github.com:7875/go-kratos/kratos.git", + //[user@]host.xz:path/to/repo.git/ + "git@github.com:go-kratos/kratos.git", + // ssh://[user@]host.xz[:port]/~[user]/path/to/repo.git/ + "ssh://git@github.com:7875/go-kratos/kratos.git", + // git://host.xz[:port]/~[user]/path/to/repo.git/ + "git://github.com:7875/go-kratos/kratos.git", + //[user@]host.xz:/~[user]/path/to/repo.git/ + "git@github.com:go-kratos/kratos.git", + ///path/to/repo.git/ + "//github.com/go-kratos/kratos.git", + // file:///path/to/repo.git/ + "file://./github.com/go-kratos/kratos.git", + } + for _, url := range urls { + dir := repoDir(url) + if dir != "github.com/go-kratos" && dir != "/go-kratos" { + t.Fatal(url, "repoDir test failed", dir) + } + } +} + +func TestRepoClone(t *testing.T) { r := NewRepo("https://github.com/go-kratos/service-layout.git", "") if err := r.Clone(context.Background()); err != nil { t.Fatal(err) @@ -15,19 +45,7 @@ func TestRepo(t *testing.T) { if err := r.CopyTo(context.Background(), "/tmp/test_repo", "github.com/go-kratos/kratos-layout", nil); err != nil { t.Fatal(err) } - urls := []string{ - "ssh://git@gitlab.xxx.com:1234/foo/bar.git", - "ssh://gitlab.xxx.com:1234/foo/bar.git", - "//git@gitlab.xxx.com:1234/foo/bar.git", - "git@gitlab.xxx.com:1234/foo/bar.git", - "gitlab.xxx.com:1234/foo/bar.git", - "gitlab.xxx.com/foo/bar.git", - "gitlab.xxx.com/foo/bar", - } - for _, url := range urls { - dir := repoDir(url) - if dir != "gitlab.xxx.com/foo" { - t.Fatal("repoDir test failed", dir) - } - } + t.Cleanup(func() { + os.RemoveAll("/tmp/test_repo") + }) } diff --git a/cmd/kratos/internal/base/vcs_url.go b/cmd/kratos/internal/base/vcs_url.go new file mode 100644 index 000000000..ef76db30f --- /dev/null +++ b/cmd/kratos/internal/base/vcs_url.go @@ -0,0 +1,58 @@ +package base + +import ( + "errors" + "net/url" + "regexp" + "strings" +) + +var ( + scpSyntaxRe = regexp.MustCompile(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`) + scheme = []string{"git", "https", "http", "git+ssh", "ssh", "file", "ftp", "ftps"} +) + +// ParseVCSUrl ref https://github.com/golang/go/blob/master/src/cmd/go/internal/vcs/vcs.go +// see https://go-review.googlesource.com/c/go/+/12226/ +// git url define https://git-scm.com/docs/git-clone#_git_urls +func ParseVCSUrl(repo string) (*url.URL, error) { + var ( + repoURL *url.URL + err error + ) + + if m := scpSyntaxRe.FindStringSubmatch(repo); m != nil { + // Match SCP-like syntax and convert it to a URL. + // Eg, "git@github.com:user/repo" becomes + // "ssh://git@github.com/user/repo". + repoURL = &url.URL{ + Scheme: "ssh", + User: url.User(m[1]), + Host: m[2], + Path: m[3], + } + } else { + if !strings.Contains(repo, "//") { + repo = "//" + repo + } + if strings.HasPrefix(repo, "//git@") { + repo = "ssh:" + repo + } else if strings.HasPrefix(repo, "//") { + repo = "https:" + repo + } + repoURL, err = url.Parse(repo) + if err != nil { + return nil, err + } + } + + // Iterate over insecure schemes too, because this function simply + // reports the state of the repo. If we can't see insecure schemes then + // we can't report the actual repo URL. + for _, s := range scheme { + if repoURL.Scheme == s { + return repoURL, nil + } + } + return nil, errors.New("unable to parse repo url") +} diff --git a/cmd/kratos/internal/base/vcs_url_test.go b/cmd/kratos/internal/base/vcs_url_test.go new file mode 100644 index 000000000..09b8d5682 --- /dev/null +++ b/cmd/kratos/internal/base/vcs_url_test.go @@ -0,0 +1,55 @@ +package base + +import ( + "net" + "strings" + "testing" +) + +func TestParseVCSUrl(t *testing.T) { + repos := []string{ + // ssh://[user@]host.xz[:port]/path/to/repo.git/ + "ssh://git@github.com:7875/go-kratos/kratos.git", + // git://host.xz[:port]/path/to/repo.git/ + "git://github.com:7875/go-kratos/kratos.git", + // http[s]://host.xz[:port]/path/to/repo.git/ + "https://github.com:7875/go-kratos/kratos.git", + // ftp[s]://host.xz[:port]/path/to/repo.git/ + "ftps://github.com:7875/go-kratos/kratos.git", + //[user@]host.xz:path/to/repo.git/ + "git@github.com:go-kratos/kratos.git", + // ssh://[user@]host.xz[:port]/~[user]/path/to/repo.git/ + "ssh://git@github.com:7875/go-kratos/kratos.git", + // git://host.xz[:port]/~[user]/path/to/repo.git/ + "git://github.com:7875/go-kratos/kratos.git", + //[user@]host.xz:/~[user]/path/to/repo.git/ + "git@github.com:go-kratos/kratos.git", + ///path/to/repo.git/ + "~/go-kratos/kratos.git", + // file:///path/to/repo.git/ + "file://~/go-kratos/kratos.git", + } + for _, repo := range repos { + url, err := ParseVCSUrl(repo) + if err != nil { + t.Fatal(repo, err) + } + urlPath := strings.TrimLeft(url.Path, "/") + if urlPath != "go-kratos/kratos.git" { + t.Fatal(repo, "parse url failed", urlPath) + } + } +} + +func TestParseSsh(t *testing.T) { + repo := "ssh://git@github.com:7875/go-kratos/kratos.git" + url, err := ParseVCSUrl(repo) + if err != nil { + t.Fatal(err) + } + host, _, err := net.SplitHostPort(url.Host) + if err != nil { + host = url.Host + } + t.Log(host, url.Path) +}