mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
fix(projects): keep ambiguous clones separate
A shared remote can identify a project across hosts, but it cannot distinguish two independent clones on one host. Treat those keys as ambiguous so host-local project IDs remain separate.
This commit is contained in:
@@ -59,9 +59,65 @@ interface WorkspaceStructureSession {
|
||||
emptyProjects?: Iterable<EmptyProjectDescriptor>;
|
||||
}
|
||||
|
||||
interface MaterializedWorkspaceStructureSession {
|
||||
serverId: string;
|
||||
workspaces: WorkspaceDescriptor[];
|
||||
emptyProjects: EmptyProjectDescriptor[];
|
||||
}
|
||||
|
||||
function findAmbiguousProjectGroupKeys(
|
||||
sessions: MaterializedWorkspaceStructureSession[],
|
||||
): Set<string> {
|
||||
const projectIdsByHostByGroupKey = new Map<string, Map<string, Set<string>>>();
|
||||
for (const session of sessions) {
|
||||
const projects = [
|
||||
...session.emptyProjects.map((project) => ({
|
||||
projectId: project.projectId,
|
||||
projectGroupKey: project.projectGroupKey,
|
||||
})),
|
||||
...session.workspaces.map((workspace) => ({
|
||||
projectId: workspace.projectId,
|
||||
projectGroupKey: workspace.projectGroupKey,
|
||||
})),
|
||||
];
|
||||
for (const project of projects) {
|
||||
const groupKey = resolveProjectGroupKey({ serverId: session.serverId, ...project });
|
||||
const byHost = projectIdsByHostByGroupKey.get(groupKey) ?? new Map();
|
||||
const projectIds = byHost.get(session.serverId) ?? new Set();
|
||||
projectIds.add(project.projectId);
|
||||
byHost.set(session.serverId, projectIds);
|
||||
projectIdsByHostByGroupKey.set(groupKey, byHost);
|
||||
}
|
||||
}
|
||||
|
||||
return new Set(
|
||||
[...projectIdsByHostByGroupKey].flatMap(([groupKey, byHost]) =>
|
||||
[...byHost.values()].some((projectIds) => projectIds.size > 1) ? [groupKey] : [],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function resolveUnambiguousProjectGroupKey(input: {
|
||||
serverId: string;
|
||||
projectId: string;
|
||||
projectGroupKey?: string | null;
|
||||
ambiguousGroupKeys: ReadonlySet<string>;
|
||||
}): string {
|
||||
const groupKey = resolveProjectGroupKey(input);
|
||||
return input.ambiguousGroupKeys.has(groupKey)
|
||||
? `host:${input.serverId}:project:${input.projectId}`
|
||||
: groupKey;
|
||||
}
|
||||
|
||||
export function buildWorkspaceStructureProjects(input: {
|
||||
sessions: WorkspaceStructureSession[];
|
||||
}): WorkspaceStructureProject[] {
|
||||
const sessions = input.sessions.map((session) => ({
|
||||
serverId: session.serverId,
|
||||
workspaces: [...session.workspaces],
|
||||
emptyProjects: [...(session.emptyProjects ?? [])],
|
||||
}));
|
||||
const ambiguousGroupKeys = findAmbiguousProjectGroupKeys(sessions);
|
||||
const byProject = new Map<
|
||||
string,
|
||||
{
|
||||
@@ -74,12 +130,13 @@ export function buildWorkspaceStructureProjects(input: {
|
||||
}
|
||||
>();
|
||||
|
||||
for (const session of input.sessions) {
|
||||
for (const emptyProject of session.emptyProjects ?? []) {
|
||||
const projectKey = resolveProjectGroupKey({
|
||||
for (const session of sessions) {
|
||||
for (const emptyProject of session.emptyProjects) {
|
||||
const projectKey = resolveUnambiguousProjectGroupKey({
|
||||
serverId: session.serverId,
|
||||
projectId: emptyProject.projectId,
|
||||
projectGroupKey: emptyProject.projectGroupKey,
|
||||
ambiguousGroupKeys,
|
||||
});
|
||||
const placement = {
|
||||
serverId: session.serverId,
|
||||
@@ -108,10 +165,11 @@ export function buildWorkspaceStructureProjects(input: {
|
||||
}
|
||||
|
||||
for (const workspace of session.workspaces) {
|
||||
const projectKey = resolveProjectGroupKey({
|
||||
const projectKey = resolveUnambiguousProjectGroupKey({
|
||||
serverId: session.serverId,
|
||||
projectId: workspace.projectId,
|
||||
projectGroupKey: workspace.projectGroupKey,
|
||||
ambiguousGroupKeys,
|
||||
});
|
||||
const existing = byProject.get(projectKey);
|
||||
|
||||
|
||||
@@ -96,6 +96,29 @@ describe("buildProjects", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps independent same-host clones separate when their group key is ambiguous", () => {
|
||||
const projectGroupKey = "remote:github.com/acme/app";
|
||||
const result = buildProjects({
|
||||
hosts: [
|
||||
{
|
||||
serverId: "desktop",
|
||||
serverName: "Desktop",
|
||||
isOnline: true,
|
||||
workspaces: [
|
||||
workspace({ id: "clone-a", repoRoot: "/a", projectId: "prj_a", projectGroupKey }),
|
||||
workspace({ id: "clone-b", repoRoot: "/b", projectId: "prj_b", projectGroupKey }),
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.projects).toHaveLength(2);
|
||||
expect(result.projects.map((project) => project.hosts[0]?.projectId).sort()).toEqual([
|
||||
"prj_a",
|
||||
"prj_b",
|
||||
]);
|
||||
});
|
||||
|
||||
it("normalizes detached and blank branches out of workspace summaries", () => {
|
||||
const result = buildProjects({
|
||||
hosts: [
|
||||
|
||||
@@ -185,6 +185,13 @@ export function buildProjects(input: BuildProjectsInput): BuildProjectsResult {
|
||||
}
|
||||
|
||||
const hostProjects = buildHostProjectEntries(host);
|
||||
const projectKeyByProjectId = new Map(
|
||||
hostProjects.flatMap((project) =>
|
||||
project.hosts
|
||||
.filter((placement) => placement.serverId === host.serverId && placement.projectId)
|
||||
.map((placement) => [placement.projectId!, project.projectKey] as const),
|
||||
),
|
||||
);
|
||||
for (const hostProject of hostProjects) {
|
||||
const customName = findProjectCustomName(host.workspaces, hostProject.projectKey);
|
||||
let group = groups.get(hostProject.projectKey);
|
||||
@@ -216,11 +223,13 @@ export function buildProjects(input: BuildProjectsInput): BuildProjectsResult {
|
||||
}
|
||||
|
||||
for (const workspace of host.workspaces) {
|
||||
const key = resolveProjectGroupKey({
|
||||
serverId: host.serverId,
|
||||
projectId: workspace.projectId,
|
||||
projectGroupKey: workspace.projectGroupKey,
|
||||
});
|
||||
const key =
|
||||
projectKeyByProjectId.get(workspace.projectId) ??
|
||||
resolveProjectGroupKey({
|
||||
serverId: host.serverId,
|
||||
projectId: workspace.projectId,
|
||||
projectGroupKey: workspace.projectGroupKey,
|
||||
});
|
||||
const group = groups.get(key);
|
||||
const hostGroup = group?.hostsByServerId.get(host.serverId);
|
||||
if (!hostGroup) continue;
|
||||
|
||||
Reference in New Issue
Block a user