mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
fix(projects): preserve remote path semantics
This commit is contained in:
@@ -107,8 +107,8 @@ describe("buildWorktreeSetupCalloutPolicy", () => {
|
||||
repoRoot: "/repo/project-1",
|
||||
}),
|
||||
).toEqual({
|
||||
id: "worktree-setup-missing:project-1",
|
||||
dismissalKey: "worktree-setup-missing:project-1",
|
||||
id: "worktree-setup-missing:host:server-1:project:project-1",
|
||||
dismissalKey: "worktree-setup-missing:host:server-1:project:project-1",
|
||||
priority: 100,
|
||||
title: "Set up worktree scripts",
|
||||
description:
|
||||
@@ -130,6 +130,23 @@ describe("buildWorktreeSetupCalloutPolicy", () => {
|
||||
).toBe("/settings/projects/host%3Aserver-1%3Aproject%3Aprj_local");
|
||||
});
|
||||
|
||||
it("keeps dismissals scoped to the host placement", () => {
|
||||
const hostA = buildWorktreeSetupCalloutPolicy({
|
||||
serverId: "host-a",
|
||||
projectId: "project-a",
|
||||
projectKey: "remote:github.com/acme/project",
|
||||
repoRoot: "/host-a/project",
|
||||
});
|
||||
const hostB = buildWorktreeSetupCalloutPolicy({
|
||||
serverId: "host-b",
|
||||
projectId: "project-b",
|
||||
projectKey: "remote:github.com/acme/project",
|
||||
repoRoot: "/host-b/project",
|
||||
});
|
||||
|
||||
expect(hostA.dismissalKey).not.toBe(hostB.dismissalKey);
|
||||
});
|
||||
|
||||
it("scopes retained legacy project IDs to the active host", () => {
|
||||
expect(
|
||||
buildWorktreeSetupCalloutPolicy({
|
||||
|
||||
@@ -68,11 +68,11 @@ export function shouldShowWorktreeSetupCallout(readResult: ReadProjectConfigResu
|
||||
export function buildWorktreeSetupCalloutPolicy(
|
||||
project: ActiveGitWorkspaceProject,
|
||||
): WorktreeSetupCalloutPolicy {
|
||||
const calloutKey = `worktree-setup-missing:${project.projectKey}`;
|
||||
const projectSettingsKey = resolveHostProjectSettingsRouteKey({
|
||||
serverId: project.serverId,
|
||||
projectId: project.projectId,
|
||||
});
|
||||
const calloutKey = `worktree-setup-missing:${projectSettingsKey ?? project.projectKey}`;
|
||||
|
||||
return {
|
||||
id: calloutKey,
|
||||
|
||||
@@ -57,6 +57,32 @@ describe("deriveProjectGroupKey", () => {
|
||||
).toBe("remote:github.com/getpaseo/paseo");
|
||||
});
|
||||
|
||||
test("distinguishes absolute and home-relative SCP paths", () => {
|
||||
const rootPath = path.resolve("repo");
|
||||
const derive = (remoteUrl: string) =>
|
||||
deriveProjectGroupKey({
|
||||
rootPath,
|
||||
remoteUrl,
|
||||
worktreeRoot: rootPath,
|
||||
mainRepoRoot: null,
|
||||
});
|
||||
|
||||
expect(derive("example.com:srv/repo.git")).not.toBe(derive("example.com:/srv/repo.git"));
|
||||
});
|
||||
|
||||
test.each(["git+ssh:", "ssh+git:"])("normalizes SSH alias default ports for %s", (scheme) => {
|
||||
const rootPath = path.resolve("repo");
|
||||
|
||||
expect(
|
||||
deriveProjectGroupKey({
|
||||
rootPath,
|
||||
remoteUrl: `${scheme}//git@github.com:22/getpaseo/paseo.git`,
|
||||
worktreeRoot: rootPath,
|
||||
mainRepoRoot: null,
|
||||
}),
|
||||
).toBe("remote:github.com/getpaseo/paseo");
|
||||
});
|
||||
|
||||
test("accepts an SCP-style remote with a bracketed IPv6 host", () => {
|
||||
const rootPath = path.resolve("repo");
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ function deriveRemoteProjectGroupKey(remoteUrl: string | null): string | null {
|
||||
|
||||
let host: string | null = null;
|
||||
let remotePath: string | null = null;
|
||||
let preserveLeadingSlash = false;
|
||||
const scpLike =
|
||||
!trimmed.includes("://") && !/^[A-Za-z]:[\\/]/.test(trimmed)
|
||||
? trimmed.match(/^(?:[^@/:]+@)?(\[[^\]]+\]|[^/:]+):(.+)$/)
|
||||
@@ -44,6 +45,7 @@ function deriveRemoteProjectGroupKey(remoteUrl: string | null): string | null {
|
||||
if (scpLike) {
|
||||
host = scpLike[1] ?? null;
|
||||
remotePath = scpLike[2] ?? null;
|
||||
preserveLeadingSlash = remotePath?.startsWith("/") ?? false;
|
||||
} else if (trimmed.includes("://")) {
|
||||
try {
|
||||
const parsed = new URL(trimmed);
|
||||
@@ -55,13 +57,15 @@ function deriveRemoteProjectGroupKey(remoteUrl: string | null): string | null {
|
||||
}
|
||||
|
||||
if (!host || !remotePath) return null;
|
||||
const cleanedPath = normalizeRemotePath(remotePath);
|
||||
const cleanedPath = normalizeRemotePath(remotePath, preserveLeadingSlash);
|
||||
if (!cleanedPath) return null;
|
||||
return `remote:${host.toLowerCase()}/${cleanedPath}`;
|
||||
}
|
||||
|
||||
function normalizeRemotePath(remotePath: string): string {
|
||||
const segments = remotePath.trim().replace(/^\/+/, "").replace(/\/+$/, "").split("/");
|
||||
function normalizeRemotePath(remotePath: string, preserveLeadingSlash: boolean): string {
|
||||
const trimmedPath = remotePath.trim().replace(/\/+$/, "");
|
||||
const pathForEncoding = preserveLeadingSlash ? trimmedPath : trimmedPath.replace(/^\/+/, "");
|
||||
const segments = pathForEncoding.split("/");
|
||||
const decodedSegments = segments.map((segment) => {
|
||||
try {
|
||||
return decodeURIComponent(segment);
|
||||
@@ -78,7 +82,9 @@ function normalizeRemotePath(remotePath: string): string {
|
||||
function deriveRemoteHost(remoteUrl: URL): string | null {
|
||||
const defaultPorts: Partial<Record<string, string>> = {
|
||||
"git:": "9418",
|
||||
"git+ssh:": "22",
|
||||
"ssh:": "22",
|
||||
"ssh+git:": "22",
|
||||
};
|
||||
if (remoteUrl.port === defaultPorts[remoteUrl.protocol]) return remoteUrl.hostname || null;
|
||||
return remoteUrl.host || null;
|
||||
|
||||
Reference in New Issue
Block a user