Skip to content
Open
70 changes: 70 additions & 0 deletions apps/server/src/checkpointing/CheckpointStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,76 @@ it.layer(TestLayer)("CheckpointStore.layer", (it) => {
);
});

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

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

describe("diffCheckpoints", () => {
it.effect("returns full oversized checkpoint diffs without truncation", () =>
Effect.gen(function* () {
Expand Down
13 changes: 13 additions & 0 deletions apps/server/src/checkpointing/CheckpointStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ export class CheckpointStore extends Context.Service<
/** Check whether cwd is inside a Git worktree. */
readonly isGitRepository: (cwd: string) => Effect.Effect<boolean, CheckpointStoreError>;

/** Hash the tracked and untracked workspace state affected by restore. */
readonly readWorkspaceFingerprint: (cwd: string) => Effect.Effect<string, CheckpointStoreError>;

/**
* Capture a checkpoint commit and store it at the provided checkpoint ref.
*
Expand Down Expand Up @@ -119,6 +122,15 @@ export const make = Effect.gen(function* () {
.detect({ cwd, requestedKind: "git" })
.pipe(Effect.map((repository) => repository !== null));

const readWorkspaceFingerprint: CheckpointStore["Service"]["readWorkspaceFingerprint"] =
Effect.fn("readWorkspaceFingerprint")(function* (cwd) {
const checkpoints = yield* resolveCheckpoints(
"CheckpointStore.readWorkspaceFingerprint",
cwd,
);
return yield* checkpoints.readWorkspaceFingerprint(cwd);
});

const captureCheckpoint: CheckpointStore["Service"]["captureCheckpoint"] = Effect.fn(
"captureCheckpoint",
)(function* (input) {
Expand Down Expand Up @@ -159,6 +171,7 @@ export const make = Effect.gen(function* () {

return CheckpointStore.of({
isGitRepository,
readWorkspaceFingerprint,
captureCheckpoint,
hasCheckpointRef,
restoreCheckpoint,
Expand Down
Loading
Loading