import test from "node:test"; import assert from "node:assert/strict"; import { createHash } from "node:crypto"; import { execFile } from "node:child_process"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { promisify } from "node:util"; import { parseWorktreeCommand } from "../src/git-worktree-proxy.mjs"; import { parseAsar, readAsarMember, replaceAsarMember } from "../src/asar.mjs"; const exec = promisify(execFile); const proxyPath = path.resolve("bin/git-worktree-proxy.mjs"); function tinyAsar(memberPath, content) { const parts = memberPath.split("/"); const header = { files: {} }; let files = header.files; for (const part of parts.slice(0, -1)) { files[part] = { files: {} }; files = files[part].files; } files[parts.at(-1)] = { size: content.length, offset: "0" }; const json = Buffer.from(JSON.stringify(header)); const padded = (json.length + 4) & ~3; const prefix = Buffer.alloc(16 + padded); prefix.writeUInt32LE(4, 0); prefix.writeUInt32LE(8 + padded, 4); prefix.writeUInt32LE(4 + padded, 8); prefix.writeUInt32LE(json.length, 12); json.copy(prefix, 16); return Buffer.concat([prefix, content]); } test("parseWorktreeCommand handles Desktop's global Git config arguments", () => { const parsed = parseWorktreeCommand([ "-c", "safe.bareRepository=explicit", "-c", "core.hooksPath=/dev/null", "-C", "repo", "worktree", "add", "--detach", "../target", "deadbeef", ], "/tmp/base"); assert.deepEqual(parsed, { cwd: "/tmp/base/repo", subcommand: "add", force: false, targetPath: "/tmp/base/target", revision: "deadbeef", }); }); test("replaceAsarMember preserves readable members and updates offsets", () => { const first = Buffer.from("worker"); const archive = tinyAsar("build/worker.js", first); const replacement = Buffer.from("patched worker contents"); const result = replaceAsarMember(archive, "build/worker.js", replacement); assert.equal(readAsarMember(result.archive, "build/worker.js").toString(), replacement.toString()); const headerHash = createHash("sha256").update(parseAsar(result.archive).headerBytes).digest("hex"); assert.equal(result.headerHash, headerHash); }); test("Git worktree proxy creates and removes a jj workspace only for a jj source", async (t) => { try { await exec("jj", ["--version"]); } catch { t.skip("jj is unavailable"); return; } const root = await fs.mkdtemp(path.join(os.tmpdir(), "omp-proxy-jj-")); const repo = path.join(root, "repo"); const target = path.join(root, "missing", "nested", "target"); const legacyGitTarget = path.join(root, "legacy-git-target"); try { await exec("jj", ["git", "init", "--colocate", repo]); await fs.writeFile(path.join(repo, "base.txt"), "base\n"); await exec("jj", ["commit", "-R", repo, "-m", "base"]); const revision = (await exec("git", ["-C", repo, "rev-parse", "HEAD"])).stdout.trim(); await fs.writeFile(path.join(repo, "base.txt"), "newer source state\n"); await exec("jj", ["commit", "-R", repo, "-m", "newer"]); await exec(process.execPath, [proxyPath, "-C", repo, "-c", "safe.bareRepository=explicit", "worktree", "add", "--detach", target, revision]); assert.equal((await fs.stat(path.join(target, ".jj"))).isDirectory(), true); assert.equal((await fs.stat(path.join(target, ".git"))).isFile(), true); assert.equal( await fs.realpath((await exec("jj", ["root", "-R", target])).stdout.trim()), await fs.realpath(target) ); assert.equal((await exec("git", ["-C", target, "rev-parse", "HEAD"])).stdout.trim(), revision); assert.equal(await fs.readFile(path.join(target, "base.txt"), "utf8"), "base\n"); await fs.writeFile(path.join(target, "dirty.txt"), "keep me\n"); await assert.rejects( exec(process.execPath, [proxyPath, "-C", repo, "worktree", "remove", target]), /contains modified or untracked files/ ); assert.equal(await fs.readFile(path.join(target, "dirty.txt"), "utf8"), "keep me\n"); await exec(process.execPath, [proxyPath, "-C", repo, "worktree", "remove", "--force", target]); assert.equal(await fs.stat(target).then(() => true).catch(() => false), false); await exec("git", ["-C", repo, "worktree", "add", "--detach", legacyGitTarget, revision]); await exec(process.execPath, [proxyPath, "-C", repo, "worktree", "remove", "--force", legacyGitTarget]); const listedWorktrees = (await exec("git", ["-C", repo, "worktree", "list", "--porcelain"])).stdout; assert.equal(listedWorktrees.includes(legacyGitTarget), false); } finally { await fs.rm(root, { recursive: true, force: true }); } }); test("Git worktree proxy delegates Git-native repositories", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "omp-proxy-git-")); const repo = path.join(root, "repo"); const target = path.join(root, "target"); try { await exec("git", ["init", "-q", repo]); await exec("git", ["-C", repo, "config", "user.name", "Test"]); await exec("git", ["-C", repo, "config", "user.email", "test@example.com"]); // Throwaway fixture: never inherit the developer's commit signing, which // needs an agent this test has no reason to depend on. await exec("git", ["-C", repo, "config", "commit.gpgsign", "false"]); await fs.writeFile(path.join(repo, "base.txt"), "base\n"); await exec("git", ["-C", repo, "add", "base.txt"]); await exec("git", ["-C", repo, "commit", "-qm", "base"]); await exec(process.execPath, [proxyPath, "-C", repo, "worktree", "add", "--detach", target, "HEAD"]); assert.equal((await fs.stat(path.join(target, ".git"))).isFile(), true); assert.equal(await fs.stat(path.join(target, ".jj")).then(() => true).catch(() => false), false); await exec(process.execPath, [proxyPath, "-C", repo, "worktree", "remove", "--force", target]); } finally { await fs.rm(root, { recursive: true, force: true }); } });