diff --git a/packages/app/src/projects/project-settings-target.test.ts b/packages/app/src/projects/project-settings-target.test.ts index 2962d2b35..610ec94ce 100644 --- a/packages/app/src/projects/project-settings-target.test.ts +++ b/packages/app/src/projects/project-settings-target.test.ts @@ -17,4 +17,22 @@ describe("project settings target", () => { it("keeps a host-local route valid after the structural key changes", () => { expect(findProjectSettingsTarget([project], "host:host-a:project:prj_1234")).toBe(project); }); + + it("keeps legacy project IDs scoped to their host", () => { + const legacyKey = "remote:github.com/acme/app"; + const unchangedProject = { + projectKey: legacyKey, + hosts: [{ serverId: "host-a", projectId: legacyKey }], + }; + const changedProject = { + projectKey: "remote:github.com/acme/app-fork", + hosts: [{ serverId: "host-b", projectId: legacyKey }], + }; + + const routeKey = resolveProjectSettingsRouteKey(changedProject); + expect(routeKey).toBe(`host:host-b:project:${legacyKey}`); + expect(findProjectSettingsTarget([unchangedProject, changedProject], routeKey)).toBe( + changedProject, + ); + }); }); diff --git a/packages/app/src/projects/project-settings-target.ts b/packages/app/src/projects/project-settings-target.ts index 61b62546c..6dc995691 100644 --- a/packages/app/src/projects/project-settings-target.ts +++ b/packages/app/src/projects/project-settings-target.ts @@ -1,5 +1,3 @@ -import { resolveProjectGroupKey } from "./project-group-key"; - interface ProjectSettingsTarget { projectKey: string; hosts: ReadonlyArray<{ serverId: string; projectId?: string }>; @@ -8,7 +6,7 @@ interface ProjectSettingsTarget { function resolveHostLocalProjectKey(host: { serverId: string; projectId?: string }): string | null { const projectId = host.projectId?.trim(); if (!projectId) return null; - return resolveProjectGroupKey({ serverId: host.serverId, projectId }); + return `host:${host.serverId}:project:${projectId}`; } export function resolveProjectSettingsRouteKey(project: ProjectSettingsTarget): string { diff --git a/packages/server/src/server/project-group-key.test.ts b/packages/server/src/server/project-group-key.test.ts index d40ad0102..ae91fe164 100644 --- a/packages/server/src/server/project-group-key.test.ts +++ b/packages/server/src/server/project-group-key.test.ts @@ -57,6 +57,19 @@ describe("deriveProjectGroupKey", () => { ).toBe("remote:github.com/getpaseo/paseo"); }); + test("accepts an SCP-style remote with a bracketed IPv6 host", () => { + const rootPath = path.resolve("repo"); + + expect( + deriveProjectGroupKey({ + rootPath, + remoteUrl: "git@[2001:db8::1]:getpaseo/paseo.git", + worktreeRoot: rootPath, + mainRepoRoot: null, + }), + ).toBe("remote:[2001:db8::1]/getpaseo/paseo"); + }); + test("includes the selected path within a repository", () => { const worktreeRoot = path.resolve("repo"); const rootPath = path.join(worktreeRoot, "packages", "app"); diff --git a/packages/server/src/server/project-group-key.ts b/packages/server/src/server/project-group-key.ts index 25917750c..93144e069 100644 --- a/packages/server/src/server/project-group-key.ts +++ b/packages/server/src/server/project-group-key.ts @@ -39,7 +39,7 @@ function deriveRemoteProjectGroupKey(remoteUrl: string | null): string | null { let remotePath: string | null = null; const scpLike = !trimmed.includes("://") && !/^[A-Za-z]:[\\/]/.test(trimmed) - ? trimmed.match(/^(?:[^@/:]+@)?([^/:]+):(.+)$/) + ? trimmed.match(/^(?:[^@/:]+@)?(\[[^\]]+\]|[^/:]+):(.+)$/) : null; if (scpLike) { host = scpLike[1] ?? null; diff --git a/packages/server/src/utils/path.test.ts b/packages/server/src/utils/path.test.ts index 677122398..030d459e8 100644 --- a/packages/server/src/utils/path.test.ts +++ b/packages/server/src/utils/path.test.ts @@ -48,6 +48,10 @@ describe("path equivalence", () => { ); }); + test("accepts contained path segments beginning with two dots", () => { + expect(getRealpathAwareRelativePath("/repo", "/repo/..tools")).toBe("..tools"); + }); + test.skipIf(process.platform === "win32")( "derives the contained suffix from a realpath-equivalent root", () => { diff --git a/packages/server/src/utils/path.ts b/packages/server/src/utils/path.ts index 8a1a61646..a1f054d03 100644 --- a/packages/server/src/utils/path.ts +++ b/packages/server/src/utils/path.ts @@ -96,7 +96,9 @@ function getRelativePathInsideRoot(root: string, candidate: string): string | nu if ( comparisonRelative !== "" && - (comparisonRelative.startsWith("..") || platformPath.isAbsolute(comparisonRelative)) + (comparisonRelative === ".." || + comparisonRelative.startsWith(`..${platformPath.sep}`) || + platformPath.isAbsolute(comparisonRelative)) ) { return null; }