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",
});
});
});