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
66 changes: 65 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 @@ -112,3 +112,67 @@ it.effect("GitVcsDriver forwards execute env to the VCS process", () => {
),
);
});

it.effect("GitVcsDriver flushes checkpoint objects and refs to disk before publishing them", () => {
const observedArgs: ReadonlyArray<string>[] = [];

return Effect.gen(function* () {
const driver = yield* GitVcsDriver.makeVcsDriverShape();

yield* driver.checkpoints.captureCheckpoint({
cwd: "/repo",
checkpointRef: CheckpointRef.make("refs/t3/checkpoints/thread/turn/1"),
});

const writeCommands = ["add", "write-tree", "commit-tree", "update-ref"];
const writes = observedArgs.filter((args) =>
writeCommands.some((command) => args.includes(command)),
);
assert.strictEqual(writes.length, 4);
for (const args of writes) {
const command = args.findIndex((arg) => writeCommands.includes(arg));
for (const setting of ["core.fsync=objects,reference", "core.fsyncMethod=fsync"]) {
const index = args.indexOf(setting);
assert.strictEqual(args[index - 1], "-c", args.join(" "));
assert.isBelow(index, command);
}
}
assert.deepStrictEqual(observedArgs.at(-1), [
"-C",
"/repo",
"-c",
"core.fsync=objects,reference",
"-c",
"core.fsyncMethod=fsync",
"update-ref",
"refs/t3/checkpoints/thread/turn/1",
"commit0000",
]);
}).pipe(
Effect.provide(
Layer.mergeAll(
NodeServices.layer,
Layer.mock(VcsProcess.VcsProcess)({
run: (input) =>
Effect.sync(() => {
observedArgs.push(input.args);
const stdout = input.args.includes("write-tree")
? "tree0000\n"
: input.args.includes("commit-tree")
? "commit0000\n"
: input.args.includes("--git-common-dir")
? ".git\n"
: "";
return {
exitCode: ChildProcessSpawner.ExitCode(0),
stdout,
stderr: "",
stdoutTruncated: false,
stderrTruncated: false,
};
}),
}),
),
),
);
});
19 changes: 15 additions & 4 deletions apps/server/src/vcs/GitVcsDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,17 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* (
return path.isAbsolute(gitCommonDir) ? gitCommonDir : path.resolve(cwd, gitCommonDir);
});

// Git renames loose objects and refs into place without fsync by default, so
// an unclean restart can leave 0-byte files under refs/t3/** that break every
// later fetch and push. Checkpoint writes flush before they are published;
// macOS defaults to writeout-only, which does not reach the disk either.
const durableWrite = [
"-c",
"core.fsync=objects,reference",
"-c",
"core.fsyncMethod=fsync",
] as const;

const checkpoints: VcsDriver.VcsCheckpointOps = {
captureCheckpoint: Effect.fn("GitVcsDriver.checkpoints.captureCheckpoint")(function* (input) {
const operation = "GitVcsDriver.checkpoints.captureCheckpoint";
Expand Down Expand Up @@ -746,14 +757,14 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* (
yield* execute({
operation,
cwd: input.cwd,
args: ["add", "-A", "--", "."],
args: [...durableWrite, "add", "-A", "--", "."],
env: commitEnv,
});

const writeTreeResult = yield* execute({
operation,
cwd: input.cwd,
args: ["write-tree"],
args: [...durableWrite, "write-tree"],
env: commitEnv,
});
const treeOid = writeTreeResult.stdout.trim();
Expand All @@ -771,7 +782,7 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* (
const commitTreeResult = yield* execute({
operation,
cwd: input.cwd,
args: ["commit-tree", treeOid, "-m", message],
args: [...durableWrite, "commit-tree", treeOid, "-m", message],
env: commitEnv,
});
const commitOid = commitTreeResult.stdout.trim();
Expand All @@ -788,7 +799,7 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* (
yield* execute({
operation,
cwd: input.cwd,
args: ["update-ref", input.checkpointRef, commitOid],
args: [...durableWrite, "update-ref", input.checkpointRef, commitOid],
});
}).pipe(Effect.ensuring(cleanupTempIndex));
}),
Expand Down
Loading