@neuralnomads/codenomad-dev 0.20.0-dev-20260921-63bbdbb1 → 0.20.0-dev-20260922-e68ac524
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/automation/developer-inspection-targets.js +25 -0
- package/dist/automation/developer-inspection-targets.js.map +1 -0
- package/dist/developer-cdp.js +5 -4
- package/dist/developer-cdp.js.map +1 -1
- package/dist/developer-cdp.test.js +18 -0
- package/dist/developer-cdp.test.js.map +1 -1
- package/dist/opencode/session-pruning/navigation-contract.js +1 -0
- package/dist/opencode/session-pruning/navigation-contract.js.map +1 -1
- package/dist/opencode/session-pruning/navigation.test.js +12 -1
- package/dist/opencode/session-pruning/navigation.test.js.map +1 -1
- package/dist/opencode/session-pruning/outline-index.js +14 -1
- package/dist/opencode/session-pruning/outline-index.js.map +1 -1
- package/dist/plugins/session-pruning/plugin.mjs +16 -3
- package/dist/server/__tests__/instance-proxy.test.js +32 -0
- package/dist/server/__tests__/instance-proxy.test.js.map +1 -1
- package/dist/server/http-server.js +1 -1
- package/dist/server/http-server.js.map +1 -1
- package/dist/server/routes/automation-plugin.js +28 -15
- package/dist/server/routes/automation-plugin.js.map +1 -1
- package/dist/server/routes/automation-plugin.test.js +10 -6
- package/dist/server/routes/automation-plugin.test.js.map +1 -1
- package/dist/workspaces/__tests__/git-common-directory.test.js +86 -0
- package/dist/workspaces/__tests__/git-common-directory.test.js.map +1 -0
- package/dist/workspaces/__tests__/git-worktree-config.test.js +130 -0
- package/dist/workspaces/__tests__/git-worktree-config.test.js.map +1 -0
- package/dist/workspaces/__tests__/git-worktrees.test.js +49 -1
- package/dist/workspaces/__tests__/git-worktrees.test.js.map +1 -1
- package/dist/workspaces/__tests__/native-worktree-fixture.js +2 -2
- package/dist/workspaces/__tests__/native-worktree-fixture.js.map +1 -1
- package/dist/workspaces/git-common-directory.js +18 -0
- package/dist/workspaces/git-common-directory.js.map +1 -0
- package/dist/workspaces/git-process.js +80 -0
- package/dist/workspaces/git-process.js.map +1 -0
- package/dist/workspaces/git-worktrees.js +64 -10
- package/dist/workspaces/git-worktrees.js.map +1 -1
- package/dist/workspaces/native-worktrees.js +24 -9
- package/dist/workspaces/native-worktrees.js.map +1 -1
- package/package.json +1 -1
- package/public/assets/App-BSkmbNUC.js +51 -0
- package/public/assets/App-BSkmbNUC.js.map +1 -0
- package/public/assets/FilesTab-CznSFRW_.js +3 -0
- package/public/assets/{FilesTab-DOOT4Own.js.map → FilesTab-CznSFRW_.js.map} +1 -1
- package/public/assets/{GitChangesTab-DqS17ZG_.js → GitChangesTab-DLtyzpX0.js} +3 -3
- package/public/assets/{GitChangesTab-DqS17ZG_.js.map → GitChangesTab-DLtyzpX0.js.map} +1 -1
- package/public/assets/{SplitFilePanel-DpXPYY4L.js → SplitFilePanel-BHkyIER4.js} +2 -2
- package/public/assets/{SplitFilePanel-DpXPYY4L.js.map → SplitFilePanel-BHkyIER4.js.map} +1 -1
- package/public/assets/{StatusTab-CynqW2pr.js → StatusTab-BB3L4UEF.js} +2 -2
- package/public/assets/{StatusTab-CynqW2pr.js.map → StatusTab-BB3L4UEF.js.map} +1 -1
- package/public/assets/{align-justify-C_MrQdWv.js → align-justify-BGcr8Y3G.js} +2 -2
- package/public/assets/{align-justify-C_MrQdWv.js.map → align-justify-BGcr8Y3G.js.map} +1 -1
- package/public/assets/{bundle-full-CVPoVh_h.js → bundle-full-CwHbX2Tz.js} +2 -2
- package/public/assets/{bundle-full-CVPoVh_h.js.map → bundle-full-CwHbX2Tz.js.map} +1 -1
- package/public/assets/client-state-CjVIIkWh.js +2 -0
- package/public/assets/client-state-CjVIIkWh.js.map +1 -0
- package/public/assets/{diff-viewer-BzgxNlWD.js → diff-viewer-FNBCogHl.js} +2 -2
- package/public/assets/{diff-viewer-BzgxNlWD.js.map → diff-viewer-FNBCogHl.js.map} +1 -1
- package/public/assets/index-AeE49W2c.js +2 -0
- package/public/assets/index-AeE49W2c.js.map +1 -0
- package/public/assets/index-B875QQLi.js +2 -0
- package/public/assets/index-B875QQLi.js.map +1 -0
- package/public/assets/index-BhInW6hR.js +3 -0
- package/public/assets/index-BhInW6hR.js.map +1 -0
- package/public/assets/index-C2jBtFkI.js +2 -0
- package/public/assets/{index-CrjSY1y2.js.map → index-C2jBtFkI.js.map} +1 -1
- package/public/assets/index-C8zCyB6Z.js +2 -0
- package/public/assets/index-C8zCyB6Z.js.map +1 -0
- package/public/assets/index-C9vJvaFa.js +2 -0
- package/public/assets/index-C9vJvaFa.js.map +1 -0
- package/public/assets/index-CLdQSSfU.js +2 -0
- package/public/assets/index-CLdQSSfU.js.map +1 -0
- package/public/assets/{index-Ce-SrKvP.js → index-CLgpBTF6.js} +2 -2
- package/public/assets/{index-Ce-SrKvP.js.map → index-CLgpBTF6.js.map} +1 -1
- package/public/assets/index-CaYFZAN4.js +2 -0
- package/public/assets/index-CaYFZAN4.js.map +1 -0
- package/public/assets/index-ChudAs0f.js +2 -0
- package/public/assets/index-ChudAs0f.js.map +1 -0
- package/public/assets/index-D292-iMl.js +2 -0
- package/public/assets/index-D292-iMl.js.map +1 -0
- package/public/assets/index-DYBqUC9X.css +1 -0
- package/public/assets/index-Dk3HFhZs.js +2 -0
- package/public/assets/index-Dk3HFhZs.js.map +1 -0
- package/public/assets/{loading-BWjkEyUD.js → loading-D8BcUQaM.js} +2 -2
- package/public/assets/{loading-BWjkEyUD.js.map → loading-D8BcUQaM.js.map} +1 -1
- package/public/assets/main-DRbjgAo8.js +42 -0
- package/public/assets/main-DRbjgAo8.js.map +1 -0
- package/public/assets/{markdown-Bkm0F3oO.js → markdown-CRPEbAqL.js} +4 -4
- package/public/assets/{markdown-Bkm0F3oO.js.map → markdown-CRPEbAqL.js.map} +1 -1
- package/public/assets/{monaco-viewer-C10fFymA.js → monaco-viewer-CG0aPZ-r.js} +2 -2
- package/public/assets/{monaco-viewer-C10fFymA.js.map → monaco-viewer-CG0aPZ-r.js.map} +1 -1
- package/public/assets/{startup-state-settings-card-BTfYhHov.js → startup-state-settings-card-CHqS2Pmg.js} +2 -2
- package/public/assets/{startup-state-settings-card-BTfYhHov.js.map → startup-state-settings-card-CHqS2Pmg.js.map} +1 -1
- package/public/assets/{terminal-square-Dhdgf3hj.js → terminal-square-DhmNJqNG.js} +2 -2
- package/public/assets/{terminal-square-Dhdgf3hj.js.map → terminal-square-DhmNJqNG.js.map} +1 -1
- package/public/assets/{todo-pfT9OVP8.js → todo-DXJ5kP_Z.js} +2 -2
- package/public/assets/{todo-pfT9OVP8.js.map → todo-DXJ5kP_Z.js.map} +1 -1
- package/public/assets/{tool-call-ja_vg-Y6.js → tool-call-Bjo9DalZ.js} +5 -5
- package/public/assets/{tool-call-ja_vg-Y6.js.map → tool-call-Bjo9DalZ.js.map} +1 -1
- package/public/assets/{unified-picker-Bi-fpJBA.js → unified-picker-CxrTM_tY.js} +2 -2
- package/public/assets/{unified-picker-Bi-fpJBA.js.map → unified-picker-CxrTM_tY.js.map} +1 -1
- package/public/assets/{wrap-text-DJHeAvj4.js → wrap-text-BznZeD4J.js} +2 -2
- package/public/assets/{wrap-text-DJHeAvj4.js.map → wrap-text-BznZeD4J.js.map} +1 -1
- package/public/index.html +4 -4
- package/public/loading.html +4 -4
- package/public/sw.js +1 -1
- package/public/sw.js.map +1 -1
- package/public/assets/App-bGIUQ-dD.js +0 -49
- package/public/assets/App-bGIUQ-dD.js.map +0 -1
- package/public/assets/FilesTab-DOOT4Own.js +0 -3
- package/public/assets/client-state-DHHjx28k.js +0 -2
- package/public/assets/client-state-DHHjx28k.js.map +0 -1
- package/public/assets/index-B5KIA1QZ.js +0 -2
- package/public/assets/index-B5KIA1QZ.js.map +0 -1
- package/public/assets/index-BO3LYyge.js +0 -2
- package/public/assets/index-BO3LYyge.js.map +0 -1
- package/public/assets/index-Bzngra1L.js +0 -3
- package/public/assets/index-Bzngra1L.js.map +0 -1
- package/public/assets/index-C6-rb6B7.css +0 -1
- package/public/assets/index-CI6ZE9xs.js +0 -2
- package/public/assets/index-CI6ZE9xs.js.map +0 -1
- package/public/assets/index-CSUBS-o9.js +0 -2
- package/public/assets/index-CSUBS-o9.js.map +0 -1
- package/public/assets/index-Cl4PCYg4.js +0 -2
- package/public/assets/index-Cl4PCYg4.js.map +0 -1
- package/public/assets/index-CrjSY1y2.js +0 -2
- package/public/assets/index-D7SuJgSm.js +0 -2
- package/public/assets/index-D7SuJgSm.js.map +0 -1
- package/public/assets/index-Daf72P6o.js +0 -2
- package/public/assets/index-Daf72P6o.js.map +0 -1
- package/public/assets/index-WIaMCSWq.js +0 -2
- package/public/assets/index-WIaMCSWq.js.map +0 -1
- package/public/assets/index-WsSXkxQ_.js +0 -2
- package/public/assets/index-WsSXkxQ_.js.map +0 -1
- package/public/assets/main-Hn7UJb2f.js +0 -42
- package/public/assets/main-Hn7UJb2f.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git-worktrees.test.js","sourceRoot":"","sources":["../../../src/workspaces/__tests__/git-worktrees.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAA;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"git-worktrees.test.js","sourceRoot":"","sources":["../../../src/workspaces/__tests__/git-worktrees.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\"\nimport { execFileSync } from \"node:child_process\"\nimport { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from \"node:fs\"\nimport { tmpdir } from \"node:os\"\nimport path from \"node:path\"\nimport { describe, it } from \"node:test\"\nimport { fixtureCatalogue } from \"./native-worktree-fixture\"\n\ndescribe(\"listWorktrees\", () => {\n it(\"uses the selected workspace folder for the root worktree directory\", async () => {\n const temp = mkdtempSync(path.join(tmpdir(), \"codenomad-git-worktrees-\"))\n const repoRoot = path.join(temp, \"repo\")\n const workspaceFolder = path.join(repoRoot, \"proj-1\")\n\n try {\n execFileSync(\"git\", [\"init\", \"--initial-branch=main\", repoRoot])\n mkdirSync(workspaceFolder, { recursive: true })\n writeFileSync(path.join(repoRoot, \"README.md\"), \"test\\n\")\n execFileSync(\"git\", [\"-C\", repoRoot, \"add\", \"README.md\"])\n execFileSync(\"git\", [\"-C\", repoRoot, \"-c\", \"user.name=CodeNomad Test\", \"-c\", \"user.email=test@codenomad.local\", \"commit\", \"-m\", \"test\"])\n\n const { worktrees } = await fixtureCatalogue(workspaceFolder)\n\n assert.equal(worktrees[0]?.slug, \"root\")\n assert.equal(worktrees[0]?.directory, workspaceFolder)\n assert.equal(worktrees[0]?.kind, \"root\")\n assert.equal(worktrees[0]?.branch, \"main\")\n assert.notEqual(worktrees[0]?.directory, repoRoot)\n } finally {\n rmSync(temp, { recursive: true, force: true })\n }\n })\n\n it(\"batches named and detached annotations while retaining native membership and physical repository ownership\", async () => {\n const temp = mkdtempSync(path.join(tmpdir(), \"codenomad-worktree-annotations-\"))\n const repo = path.join(temp, \"main repo\")\n const named = path.join(temp, \"named checkout\")\n const detached = path.join(temp, \"detached checkout\")\n const gitOnly = path.join(temp, \"not in native inventory\")\n const foreign = path.join(temp, \"independent clone\")\n const git = (...args: string[]) => execFileSync(\"git\", args, { encoding: \"utf8\" }).trim()\n try {\n git(\"init\", \"--initial-branch=main\", repo)\n git(\"-C\", repo, \"-c\", \"user.name=Test\", \"-c\", \"user.email=test@example.com\", \"commit\", \"--allow-empty\", \"-m\", \"initial\")\n const head = git(\"-C\", repo, \"rev-parse\", \"HEAD\")\n git(\"-C\", repo, \"worktree\", \"add\", \"-b\", \"feature/named\", named)\n git(\"-C\", repo, \"worktree\", \"add\", \"--detach\", detached)\n git(\"-C\", repo, \"worktree\", \"add\", \"--detach\", gitOnly)\n git(\"clone\", \"--local\", repo, foreign)\n const directories = [repo, named, detached, foreign]\n const catalogue = await fixtureCatalogue(repo, directories)\n assert.equal(catalogue.worktrees.length, 3)\n const entry = catalogue.worktrees.find(item => item.directory === realpathSync(named))!\n assert.equal(entry.branch, \"feature/named\")\n assert.equal(entry.head, head)\n assert.equal(entry.removable, true)\n const detachedEntry = catalogue.worktrees.find(item => item.directory === realpathSync(detached))!\n assert.equal(detachedEntry.branch, undefined)\n assert.equal(detachedEntry.head, head)\n assert.match(detachedEntry.label!, /detached checkout @ /)\n assert.equal(catalogue.worktrees[0].removable, false)\n\n const pointer = readFileSync(path.join(named, \".git\"), \"utf8\")\n try {\n // A registered path can be replaced. Its current Git identity must still\n // belong to this repository; a stale registration alone is insufficient.\n rmSync(path.join(named, \".git\"))\n writeFileSync(path.join(named, \".git\"), `gitdir: ${path.join(foreign, \".git\")}\\n`)\n const replaced = await fixtureCatalogue(repo, directories)\n assert.equal(replaced.worktrees.some(item => item.directory === realpathSync(named)), false)\n writeFileSync(path.join(named, \".git\"), readFileSync(path.join(detached, \".git\")))\n await assert.rejects(fixtureCatalogue(repo, directories), /not a checkout root/)\n } finally {\n rmSync(path.join(named, \".git\"), { force: true })\n writeFileSync(path.join(named, \".git\"), pointer)\n }\n } finally { rmSync(temp, { recursive: true, force: true }) }\n })\n})\n"]}
|
|
@@ -3,7 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { listNativeWorktrees } from "../native-worktrees";
|
|
4
4
|
// Only the native discovery transport is replaced. Filesystem identity, Git
|
|
5
5
|
// annotations, nested projection and the production catalogue remain real.
|
|
6
|
-
export function fixtureCatalogue(workspacePath) {
|
|
6
|
+
export function fixtureCatalogue(workspacePath, nativeDirectories) {
|
|
7
7
|
const git = (directory, ...args) => execFileSync("git", ["-C", directory, ...args], { encoding: "utf8" }).trim();
|
|
8
8
|
const client = {
|
|
9
9
|
location: { get: async ({ location }) => {
|
|
@@ -13,7 +13,7 @@ export function fixtureCatalogue(workspacePath) {
|
|
|
13
13
|
} },
|
|
14
14
|
worktree: {
|
|
15
15
|
refresh: async () => { },
|
|
16
|
-
list: async () => git(workspacePath, "worktree", "list", "--porcelain", "-z").split("\0")
|
|
16
|
+
list: async () => nativeDirectories?.map(directory => ({ directory })) ?? git(workspacePath, "worktree", "list", "--porcelain", "-z").split("\0")
|
|
17
17
|
.filter(field => field.startsWith("worktree ")).map(field => ({ directory: field.slice(9) })),
|
|
18
18
|
},
|
|
19
19
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-worktree-fixture.js","sourceRoot":"","sources":["../../../src/workspaces/__tests__/native-worktree-fixture.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAEzD,4EAA4E;AAC5E,2EAA2E;AAC3E,MAAM,UAAU,gBAAgB,CAAC,aAAqB;
|
|
1
|
+
{"version":3,"file":"native-worktree-fixture.js","sourceRoot":"","sources":["../../../src/workspaces/__tests__/native-worktree-fixture.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAEzD,4EAA4E;AAC5E,2EAA2E;AAC3E,MAAM,UAAU,gBAAgB,CAAC,aAAqB,EAAE,iBAA4B;IAClF,MAAM,GAAG,GAAG,CAAC,SAAiB,EAAE,GAAG,IAAc,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IAClI,MAAM,MAAM,GAAG;QACb,QAAQ,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAuC,EAAE,EAAE;gBAC3E,MAAM,SAAS,GAAG,GAAG,CAAC,QAAQ,CAAC,SAAS,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAA;gBACzE,MAAM,MAAM,GAAG,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,wBAAwB,EAAE,kBAAkB,CAAC,CAAA;gBACxF,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,CAAA;YAClH,CAAC,EAAE;QACH,QAAQ,EAAE;YACR,OAAO,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;YACvB,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,iBAAiB,EAAE,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;iBAC9I,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;SAChG;KAC2B,CAAA;IAC9B,OAAO,mBAAmB,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,EAAE,MAAM,EAAE,KAAK,EAAC,SAAS,EAAC,EAAE,CAAC,SAAS,EAAE,CAAC,CAAA;AACrI,CAAC","sourcesContent":["import { execFileSync } from \"node:child_process\"\nimport path from \"node:path\"\nimport type { OpenCodeClient } from \"@opencode/client\"\nimport { listNativeWorktrees } from \"../native-worktrees\"\n\n// Only the native discovery transport is replaced. Filesystem identity, Git\n// annotations, nested projection and the production catalogue remain real.\nexport function fixtureCatalogue(workspacePath: string, nativeDirectories?: string[]) {\n const git = (directory: string, ...args: string[]) => execFileSync(\"git\", [\"-C\", directory, ...args], { encoding: \"utf8\" }).trim()\n const client = {\n location: { get: async ({ location }: { location: { directory: string } }) => {\n const directory = git(location.directory, \"rev-parse\", \"--show-toplevel\")\n const common = git(directory, \"rev-parse\", \"--path-format=absolute\", \"--git-common-dir\")\n return { directory: location.directory, project: { id: \"fixture\", directory, canonical: path.dirname(common) } }\n } },\n worktree: {\n refresh: async () => {},\n list: async () => nativeDirectories?.map(directory => ({ directory })) ?? git(workspacePath, \"worktree\", \"list\", \"--porcelain\", \"-z\").split(\"\\0\")\n .filter(field => field.startsWith(\"worktree \")).map(field => ({ directory: field.slice(9) })),\n },\n } as unknown as OpenCodeClient\n return listNativeWorktrees({ client, workspacePath, location: { directory: workspacePath }, toHost: async directory => directory })\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { realpath } from "node:fs/promises";
|
|
2
|
+
import { runWorktreeGit } from "./git-process";
|
|
3
|
+
const pending = new Map();
|
|
4
|
+
/** Share only in-flight negative-preflight reads. Git remains responsible for
|
|
5
|
+
* discovery/configuration; completed identities never become authority caches. */
|
|
6
|
+
export function readGitCommonDirectory(directory) {
|
|
7
|
+
const existing = pending.get(directory);
|
|
8
|
+
if (existing)
|
|
9
|
+
return existing;
|
|
10
|
+
const task = runWorktreeGit(directory, ["rev-parse", "--path-format=absolute", "--git-common-dir"], 10_000)
|
|
11
|
+
.then(directory => realpath(directory)).finally(() => {
|
|
12
|
+
if (pending.get(directory) === task)
|
|
13
|
+
pending.delete(directory);
|
|
14
|
+
});
|
|
15
|
+
pending.set(directory, task);
|
|
16
|
+
return task;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=git-common-directory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-common-directory.js","sourceRoot":"","sources":["../../src/workspaces/git-common-directory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAE9C,MAAM,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAA;AAElD;kFACkF;AAClF,MAAM,UAAU,sBAAsB,CAAC,SAAiB;IACtD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IACvC,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAA;IAC7B,MAAM,IAAI,GAAG,cAAc,CAAC,SAAS,EAAE,CAAC,WAAW,EAAE,wBAAwB,EAAE,kBAAkB,CAAC,EAAE,MAAM,CAAC;SACxG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;QACnD,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,IAAI;YAAE,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IAChE,CAAC,CAAC,CAAA;IACJ,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;IAC5B,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import { realpath } from \"node:fs/promises\"\nimport { runWorktreeGit } from \"./git-process\"\n\nconst pending = new Map<string, Promise<string>>()\n\n/** Share only in-flight negative-preflight reads. Git remains responsible for\n * discovery/configuration; completed identities never become authority caches. */\nexport function readGitCommonDirectory(directory: string): Promise<string> {\n const existing = pending.get(directory)\n if (existing) return existing\n const task = runWorktreeGit(directory, [\"rev-parse\", \"--path-format=absolute\", \"--git-common-dir\"], 10_000)\n .then(directory => realpath(directory)).finally(() => {\n if (pending.get(directory) === task) pending.delete(directory)\n })\n pending.set(directory, task)\n return task\n}\n"]}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Worker } from "node:worker_threads";
|
|
2
|
+
// Even asynchronous execFile spends synchronous time creating a Windows process.
|
|
3
|
+
// Keep that work off the HTTP/SSE thread. The worker is self-contained so the same
|
|
4
|
+
// entry works in compiled desktop resources and TypeScript test runners.
|
|
5
|
+
function workerMain() {
|
|
6
|
+
const { parentPort } = require("node:worker_threads");
|
|
7
|
+
const { execFile } = require("node:child_process");
|
|
8
|
+
const queue = [];
|
|
9
|
+
let running = 0;
|
|
10
|
+
const scheduler = { pump() {
|
|
11
|
+
while (running < 2 && queue.length) {
|
|
12
|
+
const request = queue.shift();
|
|
13
|
+
running += 1;
|
|
14
|
+
execFile("git", ["-C", request.directory, ...request.args], {
|
|
15
|
+
encoding: "utf8", windowsHide: true, maxBuffer: 1024 * 1024,
|
|
16
|
+
env: request.env, timeout: request.timeout,
|
|
17
|
+
}, (error, stdout, stderr) => {
|
|
18
|
+
parentPort.postMessage({ id: request.id, stdout, stderr,
|
|
19
|
+
...(error ? { error: { message: error.message, code: error.code } } : {}),
|
|
20
|
+
});
|
|
21
|
+
running -= 1;
|
|
22
|
+
scheduler.pump();
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
} };
|
|
26
|
+
parentPort.on("message", (request) => { queue.push(request); scheduler.pump(); });
|
|
27
|
+
}
|
|
28
|
+
let worker;
|
|
29
|
+
let sequence = 0;
|
|
30
|
+
const pending = new Map();
|
|
31
|
+
function getWorker() {
|
|
32
|
+
if (worker)
|
|
33
|
+
return worker;
|
|
34
|
+
const created = new Worker(`(${workerMain.toString()})()`, { eval: true, execArgv: [] });
|
|
35
|
+
worker = created;
|
|
36
|
+
const failed = (error) => {
|
|
37
|
+
if (worker !== created)
|
|
38
|
+
return;
|
|
39
|
+
worker = undefined;
|
|
40
|
+
for (const task of pending.values())
|
|
41
|
+
task.reject(error);
|
|
42
|
+
pending.clear();
|
|
43
|
+
};
|
|
44
|
+
created.on("error", failed);
|
|
45
|
+
created.on("exit", code => failed(new Error(`Git process worker exited (${code})`)));
|
|
46
|
+
created.on("message", (response) => {
|
|
47
|
+
const task = pending.get(response.id);
|
|
48
|
+
if (!task)
|
|
49
|
+
return;
|
|
50
|
+
pending.delete(response.id);
|
|
51
|
+
if (response.error)
|
|
52
|
+
task.reject(Object.assign(new Error(response.error.message), {
|
|
53
|
+
code: response.error.code, stdout: response.stdout, stderr: response.stderr,
|
|
54
|
+
}));
|
|
55
|
+
else
|
|
56
|
+
task.resolve(response.stdout.replace(/\r?\n$/, ""));
|
|
57
|
+
if (pending.size === 0)
|
|
58
|
+
created.unref();
|
|
59
|
+
});
|
|
60
|
+
created.unref();
|
|
61
|
+
return created;
|
|
62
|
+
}
|
|
63
|
+
export function runWorktreeGit(directory, args, timeout) {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
const current = getWorker();
|
|
66
|
+
const id = ++sequence;
|
|
67
|
+
pending.set(id, { resolve, reject });
|
|
68
|
+
current.ref();
|
|
69
|
+
try {
|
|
70
|
+
current.postMessage({ id, directory, args, env: { ...process.env }, timeout });
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
pending.delete(id);
|
|
74
|
+
if (pending.size === 0)
|
|
75
|
+
current.unref();
|
|
76
|
+
reject(error);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=git-process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-process.js","sourceRoot":"","sources":["../../src/workspaces/git-process.ts"],"names":[],"mappings":"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","sourcesContent":["import { Worker } from \"node:worker_threads\"\n\ninterface GitRequest { id: number; directory: string; args: string[]; env: NodeJS.ProcessEnv; timeout?: number }\ninterface GitResponse { id: number; stdout: string; stderr: string; error?: { message: string; code?: string | number | null } }\n\n// Even asynchronous execFile spends synchronous time creating a Windows process.\n// Keep that work off the HTTP/SSE thread. The worker is self-contained so the same\n// entry works in compiled desktop resources and TypeScript test runners.\nfunction workerMain() {\n const { parentPort } = require(\"node:worker_threads\") as typeof import(\"node:worker_threads\")\n const { execFile } = require(\"node:child_process\") as typeof import(\"node:child_process\")\n const queue: GitRequest[] = []\n let running = 0\n const scheduler = { pump() {\n while (running < 2 && queue.length) {\n const request = queue.shift()!\n running += 1\n execFile(\"git\", [\"-C\", request.directory, ...request.args], {\n encoding: \"utf8\", windowsHide: true, maxBuffer: 1024 * 1024,\n env: request.env, timeout: request.timeout,\n }, (error, stdout, stderr) => {\n parentPort!.postMessage({ id: request.id, stdout, stderr,\n ...(error ? { error: { message: error.message, code: error.code } } : {}),\n } satisfies GitResponse)\n running -= 1\n scheduler.pump()\n })\n }\n } }\n parentPort!.on(\"message\", (request: GitRequest) => { queue.push(request); scheduler.pump() })\n}\n\nlet worker: Worker | undefined\nlet sequence = 0\nconst pending = new Map<number, { resolve: (stdout: string) => void; reject: (error: Error) => void }>()\n\nfunction getWorker(): Worker {\n if (worker) return worker\n const created = new Worker(`(${workerMain.toString()})()`, { eval: true, execArgv: [] })\n worker = created\n const failed = (error: Error) => {\n if (worker !== created) return\n worker = undefined\n for (const task of pending.values()) task.reject(error)\n pending.clear()\n }\n created.on(\"error\", failed)\n created.on(\"exit\", code => failed(new Error(`Git process worker exited (${code})`)))\n created.on(\"message\", (response: GitResponse) => {\n const task = pending.get(response.id)\n if (!task) return\n pending.delete(response.id)\n if (response.error) task.reject(Object.assign(new Error(response.error.message), {\n code: response.error.code, stdout: response.stdout, stderr: response.stderr,\n }))\n else task.resolve(response.stdout.replace(/\\r?\\n$/, \"\"))\n if (pending.size === 0) created.unref()\n })\n created.unref()\n return created\n}\n\nexport function runWorktreeGit(directory: string, args: string[], timeout?: number): Promise<string> {\n return new Promise((resolve, reject) => {\n const current = getWorker()\n const id = ++sequence\n pending.set(id, { resolve, reject })\n current.ref()\n try {\n current.postMessage({ id, directory, args, env: { ...process.env }, timeout } satisfies GitRequest)\n } catch (error) {\n pending.delete(id)\n if (pending.size === 0) current.unref()\n reject(error)\n }\n })\n}\n"]}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
const execute = promisify(execFile);
|
|
6
|
-
async function git(directory, args) {
|
|
7
|
-
const { stdout } = await execute("git", ["-C", directory, ...args], { windowsHide: true, maxBuffer: 1024 * 1024 });
|
|
8
|
-
return stdout.replace(/\r?\n$/, "");
|
|
9
|
-
}
|
|
2
|
+
import { readFile, realpath, stat } from "node:fs/promises";
|
|
3
|
+
import { readGitCommonDirectory } from "./git-common-directory";
|
|
4
|
+
import { runWorktreeGit as git } from "./git-process";
|
|
10
5
|
export async function resolveRepoRoot(folder, logger) {
|
|
11
6
|
try {
|
|
12
7
|
return { repoRoot: await git(folder, ["rev-parse", "--show-toplevel"]), isGitRepo: true };
|
|
@@ -25,9 +20,8 @@ export async function isGitAvailable(folder) {
|
|
|
25
20
|
// even when independent clones share OpenCode's project ID. This is only a
|
|
26
21
|
// negative preflight; a match still requires the native worktree inventory.
|
|
27
22
|
export async function sharesGitCommonDirectory(left, right) {
|
|
28
|
-
const common = async (directory) => realpath(await git(directory, ["rev-parse", "--path-format=absolute", "--git-common-dir"]));
|
|
29
23
|
try {
|
|
30
|
-
const [a, b] = await Promise.all([
|
|
24
|
+
const [a, b] = await Promise.all([readGitCommonDirectory(left), readGitCommonDirectory(right)]);
|
|
31
25
|
return a === b;
|
|
32
26
|
}
|
|
33
27
|
catch {
|
|
@@ -45,6 +39,66 @@ export async function readCheckout(directory) {
|
|
|
45
39
|
const [root, common, gitDirectory] = paths.split(/\r?\n/);
|
|
46
40
|
return { root: await realpath(root), common: await realpath(common), gitDirectory: await realpath(gitDirectory), head, branch };
|
|
47
41
|
}
|
|
42
|
+
// One Git snapshot supplies HEAD/branch annotations for every local checkout.
|
|
43
|
+
// Native OpenCode remains the inventory authority; callers intersect its entries
|
|
44
|
+
// with these registrations and verify each checkout's physical common directory.
|
|
45
|
+
export async function readWorktreeAnnotations(directory) {
|
|
46
|
+
const output = await git(directory, ["worktree", "list", "--porcelain", "-z"]);
|
|
47
|
+
return output.split("\0\0").filter(Boolean).map(record => {
|
|
48
|
+
const fields = record.split("\0");
|
|
49
|
+
const root = fields.find(field => field.startsWith("worktree "))?.slice(9);
|
|
50
|
+
if (!root)
|
|
51
|
+
throw new Error("Git returned a worktree without a directory");
|
|
52
|
+
const head = fields.find(field => field.startsWith("HEAD "))?.slice(5);
|
|
53
|
+
const branch = fields.find(field => field.startsWith("branch refs/heads/"))?.slice(18);
|
|
54
|
+
return { root, head, branch };
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
export async function readCheckoutIdentity(directory) {
|
|
58
|
+
const entry = path.join(directory, ".git");
|
|
59
|
+
const info = await stat(entry);
|
|
60
|
+
let gitDirectory = entry;
|
|
61
|
+
if (!info.isDirectory()) {
|
|
62
|
+
const pointer = (await readFile(entry, "utf8")).replace(/\r?\n$/, "");
|
|
63
|
+
if (!pointer.startsWith("gitdir: "))
|
|
64
|
+
throw new Error("Invalid worktree Git directory");
|
|
65
|
+
gitDirectory = path.resolve(directory, pointer.slice(8));
|
|
66
|
+
}
|
|
67
|
+
const common = await readFile(path.join(gitDirectory, "commondir"), "utf8").catch(error => {
|
|
68
|
+
if (error.code === "ENOENT")
|
|
69
|
+
return ".";
|
|
70
|
+
throw error;
|
|
71
|
+
});
|
|
72
|
+
const resolvedGit = await realpath(gitDirectory);
|
|
73
|
+
const resolvedCommon = await realpath(path.resolve(gitDirectory, common.replace(/\r?\n$/, "")));
|
|
74
|
+
const backlink = resolvedGit === resolvedCommon ? undefined : (await readFile(path.join(resolvedGit, "gitdir"), "utf8")).replace(/\r?\n$/, "");
|
|
75
|
+
return {
|
|
76
|
+
gitDirectory: resolvedGit,
|
|
77
|
+
common: resolvedCommon,
|
|
78
|
+
root: backlink ? await realpath(path.dirname(path.resolve(resolvedGit, backlink))) : undefined,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export async function createCheckoutRootVerifier(directory) {
|
|
82
|
+
// Read shared/system/global configuration once, letting Git expand includes.
|
|
83
|
+
// Conditional includes and config.worktree can differ for each checkout, so
|
|
84
|
+
// their presence requires Git's own resolution even if this checkout is plain.
|
|
85
|
+
const config = await git(directory, ["config", "--null", "--list", "--includes"]);
|
|
86
|
+
const configuredRoot = config.split("\0").some(entry => {
|
|
87
|
+
const [key, value] = entry.split("\n", 2);
|
|
88
|
+
return key === "core.worktree" || key === "extensions.worktreeconfig" || key.startsWith("includeif.")
|
|
89
|
+
|| (key === "core.bare" && value !== "false");
|
|
90
|
+
}) || ["GIT_DIR", "GIT_COMMON_DIR", "GIT_WORK_TREE", "GIT_IMPLICIT_WORK_TREE"].some(key => process.env[key] !== undefined);
|
|
91
|
+
return async (checkout) => {
|
|
92
|
+
if (!configuredRoot)
|
|
93
|
+
return;
|
|
94
|
+
// Administrative gitdir backlinks describe registration, not necessarily
|
|
95
|
+
// the effective worktree. Keep annotations batched; only exceptional config
|
|
96
|
+
// needs this single extra process (which also rejects bare checkouts).
|
|
97
|
+
const root = await git(checkout, ["rev-parse", "--show-toplevel"]);
|
|
98
|
+
if (await realpath(root) !== await realpath(checkout))
|
|
99
|
+
throw new Error("Native worktree entry is not a checkout root");
|
|
100
|
+
};
|
|
101
|
+
}
|
|
48
102
|
export function isValidWorktreeSlug(slug) {
|
|
49
103
|
return Boolean(slug.trim() && slug.length <= 200 && !/[\x00-\x1F\x7F]/.test(slug));
|
|
50
104
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git-worktrees.js","sourceRoot":"","sources":["../../src/workspaces/git-worktrees.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\"\nimport { execFile } from \"node:child_process\"\nimport { promisify } from \"node:util\"\nimport { realpath } from \"node:fs/promises\"\n\nexport interface LogLike {\n debug?: (obj: any, msg?: string) => void\n warn?: (obj: any, msg?: string) => void\n}\n\nconst execute = promisify(execFile)\nasync function git(directory: string, args: string[]): Promise<string> {\n const { stdout } = await execute(\"git\", [\"-C\", directory, ...args], { windowsHide: true, maxBuffer: 1024 * 1024 })\n return stdout.replace(/\\r?\\n$/, \"\")\n}\n\nexport async function resolveRepoRoot(folder: string, logger?: LogLike): Promise<{ repoRoot: string; isGitRepo: boolean }> {\n try {\n return { repoRoot: await git(folder, [\"rev-parse\", \"--show-toplevel\"]), isGitRepo: true }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") throw new Error(\"Git is not installed or not available in PATH\")\n logger?.debug?.({ folder, err: error }, \"Folder is not a Git repository; using workspace folder as root\")\n return { repoRoot: folder, isGitRepo: false }\n }\n}\n\nexport async function isGitAvailable(folder: string): Promise<boolean> {\n return git(folder, [\"--version\"]).then(() => true, error => error.code !== \"ENOENT\")\n}\n\n// A different physical common directory cannot belong to this local repository,\n// even when independent clones share OpenCode's project ID. This is only a\n// negative preflight; a match still requires the native worktree inventory.\nexport async function sharesGitCommonDirectory(left: string, right: string): Promise<boolean> {\n const common = async (directory: string) => realpath(await git(directory, [\"rev-parse\", \"--path-format=absolute\", \"--git-common-dir\"]))\n try {\n const [a, b] = await Promise.all([common(left), common(right)])\n return a === b\n } catch { return false }\n}\n\n// Git annotations and branch policy, not another worktree registry. Native\n// OpenCode owns discovery, creation and removal of the physical checkouts.\nexport async function readCheckout(directory: string) {\n const [paths, head, branch] = await Promise.all([\n git(directory, [\"rev-parse\", \"--show-toplevel\", \"--path-format=absolute\", \"--git-common-dir\", \"--absolute-git-dir\"]),\n git(directory, [\"rev-parse\", \"--verify\", \"HEAD\"]).catch(() => undefined),\n git(directory, [\"symbolic-ref\", \"--quiet\", \"--short\", \"HEAD\"]).catch(() => undefined),\n ])\n const [root, common, gitDirectory] = paths.split(/\\r?\\n/)\n return { root: await realpath(root), common: await realpath(common), gitDirectory: await realpath(gitDirectory), head, branch }\n}\n\nexport function isValidWorktreeSlug(slug: string): boolean {\n return Boolean(slug.trim() && slug.length <= 200 && !/[\\x00-\\x1F\\x7F]/.test(slug))\n}\n\nexport async function prepareWorktreeBranch(directory: string, branch: string): Promise<{ existing: boolean; revision: string }> {\n await git(directory, [\"check-ref-format\", \"--branch\", branch])\n const revision = await git(directory, [\"rev-parse\", \"--verify\", `refs/heads/${branch}`]).catch(() => undefined)\n return { existing: Boolean(revision), revision: revision ?? await git(directory, [\"rev-parse\", \"--verify\", \"HEAD\"]) }\n}\n\nexport async function attachWorktreeBranch(directory: string, branch: string, existing: boolean): Promise<void> {\n // Never -B/--force: preserve branch refs and Git's already-checked-out guard.\n await git(directory, [\"switch\", ...(existing ? [] : [\"-c\"]), branch])\n}\n\nexport async function gitExcludePath(directory: string): Promise<string> {\n return path.resolve(directory, await git(directory, [\"rev-parse\", \"--git-path\", \"info/exclude\"]))\n}\n"]}
|
|
1
|
+
{"version":3,"file":"git-worktrees.js","sourceRoot":"","sources":["../../src/workspaces/git-worktrees.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\"\nimport { readFile, realpath, stat } from \"node:fs/promises\"\nimport { readGitCommonDirectory } from \"./git-common-directory\"\nimport { runWorktreeGit as git } from \"./git-process\"\n\nexport interface LogLike {\n debug?: (obj: any, msg?: string) => void\n warn?: (obj: any, msg?: string) => void\n}\n\nexport async function resolveRepoRoot(folder: string, logger?: LogLike): Promise<{ repoRoot: string; isGitRepo: boolean }> {\n try {\n return { repoRoot: await git(folder, [\"rev-parse\", \"--show-toplevel\"]), isGitRepo: true }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") throw new Error(\"Git is not installed or not available in PATH\")\n logger?.debug?.({ folder, err: error }, \"Folder is not a Git repository; using workspace folder as root\")\n return { repoRoot: folder, isGitRepo: false }\n }\n}\n\nexport async function isGitAvailable(folder: string): Promise<boolean> {\n return git(folder, [\"--version\"]).then(() => true, error => error.code !== \"ENOENT\")\n}\n\n// A different physical common directory cannot belong to this local repository,\n// even when independent clones share OpenCode's project ID. This is only a\n// negative preflight; a match still requires the native worktree inventory.\nexport async function sharesGitCommonDirectory(left: string, right: string): Promise<boolean> {\n try {\n const [a, b] = await Promise.all([readGitCommonDirectory(left), readGitCommonDirectory(right)])\n return a === b\n } catch { return false }\n}\n\n// Git annotations and branch policy, not another worktree registry. Native\n// OpenCode owns discovery, creation and removal of the physical checkouts.\nexport async function readCheckout(directory: string) {\n const [paths, head, branch] = await Promise.all([\n git(directory, [\"rev-parse\", \"--show-toplevel\", \"--path-format=absolute\", \"--git-common-dir\", \"--absolute-git-dir\"]),\n git(directory, [\"rev-parse\", \"--verify\", \"HEAD\"]).catch(() => undefined),\n git(directory, [\"symbolic-ref\", \"--quiet\", \"--short\", \"HEAD\"]).catch(() => undefined),\n ])\n const [root, common, gitDirectory] = paths.split(/\\r?\\n/)\n return { root: await realpath(root), common: await realpath(common), gitDirectory: await realpath(gitDirectory), head, branch }\n}\n\n// One Git snapshot supplies HEAD/branch annotations for every local checkout.\n// Native OpenCode remains the inventory authority; callers intersect its entries\n// with these registrations and verify each checkout's physical common directory.\nexport async function readWorktreeAnnotations(directory: string) {\n const output = await git(directory, [\"worktree\", \"list\", \"--porcelain\", \"-z\"])\n return output.split(\"\\0\\0\").filter(Boolean).map(record => {\n const fields = record.split(\"\\0\")\n const root = fields.find(field => field.startsWith(\"worktree \"))?.slice(9)\n if (!root) throw new Error(\"Git returned a worktree without a directory\")\n const head = fields.find(field => field.startsWith(\"HEAD \"))?.slice(5)\n const branch = fields.find(field => field.startsWith(\"branch refs/heads/\"))?.slice(18)\n return { root, head, branch }\n })\n}\n\nexport async function readCheckoutIdentity(directory: string) {\n const entry = path.join(directory, \".git\")\n const info = await stat(entry)\n let gitDirectory = entry\n if (!info.isDirectory()) {\n const pointer = (await readFile(entry, \"utf8\")).replace(/\\r?\\n$/, \"\")\n if (!pointer.startsWith(\"gitdir: \")) throw new Error(\"Invalid worktree Git directory\")\n gitDirectory = path.resolve(directory, pointer.slice(8))\n }\n const common = await readFile(path.join(gitDirectory, \"commondir\"), \"utf8\").catch(error => {\n if (error.code === \"ENOENT\") return \".\"\n throw error\n })\n const resolvedGit = await realpath(gitDirectory)\n const resolvedCommon = await realpath(path.resolve(gitDirectory, common.replace(/\\r?\\n$/, \"\")))\n const backlink = resolvedGit === resolvedCommon ? undefined : (await readFile(path.join(resolvedGit, \"gitdir\"), \"utf8\")).replace(/\\r?\\n$/, \"\")\n return {\n gitDirectory: resolvedGit,\n common: resolvedCommon,\n root: backlink ? await realpath(path.dirname(path.resolve(resolvedGit, backlink))) : undefined,\n }\n}\n\nexport async function createCheckoutRootVerifier(directory: string) {\n // Read shared/system/global configuration once, letting Git expand includes.\n // Conditional includes and config.worktree can differ for each checkout, so\n // their presence requires Git's own resolution even if this checkout is plain.\n const config = await git(directory, [\"config\", \"--null\", \"--list\", \"--includes\"])\n const configuredRoot = config.split(\"\\0\").some(entry => {\n const [key, value] = entry.split(\"\\n\", 2)\n return key === \"core.worktree\" || key === \"extensions.worktreeconfig\" || key.startsWith(\"includeif.\")\n || (key === \"core.bare\" && value !== \"false\")\n }) || [\"GIT_DIR\", \"GIT_COMMON_DIR\", \"GIT_WORK_TREE\", \"GIT_IMPLICIT_WORK_TREE\"].some(key => process.env[key] !== undefined)\n\n return async (checkout: string) => {\n if (!configuredRoot) return\n // Administrative gitdir backlinks describe registration, not necessarily\n // the effective worktree. Keep annotations batched; only exceptional config\n // needs this single extra process (which also rejects bare checkouts).\n const root = await git(checkout, [\"rev-parse\", \"--show-toplevel\"])\n if (await realpath(root) !== await realpath(checkout)) throw new Error(\"Native worktree entry is not a checkout root\")\n }\n}\n\nexport function isValidWorktreeSlug(slug: string): boolean {\n return Boolean(slug.trim() && slug.length <= 200 && !/[\\x00-\\x1F\\x7F]/.test(slug))\n}\n\nexport async function prepareWorktreeBranch(directory: string, branch: string): Promise<{ existing: boolean; revision: string }> {\n await git(directory, [\"check-ref-format\", \"--branch\", branch])\n const revision = await git(directory, [\"rev-parse\", \"--verify\", `refs/heads/${branch}`]).catch(() => undefined)\n return { existing: Boolean(revision), revision: revision ?? await git(directory, [\"rev-parse\", \"--verify\", \"HEAD\"]) }\n}\n\nexport async function attachWorktreeBranch(directory: string, branch: string, existing: boolean): Promise<void> {\n // Never -B/--force: preserve branch refs and Git's already-checked-out guard.\n await git(directory, [\"switch\", ...(existing ? [] : [\"-c\"]), branch])\n}\n\nexport async function gitExcludePath(directory: string): Promise<string> {\n return path.resolve(directory, await git(directory, [\"rev-parse\", \"--git-path\", \"info/exclude\"]))\n}\n"]}
|
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { realpath, stat } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { locationRequestOptions } from "../opencode/compatibility/location";
|
|
5
|
-
import { readCheckout, resolveRepoRoot, prepareWorktreeBranch, attachWorktreeBranch } from "./git-worktrees";
|
|
5
|
+
import { readCheckout, readCheckoutIdentity, readWorktreeAnnotations, createCheckoutRootVerifier, resolveRepoRoot, prepareWorktreeBranch, attachWorktreeBranch } from "./git-worktrees";
|
|
6
6
|
import { ensureCodenomadGitExclude } from "./worktree-map";
|
|
7
7
|
const servicePaths = (directory) => /^[A-Za-z]:[\\/]|^(?:\\\\|\/\/)/.test(directory) ? path.win32 : path.posix;
|
|
8
8
|
const identifier = (directory) => `worktree-${createHash("sha256").update(directory).digest("hex").slice(0, 20)}`;
|
|
@@ -27,14 +27,25 @@ export async function listNativeWorktrees(context) {
|
|
|
27
27
|
const options = locationRequestOptions(location, { includeDirectory: true });
|
|
28
28
|
await client.worktree.refresh({ projectID: current.project.id }, options);
|
|
29
29
|
const native = await client.worktree.list({ projectID: current.project.id }, options);
|
|
30
|
+
const verifyCheckoutRoot = await createCheckoutRootVerifier(workspacePath);
|
|
31
|
+
const annotations = new Map();
|
|
32
|
+
for (const annotation of await readWorktreeAnnotations(workspacePath)) {
|
|
33
|
+
const registered = await realpath(annotation.root).catch(error => {
|
|
34
|
+
if (error.code === "ENOENT")
|
|
35
|
+
return undefined;
|
|
36
|
+
throw error;
|
|
37
|
+
});
|
|
38
|
+
if (registered)
|
|
39
|
+
annotations.set(registered, annotation);
|
|
40
|
+
}
|
|
30
41
|
const paths = servicePaths(location.directory);
|
|
31
42
|
const relative = paths.relative(current.project.directory, current.directory);
|
|
32
43
|
if (paths.isAbsolute(relative) || relative === ".." || relative.startsWith(`..${paths.sep}`))
|
|
33
44
|
throw new Error("Workspace is outside its checkout");
|
|
34
45
|
const worktrees = [];
|
|
35
46
|
const seen = new Set();
|
|
36
|
-
//
|
|
37
|
-
//
|
|
47
|
+
// Validate native entries with bounded filesystem I/O. Branch/HEAD annotations
|
|
48
|
+
// come from one Git snapshot rather than three processes per checkout.
|
|
38
49
|
const pending = native.values();
|
|
39
50
|
const inspect = async (entry) => {
|
|
40
51
|
const host = await toHost(entry.directory);
|
|
@@ -50,27 +61,31 @@ export async function listNativeWorktrees(context) {
|
|
|
50
61
|
}
|
|
51
62
|
// Independent clones can share a native project ID. Git's physical common
|
|
52
63
|
// directory decides local membership; branch names and remote URLs do not.
|
|
53
|
-
const
|
|
64
|
+
const registeredDirectory = await realpath(host);
|
|
65
|
+
const annotation = annotations.get(registeredDirectory);
|
|
66
|
+
if (!annotation)
|
|
67
|
+
return;
|
|
68
|
+
const checkout = await readCheckoutIdentity(host);
|
|
54
69
|
if (!await sameDirectory(local.common, checkout.common))
|
|
55
70
|
return;
|
|
56
|
-
|
|
57
|
-
if (!await sameDirectory(checkout.root, registeredDirectory))
|
|
71
|
+
if (!await sameDirectory(checkout.root ?? mainHost, registeredDirectory))
|
|
58
72
|
throw new Error("Native worktree entry is not a checkout root");
|
|
73
|
+
await verifyCheckoutRoot(host);
|
|
59
74
|
if (seen.has(registeredDirectory))
|
|
60
75
|
return;
|
|
61
76
|
seen.add(registeredDirectory);
|
|
62
77
|
const root = await sameDirectory(local.root, registeredDirectory);
|
|
63
78
|
worktrees.push({
|
|
64
79
|
slug: root ? "root" : identifier(registeredDirectory),
|
|
65
|
-
label:
|
|
80
|
+
label: annotation.branch ?? `${path.basename(registeredDirectory)} @ ${annotation.head?.slice(0, 7) ?? "HEAD"}`,
|
|
66
81
|
directory: root ? workspacePath : path.join(registeredDirectory, ...relative.split(/[\\/]/).filter(Boolean)),
|
|
67
82
|
serviceDirectory: root ? current.directory : paths.join(entry.directory, relative),
|
|
68
83
|
registeredDirectory,
|
|
69
84
|
serviceRoot: entry.directory,
|
|
70
85
|
kind: root ? "root" : "worktree",
|
|
71
86
|
removable: !root && !await sameDirectory(checkout.gitDirectory, checkout.common),
|
|
72
|
-
branch:
|
|
73
|
-
head:
|
|
87
|
+
branch: annotation.branch,
|
|
88
|
+
head: annotation.head,
|
|
74
89
|
});
|
|
75
90
|
};
|
|
76
91
|
await Promise.all(Array.from({ length: Math.min(8, native.length) }, async () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-worktrees.js","sourceRoot":"","sources":["../../src/workspaces/native-worktrees.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\"\nimport { realpath, stat } from \"node:fs/promises\"\nimport path from \"node:path\"\nimport type { LocationRef, OpenCodeClient } from \"@opencode/client\"\nimport type { WorktreeDescriptor, WorktreeListResponse } from \"../api-types\"\nimport { locationRequestOptions } from \"../opencode/compatibility/location\"\nimport { readCheckout, resolveRepoRoot, prepareWorktreeBranch, attachWorktreeBranch } from \"./git-worktrees\"\nimport { ensureCodenomadGitExclude } from \"./worktree-map\"\n\nexport interface NativeWorktreeContext {\n client: OpenCodeClient\n location: LocationRef\n workspacePath: string\n toHost: (directory: string) => Promise<string | null>\n}\n\nconst servicePaths = (directory: string) => /^[A-Za-z]:[\\\\/]|^(?:\\\\\\\\|\\/\\/)/.test(directory) ? path.win32 : path.posix\nconst identifier = (directory: string) => `worktree-${createHash(\"sha256\").update(directory).digest(\"hex\").slice(0, 20)}`\n\nasync function sameDirectory(left: string, right: string): Promise<boolean> {\n const [a, b] = await Promise.all([stat(left), stat(right)])\n return a.isDirectory() && b.isDirectory() && a.dev === b.dev && a.ino === b.ino && a.birthtimeMs === b.birthtimeMs\n}\n\nexport async function listNativeWorktrees(context: NativeWorktreeContext): Promise<WorktreeListResponse> {\n const { client, location, workspacePath, toHost } = context\n const { isGitRepo } = await resolveRepoRoot(workspacePath)\n if (!isGitRepo) return { isGitRepo, worktrees: [{ slug: \"root\", directory: workspacePath, serviceDirectory: location.directory, kind: \"root\" }] }\n const local = await readCheckout(workspacePath)\n const current = await client.location.get({ location: { directory: location.directory } }, locationRequestOptions(location))\n const checkoutHost = await toHost(current.project.directory)\n if (!checkoutHost || !await sameDirectory(local.root, checkoutHost)) throw new Error(\"OpenCode resolved a different local checkout\")\n const mainHost = await toHost(current.project.canonical)\n if (!mainHost || !await sameDirectory(local.common, (await readCheckout(mainHost)).common)) {\n throw new Error(\"OpenCode resolved a different local repository\")\n }\n const options = locationRequestOptions(location, { includeDirectory: true })\n await client.worktree.refresh({ projectID: current.project.id }, options)\n const native = await client.worktree.list({ projectID: current.project.id }, options)\n const paths = servicePaths(location.directory)\n const relative = paths.relative(current.project.directory, current.directory)\n if (paths.isAbsolute(relative) || relative === \"..\" || relative.startsWith(`..${paths.sep}`)) throw new Error(\"Workspace is outside its checkout\")\n const worktrees: WorktreeDescriptor[] = []\n const seen = new Set<string>()\n // Large local repositories can contain hundreds of checkouts. Bound process\n // concurrency, but do not serialize three Git processes per inventory entry.\n const pending = native.values()\n const inspect = async (entry: (typeof native)[number]) => {\n const host = await toHost(entry.directory)\n if (!host) throw new Error(\"Unable to translate native worktree directory\")\n try {\n await stat(host)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return\n throw error\n }\n // Independent clones can share a native project ID. Git's physical common\n // directory decides local membership; branch names and remote URLs do not.\n const checkout = await readCheckout(host)\n if (!await sameDirectory(local.common, checkout.common)) return\n const registeredDirectory = await realpath(host)\n if (!await sameDirectory(checkout.root, registeredDirectory)) throw new Error(\"Native worktree entry is not a checkout root\")\n if (seen.has(registeredDirectory)) return\n seen.add(registeredDirectory)\n const root = await sameDirectory(local.root, registeredDirectory)\n worktrees.push({\n slug: root ? \"root\" : identifier(registeredDirectory),\n label: checkout.branch ?? `${path.basename(registeredDirectory)} @ ${checkout.head?.slice(0, 7) ?? \"HEAD\"}`,\n directory: root ? workspacePath : path.join(registeredDirectory, ...relative.split(/[\\\\/]/).filter(Boolean)),\n serviceDirectory: root ? current.directory : paths.join(entry.directory, relative),\n registeredDirectory,\n serviceRoot: entry.directory,\n kind: root ? \"root\" : \"worktree\",\n removable: !root && !await sameDirectory(checkout.gitDirectory, checkout.common),\n branch: checkout.branch,\n head: checkout.head,\n })\n }\n await Promise.all(Array.from({ length: Math.min(8, native.length) }, async () => {\n for (const entry of pending) await inspect(entry)\n }))\n if (!worktrees.some(entry => entry.kind === \"root\")) throw new Error(\"Native worktree inventory is missing the opened checkout\")\n return {\n isGitRepo,\n defaultDirectory: paths.join(current.project.canonical, \".codenomad\", \"worktrees\"),\n worktrees: worktrees.sort((a, b) => a.kind === \"root\" ? -1 : b.kind === \"root\" ? 1 : a.label!.localeCompare(b.label!)),\n }\n}\n\nexport async function createNativeWorktree(context: NativeWorktreeContext, branch: string, fromSlug = \"root\") {\n const catalogue = await listNativeWorktrees(context)\n const source = catalogue.worktrees.find(entry => entry.slug === fromSlug)\n if (!catalogue.isGitRepo || !source?.serviceRoot || !catalogue.defaultDirectory) throw new Error(\"Source worktree not found\")\n const policy = await prepareWorktreeBranch(source.registeredDirectory!, branch)\n await ensureCodenomadGitExclude(context.workspacePath)\n const { client, location } = context\n const current = await client.location.get({ location: { directory: location.directory } }, locationRequestOptions(location))\n const options = locationRequestOptions(location, { includeDirectory: true })\n const created = await client.worktree.create({\n projectID: current.project.id,\n from: source.serviceRoot,\n branch: policy.revision,\n directory: catalogue.defaultDirectory,\n name: branch.replace(/[^a-zA-Z0-9_.-]+/g, \"-\").replace(/^\\.+/, \"\") || \"worktree\",\n }, options)\n const host = await context.toHost(created.directory)\n const paths = servicePaths(created.directory)\n const relative = paths.relative(catalogue.defaultDirectory, created.directory)\n if (!host || !relative || paths.isAbsolute(relative) || relative === \"..\" || relative.startsWith(`..${paths.sep}`)) {\n throw new Error(\"Native worktree creation returned an unexpected directory\")\n }\n const [sourceCheckout, createdCheckout] = await Promise.all([readCheckout(source.registeredDirectory!), readCheckout(host)])\n if (!await sameDirectory(sourceCheckout.common, createdCheckout.common)) throw new Error(\"Created worktree belongs to another local repository\")\n try {\n // Native creation deliberately detaches HEAD. Branch selection remains Git\n // policy: preserve a named branch without duplicating worktree management.\n await attachWorktreeBranch(host, branch, policy.existing)\n } catch (error) {\n try {\n await client.worktree.remove({ projectID: current.project.id, directory: created.directory, force: false }, options)\n } catch (rollback) {\n throw new Error(`Unable to attach branch: ${String(error)}; worktree cleanup failed: ${String(rollback)}`)\n }\n throw error\n }\n const updated = await listNativeWorktrees(context)\n const registered = await realpath(host)\n const result = updated.worktrees.find(entry => entry.registeredDirectory === registered)\n if (!result) throw new Error(\"Created worktree is missing from native inventory\")\n return result\n}\n\nexport async function removeNativeWorktree(context: NativeWorktreeContext, directory: string, force: boolean) {\n const { client, location } = context\n const current = await client.location.get({ location: { directory: location.directory } }, locationRequestOptions(location))\n await client.worktree.remove({ projectID: current.project.id, directory, force }, locationRequestOptions(location, { includeDirectory: true }))\n}\n"]}
|
|
1
|
+
{"version":3,"file":"native-worktrees.js","sourceRoot":"","sources":["../../src/workspaces/native-worktrees.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,IAAI,MAAM,WAAW,CAAA;AAG5B,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAA;AAC3E,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,eAAe,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AACvL,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAA;AAS1D,MAAM,YAAY,GAAG,CAAC,SAAiB,EAAE,EAAE,CAAC,gCAAgC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAA;AACtH,MAAM,UAAU,GAAG,CAAC,SAAiB,EAAE,EAAE,CAAC,YAAY,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAA;AAEzH,KAAK,UAAU,aAAa,CAAC,IAAY,EAAE,KAAa;IACtD,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IAC3D,OAAO,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,WAAW,CAAA;AACpH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,OAA8B;IACtE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,OAAO,CAAA;IAC3D,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,eAAe,CAAC,aAAa,CAAC,CAAA;IAC1D,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,gBAAgB,EAAE,QAAQ,CAAC,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAA;IACjJ,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,aAAa,CAAC,CAAA;IAC/C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,EAAE,EAAE,sBAAsB,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC5H,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IAC5D,IAAI,CAAC,YAAY,IAAI,CAAC,MAAM,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,YAAY,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAA;IACpI,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IACxD,IAAI,CAAC,QAAQ,IAAI,CAAC,MAAM,aAAa,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3F,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAA;IACnE,CAAC;IACD,MAAM,OAAO,GAAG,sBAAsB,CAAC,QAAQ,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAA;IAC5E,MAAM,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;IACzE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;IACrF,MAAM,kBAAkB,GAAG,MAAM,0BAA0B,CAAC,aAAa,CAAC,CAAA;IAC1E,MAAM,WAAW,GAAG,IAAI,GAAG,EAAuE,CAAA;IAClG,KAAK,MAAM,UAAU,IAAI,MAAM,uBAAuB,CAAC,aAAa,CAAC,EAAE,CAAC;QACtE,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;YAC/D,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAO,SAAS,CAAA;YAC7C,MAAM,KAAK,CAAA;QACb,CAAC,CAAC,CAAA;QACF,IAAI,UAAU;YAAE,WAAW,CAAC,GAAG,CAAC,UAAU,EAAE,UAAU,CAAC,CAAA;IACzD,CAAC;IACD,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;IAC9C,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,SAAS,CAAC,CAAA;IAC7E,IAAI,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,KAAK,CAAC,GAAG,EAAE,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAA;IAClJ,MAAM,SAAS,GAAyB,EAAE,CAAA;IAC1C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,+EAA+E;IAC/E,uEAAuE;IACvE,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,CAAA;IAC/B,MAAM,OAAO,GAAG,KAAK,EAAE,KAA8B,EAAE,EAAE;QACvD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QAC1C,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;QAC3E,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,IAAI,CAAC,CAAA;QAClB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAM;YAC9D,MAAM,KAAK,CAAA;QACb,CAAC;QACD,0EAA0E;QAC1E,2EAA2E;QAC3E,MAAM,mBAAmB,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAA;QAChD,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAA;QACvD,IAAI,CAAC,UAAU;YAAE,OAAM;QACvB,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,IAAI,CAAC,CAAA;QACjD,IAAI,CAAC,MAAM,aAAa,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAM;QAC/D,IAAI,CAAC,MAAM,aAAa,CAAC,QAAQ,CAAC,IAAI,IAAI,QAAQ,EAAE,mBAAmB,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAA;QACzI,MAAM,kBAAkB,CAAC,IAAI,CAAC,CAAA;QAC9B,IAAI,IAAI,CAAC,GAAG,CAAC,mBAAmB,CAAC;YAAE,OAAM;QACzC,IAAI,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAA;QAC7B,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAA;QACjE,SAAS,CAAC,IAAI,CAAC;YACb,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,mBAAmB,CAAC;YACrD,KAAK,EAAE,UAAU,CAAC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,MAAM,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,EAAE;YAC/G,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC5G,gBAAgB,EAAE,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,QAAQ,CAAC;YAClF,mBAAmB;YACnB,WAAW,EAAE,KAAK,CAAC,SAAS;YAC5B,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU;YAChC,SAAS,EAAE,CAAC,IAAI,IAAI,CAAC,MAAM,aAAa,CAAC,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC;YAChF,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,IAAI,EAAE,UAAU,CAAC,IAAI;SACtB,CAAC,CAAA;IACJ,CAAC,CAAA;IACD,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,IAAI,EAAE;QAC9E,KAAK,MAAM,KAAK,IAAI,OAAO;YAAE,MAAM,OAAO,CAAC,KAAK,CAAC,CAAA;IACnD,CAAC,CAAC,CAAC,CAAA;IACH,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAA;IAChI,OAAO;QACL,SAAS;QACT,gBAAgB,EAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,YAAY,EAAE,WAAW,CAAC;QAClF,SAAS,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAM,CAAC,aAAa,CAAC,CAAC,CAAC,KAAM,CAAC,CAAC;KACvH,CAAA;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,OAA8B,EAAE,MAAc,EAAE,QAAQ,GAAG,MAAM;IAC1G,MAAM,SAAS,GAAG,MAAM,mBAAmB,CAAC,OAAO,CAAC,CAAA;IACpD,MAAM,MAAM,GAAG,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAA;IACzE,IAAI,CAAC,SAAS,CAAC,SAAS,IAAI,CAAC,MAAM,EAAE,WAAW,IAAI,CAAC,SAAS,CAAC,gBAAgB;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAA;IAC7H,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,MAAM,CAAC,mBAAoB,EAAE,MAAM,CAAC,CAAA;IAC/E,MAAM,yBAAyB,CAAC,OAAO,CAAC,aAAa,CAAC,CAAA;IACtD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAA;IACpC,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,EAAE,EAAE,sBAAsB,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC5H,MAAM,OAAO,GAAG,sBAAsB,CAAC,QAAQ,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAA;IAC5E,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC3C,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE;QAC7B,IAAI,EAAE,MAAM,CAAC,WAAW;QACxB,MAAM,EAAE,MAAM,CAAC,QAAQ;QACvB,SAAS,EAAE,SAAS,CAAC,gBAAgB;QACrC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,UAAU;KACjF,EAAE,OAAO,CAAC,CAAA;IACX,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IACpD,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IAC7C,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,gBAAgB,EAAE,OAAO,CAAC,SAAS,CAAC,CAAA;IAC9E,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;QACnH,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAA;IAC9E,CAAC;IACD,MAAM,CAAC,cAAc,EAAE,eAAe,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,mBAAoB,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC5H,IAAI,CAAC,MAAM,aAAa,CAAC,cAAc,CAAC,MAAM,EAAE,eAAe,CAAC,MAAM,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAA;IAChJ,IAAI,CAAC;QACH,2EAA2E;QAC3E,2EAA2E;QAC3E,MAAM,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;IAC3D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,OAAO,CAAC,CAAA;QACtH,CAAC;QAAC,OAAO,QAAQ,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,4BAA4B,MAAM,CAAC,KAAK,CAAC,8BAA8B,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;QAC5G,CAAC;QACD,MAAM,KAAK,CAAA;IACb,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,mBAAmB,CAAC,OAAO,CAAC,CAAA;IAClD,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAA;IACvC,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,mBAAmB,KAAK,UAAU,CAAC,CAAA;IACxF,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAA;IACjF,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,OAA8B,EAAE,SAAiB,EAAE,KAAc;IAC1G,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAA;IACpC,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,EAAE,EAAE,sBAAsB,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC5H,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,sBAAsB,CAAC,QAAQ,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;AACjJ,CAAC","sourcesContent":["import { createHash } from \"node:crypto\"\nimport { realpath, stat } from \"node:fs/promises\"\nimport path from \"node:path\"\nimport type { LocationRef, OpenCodeClient } from \"@opencode/client\"\nimport type { WorktreeDescriptor, WorktreeListResponse } from \"../api-types\"\nimport { locationRequestOptions } from \"../opencode/compatibility/location\"\nimport { readCheckout, readCheckoutIdentity, readWorktreeAnnotations, createCheckoutRootVerifier, resolveRepoRoot, prepareWorktreeBranch, attachWorktreeBranch } from \"./git-worktrees\"\nimport { ensureCodenomadGitExclude } from \"./worktree-map\"\n\nexport interface NativeWorktreeContext {\n client: OpenCodeClient\n location: LocationRef\n workspacePath: string\n toHost: (directory: string) => Promise<string | null>\n}\n\nconst servicePaths = (directory: string) => /^[A-Za-z]:[\\\\/]|^(?:\\\\\\\\|\\/\\/)/.test(directory) ? path.win32 : path.posix\nconst identifier = (directory: string) => `worktree-${createHash(\"sha256\").update(directory).digest(\"hex\").slice(0, 20)}`\n\nasync function sameDirectory(left: string, right: string): Promise<boolean> {\n const [a, b] = await Promise.all([stat(left), stat(right)])\n return a.isDirectory() && b.isDirectory() && a.dev === b.dev && a.ino === b.ino && a.birthtimeMs === b.birthtimeMs\n}\n\nexport async function listNativeWorktrees(context: NativeWorktreeContext): Promise<WorktreeListResponse> {\n const { client, location, workspacePath, toHost } = context\n const { isGitRepo } = await resolveRepoRoot(workspacePath)\n if (!isGitRepo) return { isGitRepo, worktrees: [{ slug: \"root\", directory: workspacePath, serviceDirectory: location.directory, kind: \"root\" }] }\n const local = await readCheckout(workspacePath)\n const current = await client.location.get({ location: { directory: location.directory } }, locationRequestOptions(location))\n const checkoutHost = await toHost(current.project.directory)\n if (!checkoutHost || !await sameDirectory(local.root, checkoutHost)) throw new Error(\"OpenCode resolved a different local checkout\")\n const mainHost = await toHost(current.project.canonical)\n if (!mainHost || !await sameDirectory(local.common, (await readCheckout(mainHost)).common)) {\n throw new Error(\"OpenCode resolved a different local repository\")\n }\n const options = locationRequestOptions(location, { includeDirectory: true })\n await client.worktree.refresh({ projectID: current.project.id }, options)\n const native = await client.worktree.list({ projectID: current.project.id }, options)\n const verifyCheckoutRoot = await createCheckoutRootVerifier(workspacePath)\n const annotations = new Map<string, Awaited<ReturnType<typeof readWorktreeAnnotations>>[number]>()\n for (const annotation of await readWorktreeAnnotations(workspacePath)) {\n const registered = await realpath(annotation.root).catch(error => {\n if (error.code === \"ENOENT\") return undefined\n throw error\n })\n if (registered) annotations.set(registered, annotation)\n }\n const paths = servicePaths(location.directory)\n const relative = paths.relative(current.project.directory, current.directory)\n if (paths.isAbsolute(relative) || relative === \"..\" || relative.startsWith(`..${paths.sep}`)) throw new Error(\"Workspace is outside its checkout\")\n const worktrees: WorktreeDescriptor[] = []\n const seen = new Set<string>()\n // Validate native entries with bounded filesystem I/O. Branch/HEAD annotations\n // come from one Git snapshot rather than three processes per checkout.\n const pending = native.values()\n const inspect = async (entry: (typeof native)[number]) => {\n const host = await toHost(entry.directory)\n if (!host) throw new Error(\"Unable to translate native worktree directory\")\n try {\n await stat(host)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return\n throw error\n }\n // Independent clones can share a native project ID. Git's physical common\n // directory decides local membership; branch names and remote URLs do not.\n const registeredDirectory = await realpath(host)\n const annotation = annotations.get(registeredDirectory)\n if (!annotation) return\n const checkout = await readCheckoutIdentity(host)\n if (!await sameDirectory(local.common, checkout.common)) return\n if (!await sameDirectory(checkout.root ?? mainHost, registeredDirectory)) throw new Error(\"Native worktree entry is not a checkout root\")\n await verifyCheckoutRoot(host)\n if (seen.has(registeredDirectory)) return\n seen.add(registeredDirectory)\n const root = await sameDirectory(local.root, registeredDirectory)\n worktrees.push({\n slug: root ? \"root\" : identifier(registeredDirectory),\n label: annotation.branch ?? `${path.basename(registeredDirectory)} @ ${annotation.head?.slice(0, 7) ?? \"HEAD\"}`,\n directory: root ? workspacePath : path.join(registeredDirectory, ...relative.split(/[\\\\/]/).filter(Boolean)),\n serviceDirectory: root ? current.directory : paths.join(entry.directory, relative),\n registeredDirectory,\n serviceRoot: entry.directory,\n kind: root ? \"root\" : \"worktree\",\n removable: !root && !await sameDirectory(checkout.gitDirectory, checkout.common),\n branch: annotation.branch,\n head: annotation.head,\n })\n }\n await Promise.all(Array.from({ length: Math.min(8, native.length) }, async () => {\n for (const entry of pending) await inspect(entry)\n }))\n if (!worktrees.some(entry => entry.kind === \"root\")) throw new Error(\"Native worktree inventory is missing the opened checkout\")\n return {\n isGitRepo,\n defaultDirectory: paths.join(current.project.canonical, \".codenomad\", \"worktrees\"),\n worktrees: worktrees.sort((a, b) => a.kind === \"root\" ? -1 : b.kind === \"root\" ? 1 : a.label!.localeCompare(b.label!)),\n }\n}\n\nexport async function createNativeWorktree(context: NativeWorktreeContext, branch: string, fromSlug = \"root\") {\n const catalogue = await listNativeWorktrees(context)\n const source = catalogue.worktrees.find(entry => entry.slug === fromSlug)\n if (!catalogue.isGitRepo || !source?.serviceRoot || !catalogue.defaultDirectory) throw new Error(\"Source worktree not found\")\n const policy = await prepareWorktreeBranch(source.registeredDirectory!, branch)\n await ensureCodenomadGitExclude(context.workspacePath)\n const { client, location } = context\n const current = await client.location.get({ location: { directory: location.directory } }, locationRequestOptions(location))\n const options = locationRequestOptions(location, { includeDirectory: true })\n const created = await client.worktree.create({\n projectID: current.project.id,\n from: source.serviceRoot,\n branch: policy.revision,\n directory: catalogue.defaultDirectory,\n name: branch.replace(/[^a-zA-Z0-9_.-]+/g, \"-\").replace(/^\\.+/, \"\") || \"worktree\",\n }, options)\n const host = await context.toHost(created.directory)\n const paths = servicePaths(created.directory)\n const relative = paths.relative(catalogue.defaultDirectory, created.directory)\n if (!host || !relative || paths.isAbsolute(relative) || relative === \"..\" || relative.startsWith(`..${paths.sep}`)) {\n throw new Error(\"Native worktree creation returned an unexpected directory\")\n }\n const [sourceCheckout, createdCheckout] = await Promise.all([readCheckout(source.registeredDirectory!), readCheckout(host)])\n if (!await sameDirectory(sourceCheckout.common, createdCheckout.common)) throw new Error(\"Created worktree belongs to another local repository\")\n try {\n // Native creation deliberately detaches HEAD. Branch selection remains Git\n // policy: preserve a named branch without duplicating worktree management.\n await attachWorktreeBranch(host, branch, policy.existing)\n } catch (error) {\n try {\n await client.worktree.remove({ projectID: current.project.id, directory: created.directory, force: false }, options)\n } catch (rollback) {\n throw new Error(`Unable to attach branch: ${String(error)}; worktree cleanup failed: ${String(rollback)}`)\n }\n throw error\n }\n const updated = await listNativeWorktrees(context)\n const registered = await realpath(host)\n const result = updated.worktrees.find(entry => entry.registeredDirectory === registered)\n if (!result) throw new Error(\"Created worktree is missing from native inventory\")\n return result\n}\n\nexport async function removeNativeWorktree(context: NativeWorktreeContext, directory: string, force: boolean) {\n const { client, location } = context\n const current = await client.location.get({ location: { directory: location.directory } }, locationRequestOptions(location))\n await client.worktree.remove({ projectID: current.project.id, directory, force }, locationRequestOptions(location, { includeDirectory: true }))\n}\n"]}
|