diff --git a/internal/git/config.go b/internal/git/config.go index 9b9943b2083..2987ebd5420 100644 --- a/internal/git/config.go +++ b/internal/git/config.go @@ -16,7 +16,7 @@ func ExtractRepoFromConfig(ctx context.Context) (result config.Repo, err error) if !IsRepo(ctx) { return result, errors.New("current folder is not a git repository") } - out, err := Run(ctx, "ls-remote", "--get-url") + out, err := Clean(Run(ctx, "ls-remote", "--get-url")) if err != nil { return result, fmt.Errorf("no remote configured to list refs from") } @@ -44,19 +44,32 @@ func ExtractRepoFromURL(rawurl string) (config.Repo, error) { // now we can parse it with net/url u, err := url.Parse(s) if err != nil { - return config.Repo{}, err + return config.Repo{ + RawURL: rawurl, + }, err } // split the parsed url path by /, the last parts should be the owner and name ss := strings.Split(strings.TrimPrefix(u.Path, "/"), "/") - // if less than 2 parts, its likely not a valid repository + // if empty, returns an error + if len(ss) == 0 || ss[0] == "" { + return config.Repo{ + RawURL: rawurl, + }, fmt.Errorf("unsupported repository URL: %s", rawurl) + } + + // if less than 2 parts, its likely not a valid repository, but we'll allow it. if len(ss) < 2 { - return config.Repo{}, fmt.Errorf("unsupported repository URL: %s", rawurl) + return config.Repo{ + RawURL: rawurl, + Owner: ss[0], + }, nil } repo := config.Repo{ - Owner: strings.Join(ss[:len(ss)-1], "/"), - Name: ss[len(ss)-1], + RawURL: rawurl, + Owner: strings.Join(ss[:len(ss)-1], "/"), + Name: ss[len(ss)-1], } log.WithField("owner", repo.Owner).WithField("name", repo.Name).Debugf("parsed url") return repo, nil diff --git a/internal/git/config_test.go b/internal/git/config_test.go index 1519c6c1394..8de694ac3e1 100644 --- a/internal/git/config_test.go +++ b/internal/git/config_test.go @@ -60,6 +60,8 @@ func TestExtractRepoFromURL(t *testing.T) { repo, err := git.ExtractRepoFromURL(url) require.NoError(t, err) require.Equal(t, "goreleaser/goreleaser", repo.String()) + require.NoError(t, repo.CheckSCM()) + require.Equal(t, url, repo.RawURL) }) } @@ -73,18 +75,35 @@ func TestExtractRepoFromURL(t *testing.T) { repo, err := git.ExtractRepoFromURL(url) require.NoError(t, err) require.Equal(t, "group/nested/goreleaser/goreleaser", repo.String()) + require.NoError(t, repo.CheckSCM()) + require.Equal(t, url, repo.RawURL) }) } - // invalid urls for _, url := range []string{ "git@gist.github.com:someid.git", "https://gist.github.com/someid.git", + } { + t.Run(url, func(t *testing.T) { + repo, err := git.ExtractRepoFromURL(url) + require.NoError(t, err) + require.Equal(t, "someid", repo.String()) + require.Error(t, repo.CheckSCM()) + require.Equal(t, url, repo.RawURL) + }) + } + + // invalid urls + for _, url := range []string{ + "git@gist.github.com:", + "https://gist.github.com/", } { t.Run(url, func(t *testing.T) { repo, err := git.ExtractRepoFromURL(url) require.EqualError(t, err, "unsupported repository URL: "+url) require.Equal(t, "", repo.String()) + require.Error(t, repo.CheckSCM()) + require.Equal(t, url, repo.RawURL) }) } } diff --git a/internal/pipe/changelog/changelog.go b/internal/pipe/changelog/changelog.go index 5eb9af1452c..8c3997a6553 100644 --- a/internal/pipe/changelog/changelog.go +++ b/internal/pipe/changelog/changelog.go @@ -287,6 +287,9 @@ func newGithubChangeloger(ctx *context.Context) (changeloger, error) { if err != nil { return nil, err } + if err := repo.CheckSCM(); err != nil { + return nil, err + } return &githubNativeChangeloger{ client: cli, repo: client.Repo{ @@ -305,6 +308,9 @@ func newSCMChangeloger(ctx *context.Context) (changeloger, error) { if err != nil { return nil, err } + if err := repo.CheckSCM(); err != nil { + return nil, err + } return &scmChangeloger{ client: cli, repo: client.Repo{ diff --git a/internal/pipe/changelog/changelog_test.go b/internal/pipe/changelog/changelog_test.go index 886b9fa23bf..776747ddd9f 100644 --- a/internal/pipe/changelog/changelog_test.go +++ b/internal/pipe/changelog/changelog_test.go @@ -536,6 +536,21 @@ func TestGetChangeloger(t *testing.T) { require.IsType(t, c, &githubNativeChangeloger{}) }) + t.Run(useGitHubNative+"-invalid-repo", func(t *testing.T) { + testlib.Mktmp(t) + testlib.GitInit(t) + testlib.GitRemoteAdd(t, "https://gist.github.com/") + ctx := context.New(config.Project{ + Changelog: config.Changelog{ + Use: useGitHubNative, + }, + }) + ctx.TokenType = context.TokenTypeGitHub + c, err := getChangeloger(ctx) + require.EqualError(t, err, "unsupported repository URL: https://gist.github.com/") + require.Nil(t, c) + }) + t.Run(useGitLab, func(t *testing.T) { ctx := context.New(config.Project{ Changelog: config.Changelog{ @@ -548,6 +563,21 @@ func TestGetChangeloger(t *testing.T) { require.IsType(t, c, &scmChangeloger{}) }) + t.Run(useGitHub+"-invalid-repo", func(t *testing.T) { + testlib.Mktmp(t) + testlib.GitInit(t) + testlib.GitRemoteAdd(t, "https://gist.github.com/") + ctx := context.New(config.Project{ + Changelog: config.Changelog{ + Use: useGitHub, + }, + }) + ctx.TokenType = context.TokenTypeGitHub + c, err := getChangeloger(ctx) + require.EqualError(t, err, "unsupported repository URL: https://gist.github.com/") + require.Nil(t, c) + }) + t.Run("invalid", func(t *testing.T) { c, err := getChangeloger(context.New(config.Project{ Changelog: config.Changelog{ diff --git a/internal/pipe/milestone/milestone.go b/internal/pipe/milestone/milestone.go index 05c05c24a7d..ee8526b3094 100644 --- a/internal/pipe/milestone/milestone.go +++ b/internal/pipe/milestone/milestone.go @@ -29,10 +29,12 @@ func (Pipe) Default(ctx *context.Context) error { if milestone.Repo.Name == "" { repo, err := git.ExtractRepoFromConfig(ctx) - if err != nil && !ctx.Snapshot { return err } + if err := repo.CheckSCM(); err != nil && !ctx.Snapshot { + return err + } milestone.Repo = repo } diff --git a/internal/pipe/milestone/milestone_test.go b/internal/pipe/milestone/milestone_test.go index a15b41a9f80..1255143c34c 100644 --- a/internal/pipe/milestone/milestone_test.go +++ b/internal/pipe/milestone/milestone_test.go @@ -31,6 +31,18 @@ func TestDefaultWithRepoConfig(t *testing.T) { require.Equal(t, "configowner", ctx.Config.Milestones[0].Repo.Owner) } +func TestDefaultWithInvalidRemote(t *testing.T) { + testlib.Mktmp(t) + testlib.GitInit(t) + testlib.GitRemoteAdd(t, "git@github.com:githubowner.git") + + ctx := context.New(config.Project{ + Milestones: []config.Milestone{{}}, + }) + ctx.TokenType = context.TokenTypeGitHub + require.Error(t, Pipe{}.Default(ctx)) +} + func TestDefaultWithRepoRemote(t *testing.T) { testlib.Mktmp(t) testlib.GitInit(t) diff --git a/internal/pipe/release/release.go b/internal/pipe/release/release.go index 8208d33151b..b5a3d9c860a 100644 --- a/internal/pipe/release/release.go +++ b/internal/pipe/release/release.go @@ -12,6 +12,7 @@ import ( "github.com/goreleaser/goreleaser/internal/extrafiles" "github.com/goreleaser/goreleaser/internal/git" "github.com/goreleaser/goreleaser/internal/semerrgroup" + "github.com/goreleaser/goreleaser/pkg/config" "github.com/goreleaser/goreleaser/pkg/context" ) @@ -48,7 +49,7 @@ func (Pipe) Default(ctx *context.Context) error { switch ctx.TokenType { case context.TokenTypeGitLab: if ctx.Config.Release.GitLab.Name == "" { - repo, err := git.ExtractRepoFromConfig(ctx) + repo, err := getRepository(ctx) if err != nil { return err } @@ -63,7 +64,7 @@ func (Pipe) Default(ctx *context.Context) error { ) case context.TokenTypeGitea: if ctx.Config.Release.Gitea.Name == "" { - repo, err := git.ExtractRepoFromConfig(ctx) + repo, err := getRepository(ctx) if err != nil { return err } @@ -79,7 +80,7 @@ func (Pipe) Default(ctx *context.Context) error { default: // We keep github as default for now if ctx.Config.Release.GitHub.Name == "" { - repo, err := git.ExtractRepoFromConfig(ctx) + repo, err := getRepository(ctx) if err != nil && !ctx.Snapshot { return err } @@ -109,6 +110,17 @@ func (Pipe) Default(ctx *context.Context) error { return nil } +func getRepository(ctx *context.Context) (config.Repo, error) { + repo, err := git.ExtractRepoFromConfig(ctx) + if err != nil { + return config.Repo{}, err + } + if err := repo.CheckSCM(); err != nil { + return config.Repo{}, err + } + return repo, nil +} + // Publish the release. func (Pipe) Publish(ctx *context.Context) error { c, err := client.New(ctx) diff --git a/internal/pipe/release/release_test.go b/internal/pipe/release/release_test.go index 6c29e9c8cb8..2414954e925 100644 --- a/internal/pipe/release/release_test.go +++ b/internal/pipe/release/release_test.go @@ -354,6 +354,18 @@ func TestDefault(t *testing.T) { require.Equal(t, "https://github.com/goreleaser/goreleaser/releases/tag/v1.0.0", ctx.ReleaseURL) } +func TestDefaultInvalidURL(t *testing.T) { + testlib.Mktmp(t) + testlib.GitInit(t) + testlib.GitRemoteAdd(t, "git@github.com:goreleaser.git") + + ctx := context.New(config.Project{}) + ctx.TokenType = context.TokenTypeGitHub + ctx.Config.GitHubURLs.Download = "https://github.com" + ctx.Git.CurrentTag = "v1.0.0" + require.Error(t, Pipe{}.Default(ctx)) +} + func TestDefaultWithGitlab(t *testing.T) { testlib.Mktmp(t) testlib.GitInit(t) @@ -369,6 +381,18 @@ func TestDefaultWithGitlab(t *testing.T) { require.Equal(t, "https://gitlab.com/gitlabowner/gitlabrepo/-/releases/v1.0.0", ctx.ReleaseURL) } +func TestDefaultWithGitlabInvalidURL(t *testing.T) { + testlib.Mktmp(t) + testlib.GitInit(t) + testlib.GitRemoteAdd(t, "git@gitlab.com:gitlabrepo.git") + + ctx := context.New(config.Project{}) + ctx.TokenType = context.TokenTypeGitLab + ctx.Config.GitLabURLs.Download = "https://gitlab.com" + ctx.Git.CurrentTag = "v1.0.0" + require.Error(t, Pipe{}.Default(ctx)) +} + func TestDefaultWithGitea(t *testing.T) { testlib.Mktmp(t) testlib.GitInit(t) @@ -384,6 +408,18 @@ func TestDefaultWithGitea(t *testing.T) { require.Equal(t, "https://git.honk.com/giteaowner/gitearepo/releases/tag/v1.0.0", ctx.ReleaseURL) } +func TestDefaultWithGiteaInvalidURL(t *testing.T) { + testlib.Mktmp(t) + testlib.GitInit(t) + testlib.GitRemoteAdd(t, "git@gitea.example.com:gitearepo.git") + + ctx := context.New(config.Project{}) + ctx.TokenType = context.TokenTypeGitea + ctx.Config.GiteaURLs.Download = "https://git.honk.com" + ctx.Git.CurrentTag = "v1.0.0" + require.Error(t, Pipe{}.Default(ctx)) +} + func TestDefaultPreRelease(t *testing.T) { testlib.Mktmp(t) testlib.GitInit(t) diff --git a/pkg/config/config.go b/pkg/config/config.go index f3720dce37e..82b1a3ee87d 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -4,6 +4,7 @@ package config import ( "encoding/json" + "fmt" "io" "os" "strings" @@ -42,16 +43,30 @@ type GiteaURLs struct { // Repo represents any kind of repo (github, gitlab, etc). // to upload releases into. type Repo struct { - Owner string `yaml:"owner,omitempty"` - Name string `yaml:"name,omitempty"` + Owner string `yaml:"owner,omitempty"` + Name string `yaml:"name,omitempty"` + RawURL string `yaml:"-"` } // String of the repo, e.g. owner/name. func (r Repo) String() string { - if r.Owner == "" && r.Name == "" { - return "" + if r.isSCM() { + return r.Owner + "/" + r.Name } - return r.Owner + "/" + r.Name + return r.Owner +} + +// CheckSCM returns an error if the given url is not a valid scm url. +func (r Repo) CheckSCM() error { + if r.isSCM() { + return nil + } + return fmt.Errorf("invalid scm url: %s", r.RawURL) +} + +// isSCM returns true if the repo has both an owner and name. +func (r Repo) isSCM() bool { + return r.Owner != "" && r.Name != "" } // RepoRef represents any kind of repo which may differ