// @effect-diagnostics nodeBuiltinImport:offimport * as NodePath from "node:path"; import * as NodeServices from "@effect/platform-node/NodeServices";import { it } from "@effect/vitest";import { ThreadId, type VcsError } from "@t3tools/contracts";import { HostProcessPlatform } from "@t3tools/shared/hostProcess";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as PlatformError from "effect/PlatformError";import * as Scope from "effect/Scope";import { describe, expect } from "vite-plus/test"; import { checkpointRefForThreadTurn } from "./Utils.ts";import { parseTurnDiffFilesFromNumstat } from "./Diffs.ts";import * as CheckpointStore from "./CheckpointStore.ts";import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";import * as VcsProcess from "../vcs/VcsProcess.ts";import * as ServerConfig from "../config.ts"; const ServerConfigLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), { prefix: "t3-checkpoint-store-test-",});const VcsProcessTestLayer = VcsProcess.layer.pipe(Layer.provide(NodeServices.layer));const VcsDriverTestLayer = VcsDriverRegistry.layer.pipe(Layer.provide(VcsProcessTestLayer));const CheckpointStoreTestLayer = CheckpointStore.layer.pipe( Layer.provideMerge(VcsDriverTestLayer), Layer.provideMerge(NodeServices.layer),);const TestLayer = CheckpointStoreTestLayer.pipe( Layer.provideMerge(VcsProcessTestLayer), Layer.provideMerge(VcsDriverTestLayer), Layer.provideMerge(ServerConfigLayer), Layer.provideMerge(NodeServices.layer),); function makeTmpDir( prefix = "checkpoint-store-test-",): Effect.Effect<string, PlatformError.PlatformError, FileSystem.FileSystem | Scope.Scope> { return Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; return yield* fileSystem.makeTempDirectoryScoped({ prefix }); });} function writeTextFile( filePath: string, contents: string,): Effect.Effect<void, PlatformError.PlatformError, FileSystem.FileSystem> { return Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; yield* fileSystem.writeFileString(filePath, contents); });} function git( cwd: string, args: ReadonlyArray<string>,): Effect.Effect<string, VcsError, VcsProcess.VcsProcess> { return Effect.gen(function* () { const process = yield* VcsProcess.VcsProcess; const result = yield* process.run({ operation: "CheckpointStore.test.git", command: "git", cwd, args, timeoutMs: 10_000, }); return result.stdout.trim(); });} function gitAllowNonZero(cwd: string, args: ReadonlyArray<string>) { return Effect.gen(function* () { const process = yield* VcsProcess.VcsProcess; return yield* process.run({ operation: "CheckpointStore.test.gitAllowNonZero", command: "git", cwd, args, timeoutMs: 10_000, allowNonZeroExit: true, }); });} function gitWithStdin(cwd: string, args: ReadonlyArray<string>, stdin: string) { return Effect.gen(function* () { const process = yield* VcsProcess.VcsProcess; const result = yield* process.run({ operation: "CheckpointStore.test.gitWithStdin", command: "git", cwd, args, stdin, timeoutMs: 10_000, }); return result.stdout.trim(); });} function initRepoWithCommit( cwd: string,): Effect.Effect< void, VcsError | PlatformError.PlatformError, VcsProcess.VcsProcess | FileSystem.FileSystem> { return Effect.gen(function* () { yield* git(cwd, ["init"]); yield* git(cwd, ["config", "user.email", "test@test.com"]); yield* git(cwd, ["config", "user.name", "Test"]); yield* writeTextFile(NodePath.join(cwd, "README.md"), "# test\n"); yield* git(cwd, ["add", "."]); yield* git(cwd, ["commit", "-m", "initial commit"]); });} function buildLargeText(lineCount = 5_000): string { return Array.from({ length: lineCount }, (_, index) => `line ${String(index).padStart(5, "0")}`) .join("\n") .concat("\n");} it.layer(TestLayer)("CheckpointStore.layer", (it) => { describe("isGitRepository", () => { it.effect("returns false when no Git repository is detected", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); const checkpointStore = yield* CheckpointStore.CheckpointStore; expect(yield* checkpointStore.isGitRepository(tmp)).toBe(false); }), ); it.effect("returns true when a Git repository is detected", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const checkpointStore = yield* CheckpointStore.CheckpointStore; expect(yield* checkpointStore.isGitRepository(tmp)).toBe(true); }), ); }); it.effect("detects a nested workspace without its own .git entry", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const fileSystem = yield* FileSystem.FileSystem; const nested = NodePath.join(tmp, "packages", "nested"); yield* fileSystem.makeDirectory(nested, { recursive: true }); const checkpointStore = yield* CheckpointStore.CheckpointStore; expect(yield* checkpointStore.isGitRepository(nested)).toBe(true); }), ); describe("readWorkspaceFingerprint", () => { it.effect("tracks the exact tracked and untracked tree affected by restore", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const checkpointStore = yield* CheckpointStore.CheckpointStore; const initial = yield* checkpointStore.readWorkspaceFingerprint(tmp); yield* writeTextFile(NodePath.join(tmp, "README.md"), "# changed\n"); const trackedChange = yield* checkpointStore.readWorkspaceFingerprint(tmp); yield* writeTextFile(NodePath.join(tmp, "new-file.txt"), "new\n"); const untrackedChange = yield* checkpointStore.readWorkspaceFingerprint(tmp); expect(trackedChange).not.toBe(initial); expect(untrackedChange).not.toBe(trackedChange); expect(yield* git(tmp, ["status", "--short"])).toContain("README.md"); expect(yield* git(tmp, ["status", "--short"])).toContain("new-file.txt"); }), ); it.effect("tracks staged-only changes without modifying the user's index", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const checkpointStore = yield* CheckpointStore.CheckpointStore; const readme = NodePath.join(tmp, "README.md"); const initial = yield* checkpointStore.readWorkspaceFingerprint(tmp); yield* writeTextFile(readme, "# staged\n"); yield* git(tmp, ["add", "README.md"]); yield* writeTextFile(readme, "# test\n"); const stagedBefore = yield* git(tmp, ["diff", "--cached", "--", "README.md"]); const stagedOnly = yield* checkpointStore.readWorkspaceFingerprint(tmp); const stagedAfter = yield* git(tmp, ["diff", "--cached", "--", "README.md"]); expect(stagedOnly).not.toBe(initial); expect(stagedAfter).toBe(stagedBefore); expect(yield* git(tmp, ["diff", "--", "README.md"])).not.toBe(""); const oddPath = "odd\tname\n.txt"; yield* writeTextFile(NodePath.join(tmp, oddPath), "odd staged path\n"); yield* git(tmp, ["add", "--", oddPath]); expect(yield* checkpointStore.readWorkspaceFingerprint(tmp)).not.toBe(stagedOnly); }), ); it.effect("fingerprints an index whose staged listing exceeds the process output cap", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const checkpointStore = yield* CheckpointStore.CheckpointStore; const initial = yield* checkpointStore.readWorkspaceFingerprint(tmp); const fileSystem = yield* FileSystem.FileSystem; yield* Effect.forEach( Array.from({ length: 4_000 }, (_, index) => index), (index) => fileSystem.writeFileString( NodePath.join(tmp, `${String(index).padStart(4, "0")}-${"x".repeat(220)}.txt`), "staged\n", ), { concurrency: 64, discard: true }, ); yield* git(tmp, ["add", "."]); expect(yield* checkpointStore.readWorkspaceFingerprint(tmp)).not.toBe(initial); }), ); it.effect("ignores unmerged index stages outside a nested restore cwd", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const nested = NodePath.join(tmp, "packages", "nested"); const sibling = NodePath.join(tmp, "sibling.txt"); const fileSystem = yield* FileSystem.FileSystem; yield* fileSystem.makeDirectory(nested, { recursive: true }); yield* writeTextFile(NodePath.join(nested, "file.txt"), "nested\n"); yield* writeTextFile(sibling, "base\n"); yield* git(tmp, ["add", "."]); yield* git(tmp, ["commit", "-m", "add scoped files"]); const baseBranch = yield* git(tmp, ["branch", "--show-current"]); yield* git(tmp, ["checkout", "-b", "conflict-side"]); yield* writeTextFile(sibling, "side\n"); yield* git(tmp, ["commit", "-am", "change sibling on side"]); yield* git(tmp, ["checkout", baseBranch]); yield* writeTextFile(sibling, "main\n"); yield* git(tmp, ["commit", "-am", "change sibling on main"]); const checkpointStore = yield* CheckpointStore.CheckpointStore; const beforeConflict = yield* checkpointStore.readWorkspaceFingerprint(nested); const merge = yield* gitAllowNonZero(tmp, ["merge", "conflict-side"]); expect(merge.exitCode).not.toBe(0); expect(yield* git(tmp, ["ls-files", "--unmerged", "--", "sibling.txt"])).not.toBe(""); expect(yield* checkpointStore.readWorkspaceFingerprint(nested)).toBe(beforeConflict); }), ); it.effect("changes when an in-scope conflict stage changes", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const nested = NodePath.join(tmp, "packages", "nested"); const nestedFile = NodePath.join(nested, "file.txt"); const replacement = NodePath.join(tmp, "replacement-stage.txt"); const fileSystem = yield* FileSystem.FileSystem; yield* fileSystem.makeDirectory(nested, { recursive: true }); yield* writeTextFile(nestedFile, "base\n"); yield* git(tmp, ["add", "."]); yield* git(tmp, ["commit", "-m", "add nested conflict file"]); const baseBranch = yield* git(tmp, ["branch", "--show-current"]); yield* git(tmp, ["checkout", "-b", "nested-conflict-side"]); yield* writeTextFile(nestedFile, "side\n"); yield* git(tmp, ["commit", "-am", "change nested file on side"]); yield* git(tmp, ["checkout", baseBranch]); yield* writeTextFile(nestedFile, "main\n"); yield* git(tmp, ["commit", "-am", "change nested file on main"]); expect((yield* gitAllowNonZero(tmp, ["merge", "nested-conflict-side"])).exitCode).not.toBe( 0, ); const checkpointStore = yield* CheckpointStore.CheckpointStore; const beforeStageChange = yield* checkpointStore.readWorkspaceFingerprint(nested); const stages = (yield* git(tmp, ["ls-files", "--stage", "--", "packages/nested/file.txt"])) .split("\n") .filter((line) => line.length > 0); expect(stages).toHaveLength(3); expect(stages.map((line) => line.split(/\s+/, 3)[2])).toEqual(["1", "2", "3"]); yield* writeTextFile(replacement, "replacement stage two\n"); const replacementOid = yield* git(tmp, ["hash-object", "-w", "--", replacement]); yield* gitWithStdin( tmp, ["update-index", "--index-info"], `100644 ${replacementOid} 2\tpackages/nested/file.txt\n`, ); expect(yield* checkpointStore.readWorkspaceFingerprint(nested)).not.toBe(beforeStageChange); }), ); it.effect("limits staged-state fingerprints to a nested restore cwd", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const nested = NodePath.join(tmp, "packages", "nested"); const sibling = NodePath.join(tmp, "sibling.txt"); const nestedFile = NodePath.join(nested, "file.txt"); const fileSystem = yield* FileSystem.FileSystem; yield* fileSystem.makeDirectory(nested, { recursive: true }); yield* writeTextFile(nestedFile, "nested\n"); yield* writeTextFile(sibling, "sibling\n"); yield* git(tmp, ["add", "."]); yield* git(tmp, ["commit", "-m", "nested files"]); const checkpointStore = yield* CheckpointStore.CheckpointStore; const initial = yield* checkpointStore.readWorkspaceFingerprint(nested); yield* writeTextFile(sibling, "sibling staged\n"); yield* git(tmp, ["add", "sibling.txt"]); yield* writeTextFile(sibling, "sibling\n"); expect(yield* checkpointStore.readWorkspaceFingerprint(nested)).toBe(initial); yield* git(tmp, ["commit", "-m", "change committed sibling"]); expect(yield* checkpointStore.readWorkspaceFingerprint(nested)).toBe(initial); yield* writeTextFile(nestedFile, "nested staged\n"); yield* git(tmp, ["add", "packages/nested/file.txt"]); yield* writeTextFile(nestedFile, "nested\n"); expect(yield* checkpointStore.readWorkspaceFingerprint(nested)).not.toBe(initial); }), ); it.effect("fingerprints an absent nested worktree subtree", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const nested = NodePath.join(tmp, "packages", "nested"); const nestedFile = NodePath.join(nested, "file.txt"); const fileSystem = yield* FileSystem.FileSystem; yield* fileSystem.makeDirectory(nested, { recursive: true }); yield* writeTextFile(nestedFile, "nested\n"); yield* git(tmp, ["add", "."]); yield* git(tmp, ["commit", "-m", "add nested subtree"]); const checkpointStore = yield* CheckpointStore.CheckpointStore; const initial = yield* checkpointStore.readWorkspaceFingerprint(nested); yield* fileSystem.remove(nestedFile); expect(yield* checkpointStore.readWorkspaceFingerprint(nested)).not.toBe(initial); }), ); it.effect("fingerprints a nested cwd containing a tab", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const nested = NodePath.join(tmp, "packages", "nested\tworkspace"); const nestedFile = NodePath.join(nested, "file.txt"); const fileSystem = yield* FileSystem.FileSystem; yield* fileSystem.makeDirectory(nested, { recursive: true }); yield* writeTextFile(nestedFile, "nested\n"); yield* git(tmp, ["add", "."]); yield* git(tmp, ["commit", "-m", "add tabbed nested workspace"]); const checkpointStore = yield* CheckpointStore.CheckpointStore; const initial = yield* checkpointStore.readWorkspaceFingerprint(nested); yield* writeTextFile(nestedFile, "changed\n"); expect(yield* checkpointStore.readWorkspaceFingerprint(nested)).not.toBe(initial); }), ); }); describe("diffCheckpoints", () => { it.effect("returns full oversized checkpoint diffs without truncation", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const checkpointStore = yield* CheckpointStore.CheckpointStore; const threadId = ThreadId.make("thread-checkpoint-store"); const fromCheckpointRef = checkpointRefForThreadTurn(threadId, 0); const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: fromCheckpointRef, }); yield* writeTextFile(NodePath.join(tmp, "README.md"), buildLargeText()); yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: toCheckpointRef, }); const diff = yield* checkpointStore.diffCheckpoints({ cwd: tmp, fromCheckpointRef, toCheckpointRef, ignoreWhitespace: true, }); expect(diff).toContain("diff --git"); expect(diff).not.toContain("[truncated]"); expect(diff).toContain("+line 04999"); }), ); it.effect("fails closed when a complete oversized checkpoint diff is required", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const checkpointStore = yield* CheckpointStore.CheckpointStore; const threadId = ThreadId.make("thread-checkpoint-store-complete-output"); const fromCheckpointRef = checkpointRefForThreadTurn(threadId, 0); const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: fromCheckpointRef }); yield* writeTextFile(NodePath.join(tmp, "large.txt"), "x".repeat(10_100_000)); yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: toCheckpointRef }); const error = yield* checkpointStore .diffCheckpoints({ cwd: tmp, fromCheckpointRef, toCheckpointRef, ignoreWhitespace: false, requireCompleteOutput: true, }) .pipe(Effect.flip); expect(error._tag).toBe("VcsProcessOutputLimitError"); }), ); it.effect("keeps a/ and b/ patch prefixes when the repository disables them", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); yield* git(tmp, ["config", "diff.noprefix", "true"]); const checkpointStore = yield* CheckpointStore.CheckpointStore; const threadId = ThreadId.make("thread-checkpoint-store-noprefix"); const fromCheckpointRef = checkpointRefForThreadTurn(threadId, 0); const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: fromCheckpointRef, }); yield* writeTextFile(NodePath.join(tmp, "README.md"), "# changed\n"); yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: toCheckpointRef, }); const diff = yield* checkpointStore.diffCheckpoints({ cwd: tmp, fromCheckpointRef, toCheckpointRef, ignoreWhitespace: false, }); expect(diff).toContain("diff --git a/README.md b/README.md"); }), ); it.effect("can hide indentation churn when changes wrap existing lines", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const checkpointStore = yield* CheckpointStore.CheckpointStore; const threadId = ThreadId.make("thread-checkpoint-store-whitespace"); const fromCheckpointRef = checkpointRefForThreadTurn(threadId, 0); const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); const componentPath = NodePath.join(tmp, "Component.tsx"); yield* writeTextFile( componentPath, [ "export function View() {", " return (", " <section>", " <h1>Title</h1>", " <p>Body</p>", " </section>", " );", "}", "", ].join("\n"), ); yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: fromCheckpointRef, }); yield* writeTextFile( componentPath, [ "export function View() {", " return (", " <section>", " {isReady ? (",Lines 1–500 of 717. Load more
// @effect-diagnostics nodeBuiltinImport:off import * as NodePath from "node:path"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { it } from "@effect/vitest"; import { ThreadId, type VcsError } from "@t3tools/contracts"; import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as PlatformError from "effect/PlatformError"; import * as Scope from "effect/Scope"; import { describe, expect } from "vite-plus/test"; import { checkpointRefForThreadTurn } from "./Utils.ts"; import { parseTurnDiffFilesFromNumstat } from "./Diffs.ts"; import * as CheckpointStore from "./CheckpointStore.ts"; import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; import * as VcsProcess from "../vcs/VcsProcess.ts"; import * as ServerConfig from "../config.ts"; const ServerConfigLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), { prefix: "t3-checkpoint-store-test-", }); const VcsProcessTestLayer = VcsProcess.layer.pipe(Layer.provide(NodeServices.layer)); const VcsDriverTestLayer = VcsDriverRegistry.layer.pipe(Layer.provide(VcsProcessTestLayer)); const CheckpointStoreTestLayer = CheckpointStore.layer.pipe( Layer.provideMerge(VcsDriverTestLayer), Layer.provideMerge(NodeServices.layer), ); const TestLayer = CheckpointStoreTestLayer.pipe( Layer.provideMerge(VcsProcessTestLayer), Layer.provideMerge(VcsDriverTestLayer), Layer.provideMerge(ServerConfigLayer), Layer.provideMerge(NodeServices.layer), ); function makeTmpDir( prefix = "checkpoint-store-test-", ): Effect.Effect<string, PlatformError.PlatformError, FileSystem.FileSystem | Scope.Scope> { return Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; return yield* fileSystem.makeTempDirectoryScoped({ prefix }); }); } function writeTextFile( filePath: string, contents: string, ): Effect.Effect<void, PlatformError.PlatformError, FileSystem.FileSystem> { return Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; yield* fileSystem.writeFileString(filePath, contents); }); } function git( cwd: string, args: ReadonlyArray<string>, ): Effect.Effect<string, VcsError, VcsProcess.VcsProcess> { return Effect.gen(function* () { const process = yield* VcsProcess.VcsProcess; const result = yield* process.run({ operation: "CheckpointStore.test.git", command: "git", cwd, args, timeoutMs: 10_000, }); return result.stdout.trim(); }); } function gitAllowNonZero(cwd: string, args: ReadonlyArray<string>) { return Effect.gen(function* () { const process = yield* VcsProcess.VcsProcess; return yield* process.run({ operation: "CheckpointStore.test.gitAllowNonZero", command: "git", cwd, args, timeoutMs: 10_000, allowNonZeroExit: true, }); }); } function gitWithStdin(cwd: string, args: ReadonlyArray<string>, stdin: string) { return Effect.gen(function* () { const process = yield* VcsProcess.VcsProcess; const result = yield* process.run({ operation: "CheckpointStore.test.gitWithStdin", command: "git", cwd, args, stdin, timeoutMs: 10_000, }); return result.stdout.trim(); }); } function initRepoWithCommit( cwd: string, ): Effect.Effect< void, VcsError | PlatformError.PlatformError, VcsProcess.VcsProcess | FileSystem.FileSystem > { return Effect.gen(function* () { yield* git(cwd, ["init"]); yield* git(cwd, ["config", "user.email", "test@test.com"]); yield* git(cwd, ["config", "user.name", "Test"]); yield* writeTextFile(NodePath.join(cwd, "README.md"), "# test\n"); yield* git(cwd, ["add", "."]); yield* git(cwd, ["commit", "-m", "initial commit"]); }); } function buildLargeText(lineCount = 5_000): string { return Array.from({ length: lineCount }, (_, index) => `line ${String(index).padStart(5, "0")}`) .join("\n") .concat("\n"); } it.layer(TestLayer)("CheckpointStore.layer", (it) => { describe("isGitRepository", () => { it.effect("returns false when no Git repository is detected", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); const checkpointStore = yield* CheckpointStore.CheckpointStore; expect(yield* checkpointStore.isGitRepository(tmp)).toBe(false); }), ); it.effect("returns true when a Git repository is detected", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const checkpointStore = yield* CheckpointStore.CheckpointStore; expect(yield* checkpointStore.isGitRepository(tmp)).toBe(true); }), ); }); it.effect("detects a nested workspace without its own .git entry", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const fileSystem = yield* FileSystem.FileSystem; const nested = NodePath.join(tmp, "packages", "nested"); yield* fileSystem.makeDirectory(nested, { recursive: true }); const checkpointStore = yield* CheckpointStore.CheckpointStore; expect(yield* checkpointStore.isGitRepository(nested)).toBe(true); }), ); describe("readWorkspaceFingerprint", () => { it.effect("tracks the exact tracked and untracked tree affected by restore", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const checkpointStore = yield* CheckpointStore.CheckpointStore; const initial = yield* checkpointStore.readWorkspaceFingerprint(tmp); yield* writeTextFile(NodePath.join(tmp, "README.md"), "# changed\n"); const trackedChange = yield* checkpointStore.readWorkspaceFingerprint(tmp); yield* writeTextFile(NodePath.join(tmp, "new-file.txt"), "new\n"); const untrackedChange = yield* checkpointStore.readWorkspaceFingerprint(tmp); expect(trackedChange).not.toBe(initial); expect(untrackedChange).not.toBe(trackedChange); expect(yield* git(tmp, ["status", "--short"])).toContain("README.md"); expect(yield* git(tmp, ["status", "--short"])).toContain("new-file.txt"); }), ); it.effect("tracks staged-only changes without modifying the user's index", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const checkpointStore = yield* CheckpointStore.CheckpointStore; const readme = NodePath.join(tmp, "README.md"); const initial = yield* checkpointStore.readWorkspaceFingerprint(tmp); yield* writeTextFile(readme, "# staged\n"); yield* git(tmp, ["add", "README.md"]); yield* writeTextFile(readme, "# test\n"); const stagedBefore = yield* git(tmp, ["diff", "--cached", "--", "README.md"]); const stagedOnly = yield* checkpointStore.readWorkspaceFingerprint(tmp); const stagedAfter = yield* git(tmp, ["diff", "--cached", "--", "README.md"]); expect(stagedOnly).not.toBe(initial); expect(stagedAfter).toBe(stagedBefore); expect(yield* git(tmp, ["diff", "--", "README.md"])).not.toBe(""); const oddPath = "odd\tname\n.txt"; yield* writeTextFile(NodePath.join(tmp, oddPath), "odd staged path\n"); yield* git(tmp, ["add", "--", oddPath]); expect(yield* checkpointStore.readWorkspaceFingerprint(tmp)).not.toBe(stagedOnly); }), ); it.effect("fingerprints an index whose staged listing exceeds the process output cap", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const checkpointStore = yield* CheckpointStore.CheckpointStore; const initial = yield* checkpointStore.readWorkspaceFingerprint(tmp); const fileSystem = yield* FileSystem.FileSystem; yield* Effect.forEach( Array.from({ length: 4_000 }, (_, index) => index), (index) => fileSystem.writeFileString( NodePath.join(tmp, `${String(index).padStart(4, "0")}-${"x".repeat(220)}.txt`), "staged\n", ), { concurrency: 64, discard: true }, ); yield* git(tmp, ["add", "."]); expect(yield* checkpointStore.readWorkspaceFingerprint(tmp)).not.toBe(initial); }), ); it.effect("ignores unmerged index stages outside a nested restore cwd", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const nested = NodePath.join(tmp, "packages", "nested"); const sibling = NodePath.join(tmp, "sibling.txt"); const fileSystem = yield* FileSystem.FileSystem; yield* fileSystem.makeDirectory(nested, { recursive: true }); yield* writeTextFile(NodePath.join(nested, "file.txt"), "nested\n"); yield* writeTextFile(sibling, "base\n"); yield* git(tmp, ["add", "."]); yield* git(tmp, ["commit", "-m", "add scoped files"]); const baseBranch = yield* git(tmp, ["branch", "--show-current"]); yield* git(tmp, ["checkout", "-b", "conflict-side"]); yield* writeTextFile(sibling, "side\n"); yield* git(tmp, ["commit", "-am", "change sibling on side"]); yield* git(tmp, ["checkout", baseBranch]); yield* writeTextFile(sibling, "main\n"); yield* git(tmp, ["commit", "-am", "change sibling on main"]); const checkpointStore = yield* CheckpointStore.CheckpointStore; const beforeConflict = yield* checkpointStore.readWorkspaceFingerprint(nested); const merge = yield* gitAllowNonZero(tmp, ["merge", "conflict-side"]); expect(merge.exitCode).not.toBe(0); expect(yield* git(tmp, ["ls-files", "--unmerged", "--", "sibling.txt"])).not.toBe(""); expect(yield* checkpointStore.readWorkspaceFingerprint(nested)).toBe(beforeConflict); }), ); it.effect("changes when an in-scope conflict stage changes", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const nested = NodePath.join(tmp, "packages", "nested"); const nestedFile = NodePath.join(nested, "file.txt"); const replacement = NodePath.join(tmp, "replacement-stage.txt"); const fileSystem = yield* FileSystem.FileSystem; yield* fileSystem.makeDirectory(nested, { recursive: true }); yield* writeTextFile(nestedFile, "base\n"); yield* git(tmp, ["add", "."]); yield* git(tmp, ["commit", "-m", "add nested conflict file"]); const baseBranch = yield* git(tmp, ["branch", "--show-current"]); yield* git(tmp, ["checkout", "-b", "nested-conflict-side"]); yield* writeTextFile(nestedFile, "side\n"); yield* git(tmp, ["commit", "-am", "change nested file on side"]); yield* git(tmp, ["checkout", baseBranch]); yield* writeTextFile(nestedFile, "main\n"); yield* git(tmp, ["commit", "-am", "change nested file on main"]); expect((yield* gitAllowNonZero(tmp, ["merge", "nested-conflict-side"])).exitCode).not.toBe( 0, ); const checkpointStore = yield* CheckpointStore.CheckpointStore; const beforeStageChange = yield* checkpointStore.readWorkspaceFingerprint(nested); const stages = (yield* git(tmp, ["ls-files", "--stage", "--", "packages/nested/file.txt"])) .split("\n") .filter((line) => line.length > 0); expect(stages).toHaveLength(3); expect(stages.map((line) => line.split(/\s+/, 3)[2])).toEqual(["1", "2", "3"]); yield* writeTextFile(replacement, "replacement stage two\n"); const replacementOid = yield* git(tmp, ["hash-object", "-w", "--", replacement]); yield* gitWithStdin( tmp, ["update-index", "--index-info"], `100644 ${replacementOid} 2\tpackages/nested/file.txt\n`, ); expect(yield* checkpointStore.readWorkspaceFingerprint(nested)).not.toBe(beforeStageChange); }), ); it.effect("limits staged-state fingerprints to a nested restore cwd", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const nested = NodePath.join(tmp, "packages", "nested"); const sibling = NodePath.join(tmp, "sibling.txt"); const nestedFile = NodePath.join(nested, "file.txt"); const fileSystem = yield* FileSystem.FileSystem; yield* fileSystem.makeDirectory(nested, { recursive: true }); yield* writeTextFile(nestedFile, "nested\n"); yield* writeTextFile(sibling, "sibling\n"); yield* git(tmp, ["add", "."]); yield* git(tmp, ["commit", "-m", "nested files"]); const checkpointStore = yield* CheckpointStore.CheckpointStore; const initial = yield* checkpointStore.readWorkspaceFingerprint(nested); yield* writeTextFile(sibling, "sibling staged\n"); yield* git(tmp, ["add", "sibling.txt"]); yield* writeTextFile(sibling, "sibling\n"); expect(yield* checkpointStore.readWorkspaceFingerprint(nested)).toBe(initial); yield* git(tmp, ["commit", "-m", "change committed sibling"]); expect(yield* checkpointStore.readWorkspaceFingerprint(nested)).toBe(initial); yield* writeTextFile(nestedFile, "nested staged\n"); yield* git(tmp, ["add", "packages/nested/file.txt"]); yield* writeTextFile(nestedFile, "nested\n"); expect(yield* checkpointStore.readWorkspaceFingerprint(nested)).not.toBe(initial); }), ); it.effect("fingerprints an absent nested worktree subtree", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const nested = NodePath.join(tmp, "packages", "nested"); const nestedFile = NodePath.join(nested, "file.txt"); const fileSystem = yield* FileSystem.FileSystem; yield* fileSystem.makeDirectory(nested, { recursive: true }); yield* writeTextFile(nestedFile, "nested\n"); yield* git(tmp, ["add", "."]); yield* git(tmp, ["commit", "-m", "add nested subtree"]); const checkpointStore = yield* CheckpointStore.CheckpointStore; const initial = yield* checkpointStore.readWorkspaceFingerprint(nested); yield* fileSystem.remove(nestedFile); expect(yield* checkpointStore.readWorkspaceFingerprint(nested)).not.toBe(initial); }), ); it.effect("fingerprints a nested cwd containing a tab", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const nested = NodePath.join(tmp, "packages", "nested\tworkspace"); const nestedFile = NodePath.join(nested, "file.txt"); const fileSystem = yield* FileSystem.FileSystem; yield* fileSystem.makeDirectory(nested, { recursive: true }); yield* writeTextFile(nestedFile, "nested\n"); yield* git(tmp, ["add", "."]); yield* git(tmp, ["commit", "-m", "add tabbed nested workspace"]); const checkpointStore = yield* CheckpointStore.CheckpointStore; const initial = yield* checkpointStore.readWorkspaceFingerprint(nested); yield* writeTextFile(nestedFile, "changed\n"); expect(yield* checkpointStore.readWorkspaceFingerprint(nested)).not.toBe(initial); }), ); }); describe("diffCheckpoints", () => { it.effect("returns full oversized checkpoint diffs without truncation", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const checkpointStore = yield* CheckpointStore.CheckpointStore; const threadId = ThreadId.make("thread-checkpoint-store"); const fromCheckpointRef = checkpointRefForThreadTurn(threadId, 0); const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: fromCheckpointRef, }); yield* writeTextFile(NodePath.join(tmp, "README.md"), buildLargeText()); yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: toCheckpointRef, }); const diff = yield* checkpointStore.diffCheckpoints({ cwd: tmp, fromCheckpointRef, toCheckpointRef, ignoreWhitespace: true, }); expect(diff).toContain("diff --git"); expect(diff).not.toContain("[truncated]"); expect(diff).toContain("+line 04999"); }), ); it.effect("fails closed when a complete oversized checkpoint diff is required", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const checkpointStore = yield* CheckpointStore.CheckpointStore; const threadId = ThreadId.make("thread-checkpoint-store-complete-output"); const fromCheckpointRef = checkpointRefForThreadTurn(threadId, 0); const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: fromCheckpointRef }); yield* writeTextFile(NodePath.join(tmp, "large.txt"), "x".repeat(10_100_000)); yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: toCheckpointRef }); const error = yield* checkpointStore .diffCheckpoints({ cwd: tmp, fromCheckpointRef, toCheckpointRef, ignoreWhitespace: false, requireCompleteOutput: true, }) .pipe(Effect.flip); expect(error._tag).toBe("VcsProcessOutputLimitError"); }), ); it.effect("keeps a/ and b/ patch prefixes when the repository disables them", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); yield* git(tmp, ["config", "diff.noprefix", "true"]); const checkpointStore = yield* CheckpointStore.CheckpointStore; const threadId = ThreadId.make("thread-checkpoint-store-noprefix"); const fromCheckpointRef = checkpointRefForThreadTurn(threadId, 0); const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: fromCheckpointRef, }); yield* writeTextFile(NodePath.join(tmp, "README.md"), "# changed\n"); yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: toCheckpointRef, }); const diff = yield* checkpointStore.diffCheckpoints({ cwd: tmp, fromCheckpointRef, toCheckpointRef, ignoreWhitespace: false, }); expect(diff).toContain("diff --git a/README.md b/README.md"); }), ); it.effect("can hide indentation churn when changes wrap existing lines", () => Effect.gen(function* () { const tmp = yield* makeTmpDir(); yield* initRepoWithCommit(tmp); const checkpointStore = yield* CheckpointStore.CheckpointStore; const threadId = ThreadId.make("thread-checkpoint-store-whitespace"); const fromCheckpointRef = checkpointRefForThreadTurn(threadId, 0); const toCheckpointRef = checkpointRefForThreadTurn(threadId, 1); const componentPath = NodePath.join(tmp, "Component.tsx"); yield* writeTextFile( componentPath, [ "export function View() {", " return (", " <section>", " <h1>Title</h1>", " <p>Body</p>", " </section>", " );", "}", "", ].join("\n"), ); yield* checkpointStore.captureCheckpoint({ cwd: tmp, checkpointRef: fromCheckpointRef, }); yield* writeTextFile( componentPath, [ "export function View() {", " return (", " <section>", " {isReady ? (",