1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-01-10 03:47:03 +02:00
goreleaser/internal/git/config.go
Aaron U'Ren 917cae54f0
fix(config): handle relative git repos (#4575)
## If applied, this commit will...

If applied this change will allow goreleaser to handle relative remotes
when attempting to parse a repo URL from git.

## Why is this change being made? 

To fix the error that I recently came across while trying to test my
goreleaser configuration:

```
% goreleaser check
  • checking                                 path=
  ⨯ configuration is invalid                 error=invalid scm url: .
  ⨯ .goreleaser.yml                                  error=configuration is invalid: invalid scm url: .
  ⨯ command failed                                   error=1 out of 1 configuration file(s) have issues
```

This change happened while on a branch doing some development. As part
of that development I needed to test a change to my goreleaser config.

My git config at the time looked like (repo obfuscated):

```
% cat .git/config
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
[remote "origin"]
        url = git@gitlab.com:some/repo
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
        remote = origin
        merge = refs/heads/main
[branch "release_fixes"]
        remote = .
        merge = refs/heads/main
```

It is fairly common for git to add remotes with a `.` when branch
tracking is enabled.

While, in general, there aren't many use cases that require a user to
need to release from a non-primary branch, there are cases where the
user may want to test their configuration with `goreleaser check` and
the error of `invalid scm url: .` isn't very helpful.

---------

Co-authored-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-01-29 10:31:19 -03:00

98 lines
2.9 KiB
Go

package git
import (
"context"
"errors"
"fmt"
"net/url"
"path"
"strings"
"github.com/caarlos0/log"
"github.com/goreleaser/goreleaser/pkg/config"
)
// ExtractRepoFromConfig gets the repo name from the Git config.
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 := Clean(Run(ctx, "ls-remote", "--get-url"))
if err != nil {
return result, fmt.Errorf("no remote configured to list refs from")
}
// This is a relative remote URL and requires some additional processing
if out == "." {
return extractRelativeRepoFromConfig(ctx)
}
log.WithField("rawurl", out).Debugf("got git url")
return ExtractRepoFromURL(out)
}
func extractRelativeRepoFromConfig(ctx context.Context) (result config.Repo, err error) {
out, err := Clean(Run(ctx, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"))
if err != nil || out == "" {
return result, fmt.Errorf("unable to get upstream while qualifying relative remote")
}
out, err = Clean(Run(ctx, "config", "--get", fmt.Sprintf("branch.%s.remote", out)))
if err != nil || out == "" {
return result, fmt.Errorf("unable to get upstream's remote while qualifying relative remote")
}
out, err = Clean(Run(ctx, "ls-remote", "--get-url", out))
if err != nil {
return result, fmt.Errorf("unable to get upstream while qualifying relative remote")
}
return ExtractRepoFromURL(out)
}
func ExtractRepoFromURL(rawurl string) (config.Repo, error) {
// removes the .git suffix and any new lines
s := strings.TrimSuffix(strings.TrimSpace(rawurl), ".git")
// if the URL contains a :, indicating a SSH config,
// remove all chars until it, including itself
// on HTTP and HTTPS URLs it will remove the http(s): prefix,
// which is ok. On SSH URLs the whole user@server will be removed,
// which is required.
// If the url contains more than 1 ':' character, assume we are doing an
// http URL with a username/password in it, and normalize the URL.
// Gitlab-CI uses this type of URL
if strings.Count(s, ":") == 1 {
s = s[strings.LastIndex(s, ":")+1:]
}
// now we can parse it with net/url
u, err := url.Parse(s)
if err != nil {
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 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{
RawURL: rawurl,
Owner: ss[0],
}, nil
}
repo := config.Repo{
RawURL: rawurl,
Owner: path.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
}