import { mkdtemp, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; // oxlint-disable-next-line unicorn/import-style -- named import avoids shadowing local `path` vars import { join } from "node:path"; import { Effect } from "effect"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { addWorktree, commit, currentBranch, nodeShell, push, } from "./git-local-runtime"; const run = (eff: Effect.Effect) => Effect.runPromise(eff); const execPlain = async (cwd: string, cmd: string) => { const res = await run(nodeShell.exec("sh", ["-c", cmd], { cwd })); return res.stdout.trim(); }; let root = ""; let origin = ""; beforeEach(async () => { root = await mkdtemp(join(tmpdir(), "git-local-")); origin = join(root, "origin.git"); await execPlain(root, `git init --bare -b main ${origin}`); const seed = join(root, "seed"); await execPlain(root, `git clone ${origin} ${seed}`); await execPlain( seed, "git config user.email t@t.t && git config user.name t" ); await writeFile(join(seed, "README.md"), "# hi\n"); await execPlain( seed, "git add -A && git commit -m init && git push -u origin main" ); }); afterEach(async () => { await execPlain(root, "chmod -R u+w . 2>/dev/null; true"); }); describe("GitLocalRuntime", () => { it("creates a worktree on a new branch", async () => { const cloneDir = join(root, "work"); await run(nodeShell.exec("git", ["clone", origin, cloneDir])); const worktree = join(root, "wt-1"); const result = await run( addWorktree(nodeShell, { branch: "work/1/fix", path: worktree, repositoryPath: cloneDir, }) ); expect(result).toBe(worktree); expect(await execPlain(worktree, "git rev-parse --abbrev-ref HEAD")).toBe( "work/1/fix" ); }); it("commits staged changes and returns the new sha", async () => { const cloneDir = join(root, "work2"); await run(nodeShell.exec("git", ["clone", origin, cloneDir])); await run( nodeShell.exec( "git", ["worktree", "add", "-b", "work/2", join(root, "wt-2")], { cwd: cloneDir } ) ); const worktree = join(root, "wt-2"); await writeFile(join(worktree, "a.txt"), "a"); const sha = await run( commit(nodeShell, { message: "add a", repositoryPath: worktree }) ); expect(sha).toMatch(/^[0-9a-f]{7,}/u); }); it("reads the current branch", async () => { const cloneDir = join(root, "work3"); await run(nodeShell.exec("git", ["clone", origin, cloneDir])); expect(await run(currentBranch(nodeShell, cloneDir))).toBe("main"); }); it("pushes a branch to origin", async () => { const cloneDir = join(root, "work4"); await run(nodeShell.exec("git", ["clone", origin, cloneDir])); await run( nodeShell.exec( "git", ["worktree", "add", "-b", "work/4", join(root, "wt-4")], { cwd: cloneDir } ) ); const worktree = join(root, "wt-4"); await writeFile(join(worktree, "b.txt"), "b"); await run( commit(nodeShell, { message: "add b", repositoryPath: worktree }) ); await run(push(nodeShell, { refspec: "work/4", repositoryPath: worktree })); const branches = await execPlain(origin, "git branch --list"); expect(branches).toContain("work/4"); }); it("maps a non-repo path to NotARepository", async () => { const empty = await mkdtemp(join(tmpdir(), "empty-")); await expect(run(currentBranch(nodeShell, empty))).rejects.toMatchObject({ reason: "NotARepository", }); }); });