Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/git.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,36 @@ describe("parseRepoUrl", () => {
});
});

it("should parse gitlab.com HTTPS URL with nested groups", () => {
const result = parseRepoUrl("https://gitlab.com/my-org/my-group/my-repo.git");
expect(result).toEqual({
owner: "my-org",
name: "my-group/my-repo",
provider: "gitlab",
url: "https://gitlab.com/my-org/my-group/my-repo",
});
});

it("should parse gitlab.com HTTPS URL with deeply nested groups", () => {
const result = parseRepoUrl("https://gitlab.com/org/group/subgroup/repo.git");
expect(result).toEqual({
owner: "org",
name: "group/subgroup/repo",
provider: "gitlab",
url: "https://gitlab.com/org/group/subgroup/repo",
});
});

it("should parse self-hosted GitLab HTTPS URL with nested groups and no .git suffix", () => {
const result = parseRepoUrl("https://gitlab.internal.io/team/platform/service");
expect(result).toEqual({
owner: "team",
name: "platform/service",
provider: "gitlab",
url: "https://gitlab.internal.io/team/platform/service",
});
});

it("should parse bitbucket.org HTTPS URL", () => {
const result = parseRepoUrl("https://bitbucket.org/myorg/myrepo.git");
expect(result).toEqual({
Expand Down Expand Up @@ -273,6 +303,26 @@ describe("parseRepoUrl", () => {
});
});

it("should parse gitlab.com SSH URL with nested groups", () => {
const result = parseRepoUrl("git@gitlab.com:my-org/my-group/my-repo.git");
expect(result).toEqual({
owner: "my-org",
name: "my-group/my-repo",
provider: "gitlab",
url: "https://gitlab.com/my-org/my-group/my-repo",
});
});

it("should parse gitlab.com SSH URL with deeply nested groups", () => {
const result = parseRepoUrl("git@gitlab.com:org/group/subgroup/repo.git");
expect(result).toEqual({
owner: "org",
name: "group/subgroup/repo",
provider: "gitlab",
url: "https://gitlab.com/org/group/subgroup/repo",
});
});

it("should parse bitbucket.org SSH URL", () => {
const result = parseRepoUrl("git@bitbucket.org:myorg/myrepo.git");
expect(result).toEqual({
Expand Down
10 changes: 6 additions & 4 deletions src/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,8 +431,9 @@ function hostToProvider(host: string): string | null {
* @returns Parsed repo info, or null if the URL could not be parsed.
*/
export function parseRepoUrl(remoteUrl: string): RepoInfo | null {
// Handle HTTPS URLs: https://github.com/owner/repo.git
const httpsMatch = remoteUrl.match(/^https?:\/\/(?:[^@]+@)?([^/]+)\/([^/]+)\/([^/]+?)(?:\.git)?$/);
// GitLab nested groups: split on the first slash so subgroup paths fold
// into the name segment (e.g. owner=group, name=subgroup/repo).
const httpsMatch = remoteUrl.match(/^https?:\/\/(?:[^@]+@)?([^/]+)\/([^/]+)\/(.+?)(?:\.git)?$/);
if (httpsMatch) {
const host = httpsMatch[1];
const owner = httpsMatch[2] || null;
Expand All @@ -445,8 +446,9 @@ export function parseRepoUrl(remoteUrl: string): RepoInfo | null {
};
}

// Handle SSH URLs: git@github.com:owner/repo.git
const sshMatch = remoteUrl.match(/^git@([^:]+):([^/]+)\/([^/]+?)(?:\.git)?$/);
// Handle SSH URLs: git@github.com:owner/repo.git (GitLab nested groups
// follow the same first-slash split as the HTTPS case above).
const sshMatch = remoteUrl.match(/^git@([^:]+):([^/]+)\/(.+?)(?:\.git)?$/);
if (sshMatch) {
const host = sshMatch[1];
const owner = sshMatch[2] || null;
Expand Down
Loading