From 1191b1be28285bffbfa80fa930e8551d638c7baf Mon Sep 17 00:00:00 2001 From: Benji Visser Date: Wed, 19 Jul 2023 17:05:30 -0600 Subject: [PATCH] [bug] fix incorrect azure project name generation (#78) Signed-off-by: Benji Visser --- internal/config/project_name.go | 202 ++++++++++++++++----------- internal/config/project_name_test.go | 16 ++- 2 files changed, 131 insertions(+), 87 deletions(-) diff --git a/internal/config/project_name.go b/internal/config/project_name.go index ad5705f9..dfbbad32 100644 --- a/internal/config/project_name.go +++ b/internal/config/project_name.go @@ -2,6 +2,8 @@ package config import ( "fmt" + "log" + "net/url" "regexp" "strings" @@ -13,129 +15,159 @@ type Project struct { Repo *git.Repository } -type RepoType int - -const ( - Unknown RepoType = iota - HTTPS - SSH -) +type URLFormatter struct { + URL string +} -func NewProject(repo *git.Repository) *Project { - p := &Project{Repo: repo} - p.Name = p.GetDefaultProjectName() +type GitURL interface { + Parse(url string) error + String() string +} - return p +type Azure struct { + Owner string + Path string } -func (p *Project) GetRemoteURL() string { - // try to get the origin remote - origin, err := p.Repo.Remote("origin") - if err == nil { - return origin.Config().URLs[0] +func (r *Azure) Parse(rawurl string) error { + if strings.HasPrefix(rawurl, "git@") { + rawurl = strings.Replace(rawurl, ":", "/", 1) + rawurl = strings.Replace(rawurl, "git@", "https://", 1) } - // if origin is not found, get the list of remotes - remotes, err := p.Repo.Remotes() + p, err := url.Parse(rawurl) if err != nil { - return "" + return err } - - if len(remotes) == 0 { - return "" - } - - // return the URL of the first remote found - return remotes[0].Config().URLs[0] + p.Path = strings.TrimSuffix(p.Path, ".git") + p.Path = strings.TrimPrefix(p.Path, "/") + p.Path = regexp.MustCompile(`^v\d+\/`).ReplaceAllString(p.Path, "") + p.Path = regexp.MustCompile(`/_git/`).ReplaceAllString(p.Path, "/") + + pathParts := strings.Split(p.Path, "/") + r.Owner = pathParts[0] + r.Path = strings.Join(pathParts[1:], "/") + r.Path = strings.ReplaceAll(r.Path, " ", "%20") + return nil } -func (p *Project) GetDefaultProjectName() string { - url := p.GetRemoteURL() - formatter := URLFormatter{URL: url} - - return formatter.Format() +func (r *Azure) String() string { + return fmt.Sprintf("azure//%s/%s", r.Owner, r.Path) } -type URLFormatter struct { - URL string +type GitHub struct { + Owner string + Path string } -func (f *URLFormatter) getRepoType() RepoType { - httpsPattern := `^https://` - sshPattern := `^git@` - - httpsRe, err := regexp.Compile(httpsPattern) - if err != nil { - return Unknown +func (r *GitHub) Parse(rawurl string) error { + if strings.HasPrefix(rawurl, "git@") { + rawurl = strings.Replace(rawurl, ":", "/", 1) + rawurl = strings.Replace(rawurl, "git@", "https://", 1) } - sshRe, err := regexp.Compile(sshPattern) + p, err := url.Parse(rawurl) if err != nil { - return Unknown + return err } + p.Path = strings.TrimSuffix(p.Path, ".git") + p.Path = strings.TrimPrefix(p.Path, "/") - if httpsRe.MatchString(f.URL) { - return HTTPS - } else if sshRe.MatchString(f.URL) { - return SSH - } + pathParts := strings.SplitN(p.Path, "/", 2) + r.Owner = pathParts[0] + r.Path = pathParts[1] + return nil +} - return Unknown +func (r *GitHub) String() string { + return fmt.Sprintf("github//%s/%s", r.Owner, r.Path) } -func (f *URLFormatter) formatStandardHTTPS() string { - trimmedURL := strings.TrimPrefix(f.URL, "https://") - trimmedURL = strings.TrimSuffix(trimmedURL, ".git") +type GitLab struct { + Owner string + Path string +} - parts := strings.Split(trimmedURL, "/") - if len(parts) > 2 { - return fmt.Sprintf("%s/%s", parts[1], parts[2]) +func (r *GitLab) Parse(rawurl string) error { + if strings.HasPrefix(rawurl, "git@") { + rawurl = strings.Replace(rawurl, ":", "/", 1) + rawurl = strings.Replace(rawurl, "git@", "https://", 1) } - return "" + + p, err := url.Parse(rawurl) + if err != nil { + return err + } + p.Path = strings.TrimSuffix(p.Path, ".git") + p.Path = strings.TrimPrefix(p.Path, "/") + + pathParts := strings.SplitN(p.Path, "/", 2) + r.Owner = pathParts[0] + r.Path = pathParts[1] + return nil } -func (f *URLFormatter) formatStandardSSH() string { - trimmedURL := strings.TrimPrefix(f.URL, "git@") - trimmedURL = strings.TrimSuffix(trimmedURL, ".git") +func (r *GitLab) String() string { + return fmt.Sprintf("gitlab//%s/%s", r.Owner, r.Path) +} - parts := strings.Split(trimmedURL, ":") - if len(parts) < 2 { - return "" +func parseRawGitURL(rawurl string) (GitURL, error) { + var g GitURL + switch { + case strings.Contains(rawurl, "github.com"): + g = &GitHub{} + case strings.Contains(rawurl, "gitlab.com"): + g = &GitLab{} + case strings.Contains(rawurl, "dev.azure.com"): + g = &Azure{} + default: + return nil, fmt.Errorf("unsupported git url: %s", rawurl) } - parts = strings.Split(parts[1], "/") - if len(parts) > 2 { - // azure - return fmt.Sprintf("%s/%s", parts[1], parts[2]) - } - if len(parts) == 2 { - // github+gitlab - return fmt.Sprintf("%s/%s", parts[0], parts[1]) + err := g.Parse(rawurl) + return g, err +} + +func (f *URLFormatter) Format() string { + gURL, err := parseRawGitURL(f.URL) + if err != nil { + log.Fatal(err) } - return "" + return gURL.String() } -func (f *URLFormatter) Format() string { - repoType := f.getRepoType() +func NewProject(repo *git.Repository) *Project { + p := &Project{Repo: repo} + p.Name = p.GetDefaultProjectName() - var projectName string - if repoType == HTTPS { - projectName = f.formatStandardHTTPS() + return p +} + +func (p *Project) GetRemoteURL() string { + // try to get the origin remote + origin, err := p.Repo.Remote("origin") + if err == nil { + return origin.Config().URLs[0] } - if repoType == SSH { - projectName = f.formatStandardSSH() + // if origin is not found, get the list of remotes + remotes, err := p.Repo.Remotes() + if err != nil { + return "" } - switch { - case strings.Contains(f.URL, "github"): - return fmt.Sprintf("github//%s", projectName) - case strings.Contains(f.URL, "gitlab"): - return fmt.Sprintf("gitlab//%s", projectName) - case strings.Contains(f.URL, "azure"): - return fmt.Sprintf("azure//%s", projectName) + if len(remotes) == 0 { + return "" } - return "" + // return the URL of the first remote found + return remotes[0].Config().URLs[0] +} + +func (p *Project) GetDefaultProjectName() string { + url := p.GetRemoteURL() + formatter := URLFormatter{URL: url} + + return formatter.Format() } diff --git a/internal/config/project_name_test.go b/internal/config/project_name_test.go index ea7fe4c2..17ec400c 100644 --- a/internal/config/project_name_test.go +++ b/internal/config/project_name_test.go @@ -13,13 +13,25 @@ func TestFormat(t *testing.T) { url: "https://github.com/xeol-io/xeol.git", expected: "github//xeol-io/xeol", }, + { + url: "git@ssh.dev.azure.com:v3/xeol-io/example-dotnet/v3", + expected: "azure//xeol-io/example-dotnet/v3", + }, { url: "git@ssh.dev.azure.com:v3/xeol-io/example-dotnet/example-dotnet", - expected: "azure//xeol-io/example-dotnet", + expected: "azure//xeol-io/example-dotnet/example-dotnet", }, { url: "https://xeol-io@dev.azure.com/xeol-io/example-dotnet/_git/example-dotnet", - expected: "azure//xeol-io/example-dotnet", + expected: "azure//xeol-io/example-dotnet/example-dotnet", + }, + { + url: "git@ssh.dev.azure.com:v3/xeol-io/Software%20Development/my-dotnet-api", + expected: "azure//xeol-io/Software%20Development/my-dotnet-api", + }, + { + url: "https://dev.azure.com/xeol-io/Software%20Development/_git/my-dotnet-api", + expected: "azure//xeol-io/Software%20Development/my-dotnet-api", }, { url: "git@github.com:noqcks/xeol.git",