Skip to content
Closed
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
71 changes: 71 additions & 0 deletions apps/server/src/vcs/GitVcsDriverCore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1659,6 +1659,77 @@ it.effect("lets a worktree checkout that is still working run past five minutes"
),
);

it.effect.each([{ exitCode: 0 }, { exitCode: 1 }])(
"finishes a noisy worktree checkout using its exit status (%j)",
({ exitCode }) =>
Effect.gen(function* () {
const updates: Array<{ percent: number; completed: number; total: number }> = [];
const outputDrained = yield* Deferred.make<void>();
const noise = `${"x".repeat(1_000_001)}\n`;
const spawner = ChildProcessSpawner.make((command) => {
if (!ChildProcess.isStandardCommand(command) || !command.args.includes("worktree")) {
return Effect.succeed(makeSuccessfulHandle(""));
}
return Effect.succeed(
ChildProcessSpawner.makeHandle({
...makeSuccessfulHandle(""),
exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(exitCode)),
stdout: Stream.encodeText(
Stream.concat(
Stream.make(noise),
Stream.fromEffect(
Deferred.succeed(outputDrained, undefined).pipe(Effect.as("hook finished\n")),
),
),
),
stderr: Stream.encodeText(Stream.make(noise, "Updating files: 100% (10/10), done.\r")),
}),
);
});
const driver = yield* makeGitVcsDriverCore().pipe(
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
);
const path = yield* Path.Path;
const cwd = yield* makeTmpDir();
const worktreePath = path.join(cwd, "worktree");

const result = yield* driver
.createWorktree(
{ cwd, refName: "main", path: worktreePath },
{
progress: {
onCheckoutProgress: (update) =>
Effect.sync(() => {
updates.push(update);
}),
},
},
)
.pipe(Effect.result);

assert.isTrue(yield* Deferred.isDone(outputDrained));
assert.deepStrictEqual(updates, [{ percent: 100, completed: 10, total: 10 }]);
if (exitCode === 0) {
assert.isTrue(Result.isSuccess(result));
if (Result.isSuccess(result)) assert.equal(result.success.worktree.path, worktreePath);
} else {
assert.isTrue(Result.isFailure(result));
if (Result.isFailure(result)) {
assert.equal(result.failure.exitCode, exitCode);
assert.equal(result.failure.detail, "git worktree add failed");
assert.equal(result.failure.stdoutLength, 1_000_000);
assert.equal(result.failure.stderrLength, 1_000_000);
}
}
}).pipe(
Effect.provide(
layerServerConfig.pipe(
Layer.provideMerge(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer)),
),
),
),
);

it.effect("cancels a slow submodule checkout without reporting best-effort success", () =>
Effect.gen(function* () {
const updateStarted = yield* Deferred.make<void>();
Expand Down
7 changes: 7 additions & 0 deletions apps/server/src/vcs/GitVcsDriverCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ interface ExecuteGitOptions {
env?: NodeJS.ProcessEnv | undefined;
maxOutputBytes?: number | undefined;
appendTruncationMarker?: boolean | undefined;
keepLineCallbacksAfterTruncation?: boolean | undefined;
progress?: GitVcsDriver.ExecuteGitProgress | undefined;
writesSharedConfig?: boolean | undefined;
}
Expand Down Expand Up @@ -1170,6 +1171,9 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
...(options.appendTruncationMarker !== undefined
? { appendTruncationMarker: options.appendTruncationMarker }
: {}),
...(options.keepLineCallbacksAfterTruncation !== undefined
? { keepLineCallbacksAfterTruncation: options.keepLineCallbacksAfterTruncation }
: {}),
...(options.progress ? { progress: options.progress } : {}),
...(options.writesSharedConfig ? { writesSharedConfig: true } : {}),
}).pipe(
Expand Down Expand Up @@ -4322,6 +4326,9 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
],
{
fallbackErrorDetail: "git worktree add failed",
// Hook output is diagnostic; reaching the capture limit must not abort the checkout.
appendTruncationMarker: true,
keepLineCallbacksAfterTruncation: true,
// Large checkouts and post-checkout hooks can legitimately run for many
// minutes; a fixed deadline kills them and leaves an orphaned worktree.
timeoutMs: null,
Expand Down
Loading