mirror of https://github.com/garrytan/gstack.git
fix: normalize ssh URL remotes in global discovery
Handle URL-style SSH git remotes such as ssh://git@github.com/owner/repo.git in the same canonicalization path as scp-style SSH remotes. This prevents global discovery consumers from reporting duplicate repos when different tools or clones use equivalent SSH remote formats. Add regression coverage for GitHub and GitLab URL-style SSH remotes and for deduping SSH URL, scp-style SSH, and HTTPS forms together. Fixes #975
This commit is contained in:
parent
c6e6a21d1a
commit
35e4aa53e9
|
|
@ -124,8 +124,18 @@ function windowToDate(window: string): Date {
|
||||||
export function normalizeRemoteUrl(url: string): string {
|
export function normalizeRemoteUrl(url: string): string {
|
||||||
let normalized = url.trim();
|
let normalized = url.trim();
|
||||||
|
|
||||||
// SSH → HTTPS: git@github.com:user/repo → https://github.com/user/repo
|
// SSH URL → HTTPS: ssh://git@github.com/user/repo → https://github.com/user/repo
|
||||||
const sshMatch = normalized.match(/^(?:ssh:\/\/)?git@([^:]+):(.+)$/);
|
try {
|
||||||
|
const parsed = new URL(normalized);
|
||||||
|
if (parsed.protocol === "ssh:" && parsed.username === "git" && parsed.pathname.length > 1) {
|
||||||
|
normalized = `https://${parsed.host}${parsed.pathname}`;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Not a URL-style remote; try scp-style below.
|
||||||
|
}
|
||||||
|
|
||||||
|
// SCP-style SSH → HTTPS: git@github.com:user/repo → https://github.com/user/repo
|
||||||
|
const sshMatch = normalized.match(/^git@([^:]+):(.+)$/);
|
||||||
if (sshMatch) {
|
if (sshMatch) {
|
||||||
normalized = `https://${sshMatch[1]}/${sshMatch[2]}`;
|
normalized = `https://${sshMatch[1]}/${sshMatch[2]}`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,12 @@ describe("gstack-global-discover", () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("converts URL-style SSH to HTTPS", () => {
|
||||||
|
expect(normalizeRemoteUrl("ssh://git@github.com/user/repo.git")).toBe(
|
||||||
|
"https://github.com/user/repo"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("converts SSH without .git to HTTPS", () => {
|
test("converts SSH without .git to HTTPS", () => {
|
||||||
expect(normalizeRemoteUrl("git@github.com:user/repo")).toBe(
|
expect(normalizeRemoteUrl("git@github.com:user/repo")).toBe(
|
||||||
"https://github.com/user/repo"
|
"https://github.com/user/repo"
|
||||||
|
|
@ -44,9 +50,11 @@ describe("gstack-global-discover", () => {
|
||||||
|
|
||||||
test("SSH and HTTPS for same repo normalize to same URL", () => {
|
test("SSH and HTTPS for same repo normalize to same URL", () => {
|
||||||
const ssh = normalizeRemoteUrl("git@github.com:garrytan/gstack.git");
|
const ssh = normalizeRemoteUrl("git@github.com:garrytan/gstack.git");
|
||||||
|
const sshUrl = normalizeRemoteUrl("ssh://git@github.com/garrytan/gstack.git");
|
||||||
const https = normalizeRemoteUrl("https://github.com/garrytan/gstack.git");
|
const https = normalizeRemoteUrl("https://github.com/garrytan/gstack.git");
|
||||||
const httpsNoDotGit = normalizeRemoteUrl("https://github.com/garrytan/gstack");
|
const httpsNoDotGit = normalizeRemoteUrl("https://github.com/garrytan/gstack");
|
||||||
expect(ssh).toBe(https);
|
expect(ssh).toBe(https);
|
||||||
|
expect(sshUrl).toBe(https);
|
||||||
expect(https).toBe(httpsNoDotGit);
|
expect(https).toBe(httpsNoDotGit);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -61,6 +69,12 @@ describe("gstack-global-discover", () => {
|
||||||
"https://gitlab.com/org/project"
|
"https://gitlab.com/org/project"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("handles GitLab URL-style SSH URLs", () => {
|
||||||
|
expect(normalizeRemoteUrl("ssh://git@gitlab.com/org/project.git")).toBe(
|
||||||
|
"https://gitlab.com/org/project"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("CLI", () => {
|
describe("CLI", () => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue