Skip to content

Commit

Permalink
fix: support deep nested subgroups in repo path
Browse files Browse the repository at this point in the history
`glab` initially only suppported repo paths in the format HOST/OWNER/REPO and HOST/GROUP/NAMESPACE/REPO.
Knowing that GitLab supports deep nested subgroups, this adds supports for deep nested
groups.

Resolves profclems#609
  • Loading branch information
profclems committed Feb 13, 2021
1 parent 14cee75 commit dbfdad3
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 20 deletions.
44 changes: 26 additions & 18 deletions internal/glrepo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,12 @@ type Interface interface {
FullName() string
}

// New instantiates a GitLab repository from owner and name arguments
// New instantiates a GitLab repository from owner and repo name arguments
func New(owner, repo string) Interface {
return NewWithHost(owner, repo, glinstance.OverridableDefault())
}

// New instantiates a GitLab repository from group, subgroup and name arguments
// NewWithGroup instantiates a GitLab repository from group, namespace and repo name arguments
func NewWithGroup(group, namespace, repo, hostname string) Interface {
owner := fmt.Sprintf("%s/%s", group, namespace)
if hostname == "" {
Expand Down Expand Up @@ -130,20 +130,24 @@ func FromFullName(nwo string) (Interface, error) {
return FromURL(u)
}

parts := strings.SplitN(nwo, "/", 4)
repo := nwo[strings.LastIndex(nwo, "/")+1:]
nwoWithoutRepo := strings.TrimSuffix(nwo[:strings.LastIndex(nwo, "/")+1], "/")
parts := strings.SplitN(nwoWithoutRepo, "/", 2)

if repo == "" {
return nil, fmt.Errorf(`expected the "[HOST/]OWNER/[NAMESPACE/]REPO" format, got %q`, nwo)
}
for _, p := range parts {
if p == "" {
return nil, fmt.Errorf(`expected the "[HOST/]OWNER/[NAMESPACE/]REPO" format, got %q`, nwo)
}
}
switch len(parts) {
case 4: // HOST/GROUP/NAMESPACE/REPO
return NewWithGroup(parts[1], parts[2], parts[3], parts[0]), nil
case 3: // GROUP/NAMESPACE/REPO or HOST/OWNER/REPO
case 2: // GROUP/NAMESPACE/REPO or HOST/OWNER/REPO or //HOST/GROUP/NAMESPACE/REPO
// First, checks if the first part matches the default instance host (i.e. gitlab.com) or the
// overridden default host (mostly from the GITLAB_HOST env variable)
if parts[0] == glinstance.Default() || parts[0] == glinstance.OverridableDefault() {
return NewWithHost(parts[1], parts[2], normalizeHostname(parts[0])), nil
return NewWithHost(parts[1], repo, normalizeHostname(parts[0])), nil
}
// Dots (.) are allowed in group names by GitLab.
// So we check if if the first part contains a dot.
Expand All @@ -157,7 +161,7 @@ func FromFullName(nwo string) (Interface, error) {
hosts, _ := cfg.Hosts()
for _, host := range hosts {
if host == parts[0] {
rI = NewWithHost(parts[1], parts[2], normalizeHostname(parts[0]))
rI = NewWithHost(parts[1], repo, normalizeHostname(parts[0]))
break
}
}
Expand All @@ -169,9 +173,9 @@ func FromFullName(nwo string) (Interface, error) {
// if the first part is not a valid URL, and does not match an
// authenticated hostname then we assume it is in
// the format GROUP/NAMESPACE/REPO
return NewWithGroup(parts[0], parts[1], parts[2], ""), nil
case 2: // OWNER/REPO
return New(parts[0], parts[1]), nil
return NewWithGroup(parts[0], parts[1], repo, ""), nil
case 1: // OWNER/REPO
return New(parts[0], repo), nil
default:
return nil, fmt.Errorf(`expected the "[HOST/]OWNER/[NAMESPACE/]REPO" format, got %q`, nwo)
}
Expand All @@ -182,13 +186,17 @@ func FromURL(u *url.URL) (Interface, error) {
if u.Hostname() == "" {
return nil, fmt.Errorf("no hostname detected")
}

parts := strings.SplitN(strings.Trim(u.Path, "/"), "/", 4)
if len(parts) == 2 {
return NewWithHost(parts[0], strings.TrimSuffix(parts[1], ".git"), u.Hostname()), nil
}
if len(parts) == 3 {
return NewWithGroup(parts[0], parts[1], strings.TrimSuffix(parts[2], ".git"), u.Hostname()), nil
path := strings.Trim(strings.TrimSuffix(u.Path, ".git"), "/")
repo := path[strings.LastIndex(path, "/")+1:]
pathWithoutRepo := strings.TrimSuffix(path[:strings.LastIndex(path, "/")+1], "/")
if repo != "" && pathWithoutRepo != "" {
parts := strings.SplitN(pathWithoutRepo, "/", 2)
if len(parts) == 1 {
return NewWithHost(parts[0], repo, u.Hostname()), nil
}
if len(parts) == 2 {
return NewWithGroup(parts[0], parts[1], repo, u.Hostname()), nil
}
}
return nil, fmt.Errorf("invalid path: %s", u.Path)
}
Expand Down
32 changes: 30 additions & 2 deletions internal/glrepo/repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,13 @@ func Test_repoFromURL(t *testing.T) {
host: "gitlab.com",
err: nil,
},
{
name: "gitlab.com deep nested",
input: "git://gitlab.com/owner/subgroup/subgroup1/subgroup2/subgroup3/namespace/repo.git",
result: "owner/subgroup/subgroup1/subgroup2/subgroup3/namespace/repo",
host: "gitlab.com",
err: nil,
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -245,8 +252,8 @@ hosts:
},
{
name: "group namespace",
input: "a/b/c/d",
wantHost: "a",
input: "example.org/b/c/d",
wantHost: "example.org",
wantOwner: "b/c",
wantName: "d",
wantFullname: "b/c/d",
Expand All @@ -270,6 +277,11 @@ hosts:
input: "a/",
wantErr: errors.New(`expected the "[HOST/]OWNER/[NAMESPACE/]REPO" format, got "a/"`),
},
{
name: "blank value inner",
input: "a//c",
wantErr: errors.New(`expected the "[HOST/]OWNER/[NAMESPACE/]REPO" format, got "a//c"`),
},
{
name: "with hostname",
input: "example.org/OWNER/REPO",
Expand Down Expand Up @@ -314,6 +326,17 @@ hosts:
wantGroup: "",
wantErr: nil,
},
{
name: "Deep Nested Groups",
input: "[email protected]:GROUP/SUBGROUP1/SUBGROUP2/SUBGROUP3/SUBGROUP4/REPO.git",
wantHost: "example.org",
wantOwner: "GROUP/SUBGROUP1/SUBGROUP2/SUBGROUP3/SUBGROUP4",
wantName: "REPO",
wantFullname: "GROUP/SUBGROUP1/SUBGROUP2/SUBGROUP3/SUBGROUP4/REPO",
wantNamespace: "SUBGROUP1/SUBGROUP2/SUBGROUP3/SUBGROUP4",
wantGroup: "GROUP",
wantErr: nil,
},
{
name: "invalid URL",
input: "[email protected]/%/url",
Expand Down Expand Up @@ -386,6 +409,11 @@ func TestFullNameFromURL(t *testing.T) {
want: "owner/namespace/repo",
wantErr: nil,
},
{
remoteURL: "[email protected]:owner/subgroup/subgroup1/subgroup2/subgroup3/namespace/repo.git",
want: "owner/subgroup/subgroup1/subgroup2/subgroup3/namespace/repo",
wantErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.remoteURL, func(t *testing.T) {
Expand Down

0 comments on commit dbfdad3

Please sign in to comment.