Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 163 additions & 1 deletion apps/server/src/vcs/GitVcsDriver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as PlatformError from "effect/PlatformError";
import { ChildProcessSpawner } from "effect/unstable/process";
import { assert, it } from "@effect/vitest";

import { GitCommandError } from "@t3tools/contracts";
import { CheckpointRef, GitCommandError } from "@t3tools/contracts";
import * as ServerConfig from "../config.ts";
import * as GitVcsDriver from "./GitVcsDriver.ts";
import * as VcsProcess from "./VcsProcess.ts";
Expand Down Expand Up @@ -65,6 +65,168 @@ runVcsDriverContractSuite<GitVcsDriver.GitVcsDriver, GitContractError>({
},
});

const makeCheckpointFixture = Effect.fn("makeCheckpointFixture")(function* (
driver: Effect.Success<ReturnType<typeof GitVcsDriver.makeVcsDriverShape>>,
cwd: string,
) {
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const git = (args: ReadonlyArray<string>) =>
driver.execute({ operation: "checkpoint-test", cwd, args });
yield* git(["init"]);
yield* git(["config", "user.name", "Test"]);
yield* git(["config", "user.email", "test@test.com"]);
yield* fileSystem.writeFileString(path.join(cwd, "file.txt"), "initial\n");
yield* git(["add", "."]);
yield* git(["commit", "-m", "initial"]);
const checkpointRef = CheckpointRef.make("refs/t3/checkpoints/test");
yield* fileSystem.writeFileString(path.join(cwd, "file.txt"), "staged\n");
yield* git(["add", "."]);
yield* fileSystem.writeFileString(path.join(cwd, "file.txt"), "unstaged\n");
return { git, checkpointRef };
});

it.effect("checkpoint capture does not rerun clean filters for unchanged indexed files", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const driver = yield* GitVcsDriver.makeVcsDriverShape();
const cwd = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-checkpoint-cache-" });
const { git, checkpointRef } = yield* makeCheckpointFixture(driver, cwd);
yield* fileSystem.writeFileString(
path.join(cwd, ".gitattributes"),
"stable.txt filter=probe\n",
);
yield* fileSystem.writeFileString(path.join(cwd, "stable.txt"), "unchanged\n");
yield* fileSystem.writeFileString(
path.join(cwd, ".git", "filter.cjs"),
'require("node:fs").appendFileSync(".git/filter-runs", "read\\n"); process.stdin.pipe(process.stdout);',
);
yield* git(["config", "filter.probe.clean", "node .git/filter.cjs"]);
yield* fileSystem.utimes(path.join(cwd, "stable.txt"), 1_700_000_000, 1_700_000_000);
yield* git(["add", "."]);
yield* git(["commit", "-m", "record stable file"]);
yield* fileSystem.writeFileString(path.join(cwd, ".git", "filter-runs"), "");
yield* fileSystem.writeFileString(path.join(cwd, "file.txt"), "changed\n");
const originalIndex = yield* fileSystem.readFile(path.join(cwd, ".git", "index"));

yield* driver.checkpoints.captureCheckpoint({ cwd, checkpointRef });

assert.strictEqual(yield* fileSystem.readFileString(path.join(cwd, ".git", "filter-runs")), "");
assert.strictEqual((yield* git(["show", `${checkpointRef}:file.txt`])).stdout, "changed\n");
assert.strictEqual((yield* git(["show", `${checkpointRef}:stable.txt`])).stdout, "unchanged\n");
assert.deepEqual(yield* fileSystem.readFile(path.join(cwd, ".git", "index")), originalIndex);
}).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
);

for (const nested of [false, true]) {
for (const indexMode of ["normal", "flags", "split"] as const) {
it.effect(
`checkpoint index reuse preserves two turns (nested=${nested}, index=${indexMode})`,
() =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const driver = yield* GitVcsDriver.makeVcsDriverShape();
const cwd = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-checkpoint-turns-" });
const { git } = yield* makeCheckpointFixture(driver, cwd);
const write = (name: string, contents: string) =>
fileSystem.writeFileString(path.join(cwd, name), contents);
yield* fileSystem.makeDirectory(path.join(cwd, "scope"));
for (const name of [
"scope/staged",
"scope/deleted",
"scope/assumed",
"scope/skipped",
"outside",
]) {
yield* write(name, "original\n");
}
yield* git(["add", "."]);
yield* git(["commit", "-m", "initial scoped files"]);
yield* write("scope/staged", "staged\n");
yield* write("scope/new-deleted", "staged then deleted\n");
yield* write("outside", "staged outside\n");
yield* git(["add", "."]);
if (indexMode === "flags") {
yield* git(["update-index", "--assume-unchanged", "scope/assumed"]);
yield* git(["update-index", "--skip-worktree", "scope/skipped"]);
}
if (indexMode === "split") {
yield* git(["update-index", "--split-index"]);
}
const originalIndex = yield* fileSystem.readFile(path.join(cwd, ".git", "index"));
for (const name of ["scope/staged", "scope/assumed", "scope/skipped", "outside"]) {
yield* write(name, "working\n");
}
yield* write("scope/new", "first\n");
yield* fileSystem.remove(path.join(cwd, "scope/deleted"));
yield* fileSystem.remove(path.join(cwd, "scope/new-deleted"));
const captureCwd = nested ? path.join(cwd, "scope") : cwd;
const first = CheckpointRef.make("refs/t3/checkpoints/turns/1");
const second = CheckpointRef.make("refs/t3/checkpoints/turns/2");
yield* driver.checkpoints.captureCheckpoint({ cwd: captureCwd, checkpointRef: first });
for (const name of ["scope/staged", "scope/assumed", "scope/skipped"]) {
assert.strictEqual((yield* git(["show", `${first}:${name}`])).stdout, "working\n");
}
assert.strictEqual(
(yield* git(["show", `${first}:outside`])).stdout,
nested ? "original\n" : "working\n",
);
const files = (yield* git(["ls-tree", "-r", "--name-only", first])).stdout.split("\n");
assert.notInclude(files, "scope/deleted");
assert.notInclude(files, "scope/new-deleted");
assert.include(files, "scope/new");

yield* write("scope/staged", "second\n");
yield* fileSystem.remove(path.join(cwd, "scope/new"));
yield* write("scope/second", "added in second turn\n");
yield* driver.checkpoints.captureCheckpoint({ cwd: captureCwd, checkpointRef: second });
assert.strictEqual(
(yield* git(["diff", "--name-only", first, second])).stdout,
"scope/new\nscope/second\nscope/staged\n",
);
assert.strictEqual((yield* git(["show", `${second}:scope/staged`])).stdout, "second\n");
assert.strictEqual(
(yield* git(["show", `${second}:scope/second`])).stdout,
"added in second turn\n",
);
assert.deepEqual(
yield* fileSystem.readFile(path.join(cwd, ".git", "index")),
originalIndex,
);
}).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
);
}
}

for (const indexState of ["missing", "invalid"] as const) {
it.effect(`checkpoint capture falls back when the user index is ${indexState}`, () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const driver = yield* GitVcsDriver.makeVcsDriverShape();
const cwd = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-checkpoint-index-" });
const { git, checkpointRef } = yield* makeCheckpointFixture(driver, cwd);
const indexPath = path.join(cwd, ".git", "index");
if (indexState === "missing") {
yield* fileSystem.remove(indexPath);
} else {
yield* fileSystem.writeFileString(indexPath, "invalid index");
}

yield* driver.checkpoints.captureCheckpoint({ cwd, checkpointRef });

assert.strictEqual((yield* git(["show", `${checkpointRef}:file.txt`])).stdout, "unstaged\n");
if (indexState === "missing") {
assert.isFalse(yield* fileSystem.exists(indexPath));
} else {
assert.strictEqual(yield* fileSystem.readFileString(indexPath), "invalid index");
}
}).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
);
}

it.effect("GitVcsDriver forwards execute env to the VCS process", () => {
let observedEnv: NodeJS.ProcessEnv | undefined;
let observedAppendTruncationMarker: boolean | undefined;
Expand Down
38 changes: 32 additions & 6 deletions apps/server/src/vcs/GitVcsDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -735,12 +735,38 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* (
yield* Effect.gen(function* () {
const headExists = yield* hasHeadCommit(input.cwd);
if (headExists) {
yield* execute({
operation,
cwd: input.cwd,
args: ["read-tree", "HEAD"],
env: commitEnv,
});
const reusedIndex = yield* Effect.gen(function* () {
const indexPath = yield* execute({
operation,
cwd: input.cwd,
args: ["rev-parse", "--path-format=absolute", "--git-path", "index"],
});
yield* fileSystem.copyFile(indexPath.stdout.trim(), tempIndexPath);
// Retain stat data only where the copied index already matches HEAD.
yield* execute({
operation,
cwd: input.cwd,
args: ["-c", "core.fsmonitor=false", "read-tree", "--reset", "HEAD"],
env: commitEnv,
});
const entries = yield* execute({
operation,
cwd: input.cwd,
args: ["ls-files", "-v"],
env: commitEnv,
maxOutputBytes: WORKSPACE_FILES_MAX_OUTPUT_BYTES,
});
// A fresh index must still capture assume-unchanged/skip-worktree files.
return !entries.stdoutTruncated && !/^[a-zS] /m.test(entries.stdout);
}).pipe(Effect.orElseSucceed(() => false));
if (!reusedIndex) {
yield* execute({
operation,
cwd: input.cwd,
args: ["read-tree", "HEAD"],
env: commitEnv,
});
}
}

yield* execute({
Expand Down
Loading