Skip to content

Commit 45471f5

Browse files
authored
Merge pull request #801 from githru/bugfix/#798_azure-devops-url-parsing-error
[vscode] Azure DevOps URL 파싱 지원 및 getRepo 함수 개선
2 parents e37f94d + 6a26c99 commit 45471f5

File tree

2 files changed

+144
-9
lines changed

2 files changed

+144
-9
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { getRepo } from "../../utils/git.util";
2+
3+
describe("getRepo", () => {
4+
describe("Valid Git URL formats", () => {
5+
it("HTTPS URL with .git extension should parse correctly", () => {
6+
const url = "https://github.com/owner/repo.git";
7+
const result = getRepo(url);
8+
9+
expect(result).toEqual({
10+
owner: "owner",
11+
repo: "repo",
12+
});
13+
});
14+
15+
it("SSH URL with .git extension should parse correctly", () => {
16+
const url = "[email protected]:owner/repo.git";
17+
const result = getRepo(url);
18+
19+
expect(result).toEqual({
20+
owner: "owner",
21+
repo: "repo",
22+
});
23+
});
24+
25+
it("HTTPS URL without .git extension should parse correctly", () => {
26+
const url = "https://github.com/owner/repo";
27+
const result = getRepo(url);
28+
29+
expect(result).toEqual({
30+
owner: "owner",
31+
repo: "repo",
32+
});
33+
});
34+
35+
it("SSH URL without .git extension should parse correctly", () => {
36+
const url = "[email protected]:owner/repo";
37+
const result = getRepo(url);
38+
39+
expect(result).toEqual({
40+
owner: "owner",
41+
repo: "repo",
42+
});
43+
});
44+
45+
it("HTTPS URL with username should parse correctly", () => {
46+
const url = "https://[email protected]/owner/repo.git";
47+
const result = getRepo(url);
48+
49+
expect(result).toEqual({
50+
owner: "owner",
51+
repo: "repo",
52+
});
53+
});
54+
55+
it("URL with trailing slash should parse correctly", () => {
56+
const url = "https://github.com/owner/repo/";
57+
const result = getRepo(url);
58+
59+
expect(result).toEqual({
60+
owner: "owner",
61+
repo: "repo",
62+
});
63+
});
64+
65+
it("Complex repo name with hyphens should parse correctly", () => {
66+
const url = "https://github.com/githru/githru-vscode-ext.git";
67+
const result = getRepo(url);
68+
69+
expect(result).toEqual({
70+
owner: "githru",
71+
repo: "githru-vscode-ext",
72+
});
73+
});
74+
75+
it("Azure DevOps URL should parse correctly", () => {
76+
const url = "https://[email protected]/organization/organization-sub-name/_git/repository-name";
77+
const result = getRepo(url);
78+
79+
expect(result).toEqual({
80+
owner: "organization",
81+
repo: "repository-name",
82+
});
83+
});
84+
85+
it("Azure DevOps URL without username should parse correctly", () => {
86+
const url = "https://dev.azure.com/organization/organization-sub-name/_git/repository-name";
87+
const result = getRepo(url);
88+
89+
expect(result).toEqual({
90+
owner: "organization",
91+
repo: "repository-name",
92+
});
93+
});
94+
});
95+
96+
describe("Invalid Git URL formats", () => {
97+
it("should throw error for invalid URL format", () => {
98+
const invalidUrl = "invalid-url-format";
99+
100+
expect(() => getRepo(invalidUrl)).toThrow(
101+
'Invalid Git remote config format: "invalid-url-format". Expected format: [https?://|git@]github.com/owner/repo[.git] or https://[email protected]/organization/project/_git/repository-name'
102+
);
103+
});
104+
105+
it("should throw error for non-GitHub URL", () => {
106+
const nonGithubUrl = "https://gitlab.com/owner/repo.git";
107+
108+
expect(() => getRepo(nonGithubUrl)).toThrow(
109+
'Invalid Git remote config format: "https://gitlab.com/owner/repo.git". Expected format: [https?://|git@]github.com/owner/repo[.git] or https://[email protected]/organization/project/_git/repository-name'
110+
);
111+
});
112+
113+
it("should throw error for URL without owner/repo", () => {
114+
const incompleteUrl = "https://github.com/";
115+
116+
expect(() => getRepo(incompleteUrl)).toThrow(
117+
'Invalid Git remote config format: "https://github.com/". Expected format: [https?://|git@]github.com/owner/repo[.git] or https://[email protected]/organization/project/_git/repository-name'
118+
);
119+
});
120+
});
121+
});

packages/vscode/src/utils/git.util.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -288,19 +288,33 @@ export async function getGitConfig(
288288
}
289289

290290
export const getRepo = (gitRemoteConfig: string) => {
291-
const gitRemoteConfigPattern =
292-
/(?:https?|git)(?::\/\/(?:\w+@)?|@)(?:github\.com)(?:\/|:)(?:(?<owner>[^/]+?)\/(?<repo>[^/.]+))(?:\.git|\/)?(\S*)$/m;
293-
const gitRemote = gitRemoteConfig.match(gitRemoteConfigPattern)?.groups;
294-
if (!gitRemote) {
295-
throw new Error("git remote config should be: [https?://|git@]${domain}/${owner}/${repo}.git");
291+
const gitHubPattern =
292+
/(?:https?|git)(?::\/\/(?:\w+@)?|@)(?:github\.com)(?:\/|:)(?:(?<owner>[^/]+?)\/(?<repo>[^/]+?))(?:\.git|\/)?$/m;
293+
const azureDevOpsPattern =
294+
/https:\/\/(?:\w+@)?dev\.azure\.com\/(?<owner>[^/]+?)\/(?<project>[^/]+?)\/_git\/(?<repo>[^/]+?)(?:\/)?$/m;
295+
const patterns = [gitHubPattern, azureDevOpsPattern];
296+
297+
let gitRemote: { owner: string; repo: string } | null = null;
298+
299+
for (const pattern of patterns) {
300+
const match = gitRemoteConfig.match(pattern);
301+
if (!match?.groups) continue;
302+
303+
const { owner, repo } = match.groups;
304+
if (owner && repo) {
305+
const repoWithoutGit = repo.replace(/\.git$/, "");
306+
gitRemote = { owner, repo: repoWithoutGit };
307+
break;
308+
}
296309
}
297310

298-
const { owner, repo } = gitRemote;
299-
if (!owner || !repo) {
300-
throw new Error("no owner/repo");
311+
if (!gitRemote) {
312+
throw new Error(
313+
`Invalid Git remote config format: "${gitRemoteConfig}". Expected format: [https?://|git@]github.com/owner/repo[.git] or https://[email protected]/organization/project/_git/repository-name`
314+
);
301315
}
302316

303-
return { owner, repo };
317+
return gitRemote;
304318
};
305319

306320
export async function getBranches(

0 commit comments

Comments
 (0)