From 3be11ce9a61eee192b406d957d893f72a05dc330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Fabianski?= Date: Fri, 12 Jan 2024 15:32:55 +0100 Subject: [PATCH] feat: allow parsing of Azure DevOps --- vcsurl.go | 58 ++++++++++++++++++++++++++++++++++++++++---------- vcsurl_test.go | 36 +++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 11 deletions(-) diff --git a/vcsurl.go b/vcsurl.go index 7779142..d9f0c11 100644 --- a/vcsurl.go +++ b/vcsurl.go @@ -24,9 +24,11 @@ type Host string // Supported VCS host provider. const ( - GitHub Host = "github.com" - Bitbucket Host = "bitbucket.org" - GitLab Host = "gitlab.com" + GitHub Host = "github.com" + Bitbucket Host = "bitbucket.org" + GitLab Host = "gitlab.com" + AzureLegacy Host = "vs-ssh.visualstudio.com" + Azure Host = "ssh.dev.azure.com" gitHubAPI Host = "api.github.com" ) @@ -49,16 +51,20 @@ const ( ) var knownHosts = map[Host]struct{}{ - GitHub: struct{}{}, - GitLab: struct{}{}, - Bitbucket: struct{}{}, + GitHub: struct{}{}, + GitLab: struct{}{}, + Bitbucket: struct{}{}, + AzureLegacy: struct{}{}, + Azure: struct{}{}, } var kindByHost = map[Host]Kind{ - GitHub: Git, - gitHubAPI: Git, - GitLab: Git, - Bitbucket: Git, + GitHub: Git, + gitHubAPI: Git, + GitLab: Git, + Bitbucket: Git, + AzureLegacy: Git, + Azure: Git, } // VCS describes a VCS repository. @@ -84,7 +90,7 @@ type VCS struct { var ( removeDotGit = regexp.MustCompile(`\.git$`) - gitPreprocessRE = regexp.MustCompile("^git@([a-zA-Z0-9-_\\.]+)\\:(.*)$") + gitPreprocessRE = regexp.MustCompile("^[a-zA-Z0-9-_]+@([a-zA-Z0-9-_\\.]+)\\:(.*)$") ) // Parse parses a string that resembles a VCS repository URL. See TestParse for @@ -123,6 +129,8 @@ func Parse(raw string) (*VCS, error) { err = vcs.parseBitbucket(parsedURL) case GitLab: err = vcs.parseGitlab(parsedURL) + case Azure, AzureLegacy: + err = vcs.parseAzure(parsedURL) default: err = vcs.parseDefault(parsedURL) } @@ -226,6 +234,34 @@ func (v *VCS) parseGitlab(url *url.URL) error { return nil } +func (v *VCS) parseAzure(url *url.URL) error { + parts := strings.Split(url.Path, "/") + if len(parts) < 4 { + return ErrUnknownURL + } + + var last int + for _, p := range parts { + if p == "-" { + break + } + last++ + } + + v.Username = strings.Join(parts[2:last-1], "/") + v.Name = removeDotGit.ReplaceAllLiteralString(parts[last-1], "") + v.FullName = v.Username + "/" + v.Name + + if len(parts) >= (last + 2) { + object := parts[last+1] + if object == "tags" || object == "commit" || object == "tree" { + v.Committish = strings.Join(parts[last+2:], "/") + } + } + + return nil +} + func (v *VCS) parseDefault(url *url.URL) error { path := url.Path if len(path) == 0 { diff --git a/vcsurl_test.go b/vcsurl_test.go index f8eb3b1..81af55b 100644 --- a/vcsurl_test.go +++ b/vcsurl_test.go @@ -152,6 +152,42 @@ func TestParse_GitlabSubGroup(t *testing.T) { } } +func TestParse_AzureLegacy(t *testing.T) { + urls := []string{ + "org@vs-ssh.visualstudio.com:v3/org/Project/RepoName", + } + + for _, url := range urls { + t.Run(url, func(t *testing.T) { + vcs, err := vcsurl.Parse(url) + require.NoError(t, err) + require.Equal(t, vcs.Kind, vcsurl.Git) + require.Equal(t, vcs.Host, vcsurl.AzureLegacy) + require.Equal(t, vcs.Username, "org/Project") + require.Equal(t, vcs.Name, "RepoName") + require.Equal(t, vcs.FullName, "org/Project/RepoName") + }) + } +} + +func TestParse_Azure(t *testing.T) { + urls := []string{ + "git@ssh.dev.azure.com:v3/org/Project/RepoName", + } + + for _, url := range urls { + t.Run(url, func(t *testing.T) { + vcs, err := vcsurl.Parse(url) + require.NoError(t, err) + require.Equal(t, vcs.Kind, vcsurl.Git) + require.Equal(t, vcs.Host, vcsurl.Azure) + require.Equal(t, vcs.Username, "org/Project") + require.Equal(t, vcs.Name, "RepoName") + require.Equal(t, vcs.FullName, "org/Project/RepoName") + }) + } +} + func TestParse_GitlabCommittish(t *testing.T) { urls := []string{ "https://gitlab.com/foo/qux/bar/-/commit/baz",