diff --git a/packages/server/src/server/project-group-key.test.ts b/packages/server/src/server/project-group-key.test.ts index a54c64b61..ea955124c 100644 --- a/packages/server/src/server/project-group-key.test.ts +++ b/packages/server/src/server/project-group-key.test.ts @@ -20,15 +20,17 @@ describe("deriveProjectGroupKey", () => { test("normalizes the default SSH port", () => { const rootPath = path.resolve("repo"); - - expect( + const derive = (remoteUrl: string) => deriveProjectGroupKey({ rootPath, - remoteUrl: "ssh://git@github.com:22/getpaseo/paseo.git", + remoteUrl, worktreeRoot: rootPath, mainRepoRoot: null, - }), - ).toBe("remote:github.com/getpaseo/paseo"); + }); + + expect(derive("ssh://git@github.com:22/getpaseo/paseo.git")).toBe( + derive("ssh://git@github.com/getpaseo/paseo.git"), + ); }); test("accepts a root-level remote repository path", () => { @@ -70,6 +72,21 @@ describe("deriveProjectGroupKey", () => { expect(derive("example.com:srv/repo.git")).not.toBe(derive("example.com:/srv/repo.git")); }); + test("distinguishes absolute SSH URL paths from home-relative SCP paths", () => { + const rootPath = path.resolve("repo"); + const derive = (remoteUrl: string) => + deriveProjectGroupKey({ + rootPath, + remoteUrl, + worktreeRoot: rootPath, + mainRepoRoot: null, + }); + + expect(derive("ssh://git@example.com/srv/repo.git")).not.toBe( + derive("git@example.com:srv/repo.git"), + ); + }); + test("distinguishes SSH users for home-relative SCP paths", () => { const rootPath = path.resolve("repo"); const derive = (remoteUrl: string) => @@ -115,15 +132,17 @@ describe("deriveProjectGroupKey", () => { test.each(["git+ssh:", "ssh+git:"])("normalizes SSH alias default ports for %s", (scheme) => { const rootPath = path.resolve("repo"); - - expect( + const derive = (remoteUrl: string) => deriveProjectGroupKey({ rootPath, - remoteUrl: `${scheme}//git@github.com:22/getpaseo/paseo.git`, + remoteUrl, worktreeRoot: rootPath, mainRepoRoot: null, - }), - ).toBe("remote:github.com/getpaseo/paseo"); + }); + + expect(derive(`${scheme}//git@github.com:22/getpaseo/paseo.git`)).toBe( + derive("ssh://git@github.com/getpaseo/paseo.git"), + ); }); test("accepts an SCP-style remote with a bracketed IPv6 host", () => { diff --git a/packages/server/src/server/project-group-key.ts b/packages/server/src/server/project-group-key.ts index ef4b3bfda..f3603f8bb 100644 --- a/packages/server/src/server/project-group-key.ts +++ b/packages/server/src/server/project-group-key.ts @@ -82,13 +82,15 @@ function parseUrlRemote(remoteUrl: string): RemoteLocation | null { try { const parsed = new URL(remoteUrl); const host = deriveRemoteHost(parsed); - const remotePath = parsed.pathname ? parsed.pathname.replace(/^\/+/, "") : null; + const preserveLeadingSlash = ["git+ssh:", "ssh:", "ssh+git:"].includes(parsed.protocol); + let remotePath = parsed.pathname || null; + if (remotePath && !preserveLeadingSlash) remotePath = remotePath.replace(/^\/+/, ""); if (!host || !remotePath) return null; return { host, path: remotePath, relativePathUser: null, - preserveLeadingSlash: false, + preserveLeadingSlash, decodePercentEncoding: true, }; } catch {