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
54 changes: 54 additions & 0 deletions apps/server/src/vcs/GitVcsDriverCore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5929,6 +5929,60 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => {
});

describe("commit context", () => {
it.effect("reads history through a chain of shared clones", () =>
Effect.gen(function* () {
const root = yield* makeTmpDir();
const fileSystem = yield* FileSystem.FileSystem;
const driver = yield* GitVcsDriver.GitVcsDriver;
const seed = `${root}/clone-0`;
yield* fileSystem.makeDirectory(seed);
yield* initRepoWithCommit(seed);
let previous = seed;
for (let index = 1; index <= 6; index += 1) {
const next = `${root}/clone-${index}`;
yield* git(root, ["clone", "--quiet", "--shared", previous, next]);
previous = next;
}
// Six shared clones deep is within Git's alternate limit, not one level deeper.
yield* writeTextFile(previous, "README.md", "changed\n");
const chained = yield* driver.prepareCommitContext(previous, ["README.md"]);
assert.include(chained?.stagedSummary ?? "", "M\tREADME.md");
assert.include(chained?.stagedPatch ?? "", "-# test");
}),
);

it.effect("leaves no objects behind when no commit follows, in a linked worktree too", () =>
Effect.gen(function* () {
const repo = yield* makeTmpDir();
yield* initRepoWithCommit(repo);
const linked = `${repo}/linked`;
yield* git(repo, ["worktree", "add", "--quiet", linked]);
const driver = yield* GitVcsDriver.GitVcsDriver;

for (const cwd of [repo, linked]) {
yield* writeTextFile(cwd, "README.md", `changed in ${cwd}\n`);
yield* writeTextFile(cwd, "draft.txt", `drafted in ${cwd}\n`);
const blobs = [
yield* git(cwd, ["hash-object", "draft.txt"]),
yield* git(cwd, ["hash-object", "README.md"]),
];
const context = yield* driver.prepareCommitContext(cwd);
assert.include(context?.stagedPatch ?? "", `+drafted in ${cwd}`);
assert.include(context?.stagedPatch ?? "", "-# test");
for (const blob of blobs) {
const probe = yield* driver.execute({
operation: "GitVcsDriver.test.git",
cwd,
args: ["cat-file", "-e", blob],
allowNonZeroExit: true,
timeoutMs: 10_000,
});
assert.notEqual(probe.exitCode, 0, `${blob} stayed in the repository`);
}
}
}),
);

it.effect("prepares source patches without running textconv", () =>
Effect.gen(function* () {
const cwd = yield* makeTmpDir();
Expand Down
38 changes: 33 additions & 5 deletions apps/server/src/vcs/GitVcsDriverCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2526,17 +2526,45 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
Effect.fn("prepareCommitContext")(function* (cwd, filePaths, options) {
return yield* Effect.scoped(
Effect.gen(function* () {
const indexValue = yield* runGitStdout(
"GitVcsDriver.prepareCommitContext.indexPath",
const [indexValue = "", objectsValue = ""] = (yield* runGitStdout(
"GitVcsDriver.prepareCommitContext.gitPaths",
cwd,
["rev-parse", "--git-path", "index"],
);
["rev-parse", "--git-path", "index", "--git-path", "objects"],
)).split("\n");
const indexPath = path.resolve(cwd, indexValue.trim());
const directory = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3code-commit-index-",
});
const tempIndexPath = path.join(directory, "index");
const env = { GIT_INDEX_FILE: tempIndexPath } satisfies NodeJS.ProcessEnv;
// Staging writes blobs; put them in the temp directory so a draft leaves nothing behind.
// List every object directory Git resolved (nested and environment alternates) flat,
// since nesting the repository's objects one level deeper can pass Git's depth limit.
const objectDirectories = yield* runGitStdout(
"GitVcsDriver.prepareCommitContext.alternates",
cwd,
["count-objects", "-v"],
);
// Git prints an entry C-quoted when it needs quoting; quote the rest the same way, so a
// path holding the list separator stays one entry (Git 2.11.1+).
const quoted = (entry: string) =>
entry.startsWith('"') ? entry : `"${entry.replace(/[\\"]/g, "\\$&")}"`;
const alternates = [
path.resolve(cwd, objectsValue.trim()),
...objectDirectories
.split("\n")
.flatMap((line) =>
line.startsWith("alternate: ") ? [line.slice("alternate: ".length)] : [],
),
];
const tempObjectsPath = path.join(directory, "objects");
yield* fileSystem.makeDirectory(tempObjectsPath);
const env = {
GIT_INDEX_FILE: tempIndexPath,
GIT_OBJECT_DIRECTORY: tempObjectsPath,
GIT_ALTERNATE_OBJECT_DIRECTORIES: alternates
.map(quoted)
.join(hostPlatform === "win32" ? ";" : ":"),
} satisfies NodeJS.ProcessEnv;
const config = [
"-c",
"core.splitIndex=false",
Expand Down
Loading