fix(server): derive non-GitHub project display names from remote owner/repo (#697)

Reconciliation overwrote correctly-set project display names with the
directory name for non-GitHub remotes (e.g. gitlab.com/acme/app), because
buildWorkspaceGitMetadataFromSnapshot only handled GitHub URLs while the
registry-model layer used the more general deriveProjectGroupingKey path.
Reuse that path here so both layers agree on the owner/repo display name.
This commit is contained in:
Mohamed Boudra
2026-05-04 19:38:58 +08:00
parent 3bf8d483d3
commit 82cf11aaae
2 changed files with 59 additions and 3 deletions

View File

@@ -5,7 +5,11 @@ import path from "node:path";
import { afterEach, beforeEach, describe, expect, test } from "vitest";
import { deriveProjectSlug, parseGitHubRepoNameFromRemote } from "./workspace-git-metadata.js";
import {
buildWorkspaceGitMetadataFromSnapshot,
deriveProjectSlug,
parseGitHubRepoNameFromRemote,
} from "./workspace-git-metadata.js";
function runGit(cwd: string, args: string[]): void {
execFileSync("git", args, {
@@ -166,3 +170,47 @@ describe("deriveProjectSlug", () => {
expect(deriveProjectSlug(cwd)).toBe("untitled");
});
});
describe("buildWorkspaceGitMetadataFromSnapshot", () => {
test("uses owner/repo as the display name for GitHub remotes", () => {
const result = buildWorkspaceGitMetadataFromSnapshot({
cwd: "/repos/some-dir",
directoryName: "some-dir",
isGit: true,
repoRoot: "/repos/some-dir",
mainRepoRoot: null,
currentBranch: "main",
remoteUrl: "git@github.com:acme/widgets.git",
});
expect(result.projectDisplayName).toBe("acme/widgets");
});
test("uses owner/repo as the display name for non-GitHub remotes", () => {
const result = buildWorkspaceGitMetadataFromSnapshot({
cwd: "/repos/random-name",
directoryName: "random-name",
isGit: true,
repoRoot: "/repos/random-name",
mainRepoRoot: null,
currentBranch: "main",
remoteUrl: "git@gitlab.com:acme/app.git",
});
expect(result.projectDisplayName).toBe("acme/app");
});
test("falls back to the directory name when there is no remote", () => {
const result = buildWorkspaceGitMetadataFromSnapshot({
cwd: "/repos/local-only",
directoryName: "local-only",
isGit: true,
repoRoot: "/repos/local-only",
mainRepoRoot: null,
currentBranch: "main",
remoteUrl: null,
});
expect(result.projectDisplayName).toBe("local-only");
});
});

View File

@@ -1,6 +1,7 @@
import { basename } from "path";
import { parseGitHubRemoteUrl } from "../utils/github-remote.js";
import { slugify } from "../utils/worktree.js";
import { deriveProjectGroupingKey, deriveProjectGroupingName } from "./workspace-registry-model.js";
export interface WorkspaceGitMetadata {
projectKind: "git" | "directory";
@@ -57,13 +58,20 @@ export function buildWorkspaceGitMetadataFromSnapshot(input: {
};
}
const githubRepo = input.remoteUrl ? parseGitHubRepoFromRemote(input.remoteUrl) : null;
const isWorktree =
input.mainRepoRoot !== null && input.repoRoot !== null && input.mainRepoRoot !== input.repoRoot;
const projectKey = deriveProjectGroupingKey({
cwd: input.repoRoot ?? input.cwd,
remoteUrl: input.remoteUrl,
mainRepoRoot: input.mainRepoRoot,
});
const projectDisplayName = projectKey.startsWith("remote:")
? deriveProjectGroupingName(projectKey)
: input.directoryName;
return {
projectKind: "git",
projectDisplayName: githubRepo ?? input.directoryName,
projectDisplayName,
workspaceDisplayName: input.currentBranch ?? input.directoryName,
gitRemote: input.remoteUrl,
isWorktree,