Skip to content
Merged
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
40 changes: 40 additions & 0 deletions apps/server/src/vcs/GitVcsDriver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ it.effect("checkpoint capture skips untracked nested repositories without a comm
`160000 commit ${nestedHead}\tcommitted\n`,
);
assert.deepEqual(yield* fileSystem.readFile(path.join(cwd, ".git", "index")), originalIndex);
assert.isTrue(yield* driver.checkpoints.restoreCheckpoint({ cwd, checkpointRef }));
assert.strictEqual(
yield* fileSystem.readFileString(path.join(cwd, nested, "private.txt")),
"nested\n",
Expand Down Expand Up @@ -1113,6 +1114,8 @@ it.effect("restores empty checkpoints without changing paths outside the workspa
yield* fileSystem.writeFileString(path.join(cwd, "ignored.txt"), "keep\n");
yield* fileSystem.makeDirectory(path.join(cwd, "untracked"));
yield* fileSystem.writeFileString(path.join(cwd, "untracked", "file.txt"), "remove\n");
yield* fileSystem.writeFileString(path.join(cwd, "untracked", ".gitignore"), "hidden.txt\n");
yield* fileSystem.writeFileString(path.join(cwd, "untracked", "hidden.txt"), "remove\n");
assert.isTrue(
yield* driver.checkpoints.restoreCheckpoint({ cwd, checkpointRef, fallbackToHead: false }),
);
Expand All @@ -1130,6 +1133,43 @@ it.effect("restores empty checkpoints without changing paths outside the workspa
});
assert.strictEqual(staged.stdout.trim(), "outside.txt");
}
yield* fileSystem.writeFileString(path.join(cwd, "checkpoint.txt"), "checkpoint\n");
yield* driver.checkpoints.captureCheckpoint({ cwd, checkpointRef });
yield* fileSystem.writeFileString(path.join(cwd, "checkpoint.txt"), "later\n");
let deepest = cwd;
for (let depth = 0; depth < 4; depth++) {
yield* fileSystem.writeFileString(path.join(deepest, ".gitignore"), "d/.gitignore\n");
deepest = path.join(deepest, "d");
yield* fileSystem.makeDirectory(deepest);
}
yield* fileSystem.writeFileString(path.join(deepest, ".gitignore"), "hidden.txt\n");
yield* fileSystem.writeFileString(path.join(deepest, "hidden.txt"), "remove\n");
const incomplete = yield* driver.checkpoints
.restoreCheckpoint({ cwd, checkpointRef, fallbackToHead: false })
.pipe(Effect.flip);
assert.strictEqual(incomplete._tag, "VcsProcessExitError");
assert.strictEqual(
yield* fileSystem.readFileString(path.join(cwd, "checkpoint.txt")),
"checkpoint\n",
);
assert.strictEqual(
(yield* driver.execute({
operation: "test",
cwd,
args: ["diff", "--cached", "--name-only", "--", "."],
})).stdout,
"",
);
if (nested) {
assert.strictEqual(
(yield* driver.execute({
operation: "test",
cwd: root,
args: ["diff", "--cached", "--name-only"],
})).stdout.trim(),
"outside.txt",
);
}
}
}).pipe(Effect.scoped, Effect.provide(layerGitContract)),
);
Expand Down
54 changes: 44 additions & 10 deletions apps/server/src/vcs/GitVcsDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1339,13 +1339,52 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* (
}),
),
);
const resetIndex = Effect.gen(function* () {
// On Windows clean can remove './' and exit 0.
yield* recreateWorkspace;
yield* execute({
operation,
cwd: input.cwd,
args: [...longPaths, "reset", "--quiet", "--", "."],
});
});
yield* recreateWorkspace;
const cleaned = yield* execute({
const clean = execute({
operation,
cwd: input.cwd,
args: [...longPaths, "clean", "-fd", "--", "."],
env: { LC_ALL: "C" },
allowNonZeroExit: true,
});
let cleaned = yield* clean;
// Removing a new .gitignore exposes the files it hid; clean again until nothing is left.
for (
let pass = 1;
pass < 4 && cleaned.exitCode === 0 && /^Removing /m.test(cleaned.stdout);
pass++
) {
yield* recreateWorkspace;
cleaned = yield* clean;
}
if (cleaned.exitCode === 0 && /^Removing /m.test(cleaned.stdout)) {
yield* recreateWorkspace;
const remaining = yield* execute({
operation,
cwd: input.cwd,
args: [...longPaths, "clean", "-dn", "--", "."],
env: { LC_ALL: "C" },
});
if (/^Would remove /m.test(remaining.stdout)) {
yield* resetIndex;
return yield* new VcsProcessExitError({
Comment thread
saphid marked this conversation as resolved.
Resolved
operation,
command: "git clean",
cwd: input.cwd,
exitCode: 0,
detail: "Could not clean the checkpoint workspace: untracked files remain.",
});
}
}
if (cleaned.exitCode !== 0) {
// Git can remove every child, then fail trying to remove './' itself.
const emptiedWorkspace =
Expand All @@ -1355,23 +1394,18 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* (
Effect.map((entries) => entries.length === 0),
Effect.orElseSucceed(() => false),
));
if (!emptiedWorkspace)
if (!emptiedWorkspace) {
yield* resetIndex;
return yield* new VcsProcessExitError({
operation,
command: "git clean",
cwd: input.cwd,
exitCode: cleaned.exitCode,
detail: cleaned.stderr.trim() || "Could not clean the checkpoint workspace.",
});
}
}
// On Windows the same clean can remove './' and exit 0.
yield* recreateWorkspace;

yield* execute({
operation,
cwd: input.cwd,
args: [...longPaths, "reset", "--quiet", "--", "."],
});
yield* resetIndex;

return true;
}),
Expand Down
Loading