fix(server): preserve selected workspace root (#361)
Fixes #202 ## Summary - keep the default `root` worktree directory pointed at the folder the user opened - continue using the git repo root only for git/worktree discovery - add a targeted regression test for opening a repo subfolder as the workspace ## Why When a workspace is opened from a subfolder inside a git repo, CodeNomad currently maps the `root` worktree to the repo root. That causes proxied OpenCode requests to run with the repo root directory and miss an `opencode.json` that lives in the selected subfolder. ## Validation - inspected the attached `config-issue.zip` from #202 - confirmed `resolveRepoRoot(proj-1)` still returns the git root while `listWorktrees()` now returns `root.directory = proj-1` - `npx tsx --test "packages/server/src/workspaces/__tests__/git-worktrees.test.ts"` - `npm run typecheck --workspace @neuralnomads/codenomad`
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"
|
||||
import { tmpdir } from "node:os"
|
||||
import path from "node:path"
|
||||
import { describe, it } from "node:test"
|
||||
import { listWorktrees } from "../git-worktrees"
|
||||
|
||||
describe("listWorktrees", () => {
|
||||
it("uses the selected workspace folder for the root worktree directory", async () => {
|
||||
const temp = mkdtempSync(path.join(tmpdir(), "codenomad-git-worktrees-"))
|
||||
const binDir = path.join(temp, "bin")
|
||||
const repoRoot = path.join(temp, "repo")
|
||||
const workspaceFolder = path.join(repoRoot, "proj-1")
|
||||
const originalPath = process.env.PATH
|
||||
|
||||
try {
|
||||
mkdirSync(binDir, { recursive: true })
|
||||
mkdirSync(workspaceFolder, { recursive: true })
|
||||
|
||||
const gitPath = path.join(binDir, process.platform === "win32" ? "git.cmd" : "git")
|
||||
const porcelain = [
|
||||
`worktree ${repoRoot}`,
|
||||
"HEAD 1111111",
|
||||
"branch refs/heads/main",
|
||||
"",
|
||||
].join("\\n")
|
||||
|
||||
if (process.platform === "win32") {
|
||||
writeFileSync(gitPath, `@echo off\r\nif "%1"=="worktree" if "%2"=="list" if "%3"=="--porcelain" (\r\necho ${porcelain.replace(/\n/g, "\r\necho ")}\r\nexit /b 0\r\n)\r\nexit /b 1\r\n`)
|
||||
} else {
|
||||
writeFileSync(gitPath, `#!/bin/sh\nif [ "$1" = "worktree" ] && [ "$2" = "list" ] && [ "$3" = "--porcelain" ]; then\nprintf '%s\n' '${porcelain.replace(/'/g, "'\\''")}'\nexit 0\nfi\nexit 1\n`, { mode: 0o755 })
|
||||
}
|
||||
|
||||
process.env.PATH = `${binDir}${path.delimiter}${originalPath ?? ""}`
|
||||
|
||||
const worktrees = await listWorktrees({ repoRoot, workspaceFolder })
|
||||
|
||||
assert.equal(worktrees[0]?.slug, "root")
|
||||
assert.equal(worktrees[0]?.directory, workspaceFolder)
|
||||
assert.equal(worktrees[0]?.kind, "root")
|
||||
assert.notEqual(worktrees[0]?.directory, repoRoot)
|
||||
} finally {
|
||||
process.env.PATH = originalPath
|
||||
rmSync(temp, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -105,7 +105,7 @@ export async function listWorktrees(params: {
|
||||
|
||||
const result = await runGit(["worktree", "list", "--porcelain"], workspaceFolder)
|
||||
if (!result.ok) {
|
||||
const rootDescriptor: WorktreeDescriptor = { slug: "root", directory: repoRoot, kind: "root" }
|
||||
const rootDescriptor: WorktreeDescriptor = { slug: "root", directory: workspaceFolder, kind: "root" }
|
||||
logger?.debug?.({ repoRoot, err: result.error }, "Failed to list git worktrees; returning root only")
|
||||
return [rootDescriptor]
|
||||
}
|
||||
@@ -114,7 +114,7 @@ export async function listWorktrees(params: {
|
||||
const rootRecord = records.find((record) => path.resolve(record.worktree) === path.resolve(repoRoot))
|
||||
const rootDescriptor: WorktreeDescriptor = {
|
||||
slug: "root",
|
||||
directory: repoRoot,
|
||||
directory: workspaceFolder,
|
||||
kind: "root",
|
||||
branch: rootRecord?.branch,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user