Skip to content

Commit 8b44f42

Browse files
authored
fix(web): load selected diff metadata before file patches (#30567)
1 parent bb4afa5 commit 8b44f42

10 files changed

Lines changed: 583 additions & 104 deletions

File tree

‎apps/server/src/vcs/GitVcsDriverCore.test.ts‎

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2170,6 +2170,14 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => {
21702170
.sources[0]!;
21712171
assert.equal(rootSource.baseRef, emptyTree);
21722172
assert.include(rootSource.diff, "+# test");
2173+
const rootMetadata = (yield* driver.getReviewDiffPreview({
2174+
cwd: upstream,
2175+
commitSha: root,
2176+
metadataOnly: true,
2177+
})).sources[0]!;
2178+
assert.deepStrictEqual(rootMetadata.files, rootSource.files);
2179+
assert.isTrue(rootMetadata.patchesDeferred);
2180+
assert.isEmpty(rootMetadata.diff);
21732181

21742182
yield* writeTextFile(upstream, "README.md", "# boundary\n");
21752183
yield* git(upstream, ["commit", "-am", "boundary"]);
@@ -2396,6 +2404,78 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => {
23962404
}),
23972405
);
23982406

2407+
it.effect(
2408+
"returns an incomplete selected tracked preview while untracked discovery is held",
2409+
() =>
2410+
Effect.gen(function* () {
2411+
const cwd = yield* makeTmpDir();
2412+
yield* initRepoWithCommit(cwd);
2413+
yield* writeTextFile(cwd, "README.md", "changed\n");
2414+
yield* writeTextFile(cwd, "new.txt", "untracked\n");
2415+
const started = yield* Deferred.make<void>();
2416+
const release = yield* Deferred.make<void>();
2417+
let untrackedReads = 0;
2418+
const delegate = yield* ChildProcessSpawner.ChildProcessSpawner;
2419+
const spawner = ChildProcessSpawner.make((command) =>
2420+
Effect.gen(function* () {
2421+
if (ChildProcess.isStandardCommand(command) && command.args.includes("--others")) {
2422+
assert.equal(
2423+
++untrackedReads,
2424+
1,
2425+
"tracked preview must not enumerate untracked files",
2426+
);
2427+
yield* Deferred.succeed(started, undefined);
2428+
yield* Deferred.await(release);
2429+
}
2430+
return yield* delegate.spawn(command);
2431+
}),
2432+
);
2433+
const driver = yield* makeGitVcsDriverCore().pipe(
2434+
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
2435+
Effect.provide(layerServerConfig),
2436+
);
2437+
const full = yield* driver
2438+
.getReviewDiffPreview({ cwd, sourceKind: "working-tree" })
2439+
.pipe(Effect.forkChild);
2440+
yield* Deferred.await(started);
2441+
yield* Effect.gen(function* () {
2442+
const preview = yield* driver.getReviewDiffPreview({
2443+
cwd,
2444+
sourceKind: "working-tree",
2445+
trackedOnly: true,
2446+
});
2447+
assert.isTrue(preview.incomplete);
2448+
assert.deepStrictEqual(
2449+
preview.sources.map((source) => source.kind),
2450+
["working-tree"],
2451+
);
2452+
assert.include(preview.sources[0]!.diff, "+changed");
2453+
assert.notInclude(preview.sources[0]!.diff, "untracked");
2454+
const metadata = yield* driver.getReviewDiffPreview({
2455+
cwd,
2456+
sourceKind: "working-tree",
2457+
trackedOnly: true,
2458+
metadataOnly: true,
2459+
});
2460+
assert.isTrue(metadata.incomplete);
2461+
assert.deepStrictEqual(metadata.sources[0]!.files, preview.sources[0]!.files);
2462+
assert.isTrue(metadata.sources[0]!.patchesDeferred);
2463+
assert.isEmpty(metadata.sources[0]!.diff);
2464+
}).pipe(Effect.ensuring(Deferred.succeed(release, undefined)));
2465+
const complete = yield* Fiber.join(full);
2466+
assert.isUndefined(complete.incomplete);
2467+
assert.include(complete.sources[0]!.diff, "+untracked");
2468+
yield* writeTextFile(cwd, "README.md", "edited again\n");
2469+
const refreshed = yield* driver.getReviewDiffPreview({
2470+
cwd,
2471+
baseRef: "missing-base",
2472+
sourceKind: "working-tree",
2473+
trackedOnly: true,
2474+
});
2475+
assert.include(refreshed.sources[0]!.diff, "+edited again");
2476+
}),
2477+
);
2478+
23992479
it.effect("propagates patch failures instead of reporting an empty complete diff", () =>
24002480
Effect.gen(function* () {
24012481
const cwd = yield* makeTmpDir();
@@ -2416,6 +2496,16 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => {
24162496
if (Result.isFailure(result)) {
24172497
assert.equal(result.failure.operation, "GitVcsDriver.getReviewDiffPreview.patch");
24182498
}
2499+
const metadata = yield* driver.getReviewDiffPreview({
2500+
cwd,
2501+
sourceKind: "working-tree",
2502+
metadataOnly: true,
2503+
});
2504+
assert.isTrue(metadata.sources[0]!.patchesDeferred);
2505+
assert.isEmpty(metadata.sources[0]!.diff);
2506+
assert.deepStrictEqual(metadata.sources[0]!.files, [
2507+
{ path: "README.md", previousPath: null, additions: 1, deletions: 1 },
2508+
]);
24192509
}),
24202510
);
24212511

@@ -2597,9 +2687,17 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => {
25972687
);
25982688
yield* writeTextFile(cwd, "README.md", "# tracked change\n");
25992689

2600-
const preview = yield* driver.getReviewDiffPreview({ cwd, ignoreWhitespace: false });
2690+
const preview = yield* driver.getReviewDiffPreview({
2691+
cwd,
2692+
ignoreWhitespace: false,
2693+
metadataOnly: true,
2694+
});
26012695
const diff = preview.sources.find((source) => source.kind === "working-tree")?.diff ?? "";
26022696

2697+
const source = preview.sources.find((source) => source.kind === "working-tree")!;
2698+
assert.isUndefined(source.patchesDeferred);
2699+
assert.isUndefined(source.files);
2700+
assert.isTrue(source.truncated);
26032701
assert.include(diff, "-# test");
26042702
assert.include(diff, "+# tracked change");
26052703
}),
@@ -2734,6 +2832,22 @@ it.layer(layerTest)("GitVcsDriver core integration", (it) => {
27342832
assert.isTrue(dirty.truncated);
27352833
assert.notInclude(branch.diff, "z-last.txt");
27362834
for (const source of [branch, dirty]) {
2835+
const selected = yield* driver.getReviewDiffPreview({
2836+
cwd,
2837+
baseRef: initialBranch,
2838+
sourceKind: source.kind === "working-tree" ? "working-tree" : "branch-range",
2839+
});
2840+
assert.deepStrictEqual(selected.sources, [source]);
2841+
const metadata = (yield* driver.getReviewDiffPreview({
2842+
cwd,
2843+
baseRef: initialBranch,
2844+
sourceKind: source.kind === "working-tree" ? "working-tree" : "branch-range",
2845+
metadataOnly: true,
2846+
})).sources[0]!;
2847+
assert.deepStrictEqual(metadata.files, source.files);
2848+
assert.isTrue(metadata.patchesDeferred);
2849+
assert.isFalse(metadata.truncated);
2850+
assert.isEmpty(metadata.diff);
27372851
for (const file of source.files ?? []) {
27382852
const individual = yield* driver.getReviewDiffPreview({
27392853
cwd,

‎apps/server/src/vcs/GitVcsDriverCore.ts‎

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3409,10 +3409,11 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
34093409
const patchLimit = input.file
34103410
? REVIEW_DIFF_FILE_MAX_OUTPUT_BYTES
34113411
: REVIEW_DIFF_PATCH_MAX_OUTPUT_BYTES;
3412+
const sourceKind = input.file?.sourceKind ?? input.sourceKind;
34123413
const needsCurrentBranch =
34133414
input.commitSha === undefined &&
34143415
input.file?.sourceKind !== "commit" &&
3415-
input.file?.sourceKind !== "working-tree";
3416+
sourceKind !== "working-tree";
34163417
const repository = yield* (
34173418
needsCurrentBranch
34183419
? resolveRepositoryPathsUncached(input.cwd)
@@ -3463,12 +3464,15 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
34633464
{ maxOutputBytes: REVIEW_METADATA_MAX_OUTPUT_BYTES },
34643465
);
34653466
const files = parseReviewNumstat(stat.stdout);
3466-
const patch = yield* executeGit(
3467-
"GitVcsDriver.getReviewDiffPreview.commitPatch",
3468-
cwd,
3469-
[...diffArgs, "--patch", commit.parent, commit.sha, "--", ...pathArgs],
3470-
{ maxOutputBytes: patchLimit, appendTruncationMarker: true },
3471-
);
3467+
const patch =
3468+
input.metadataOnly && !input.file
3469+
? { stdout: "", stdoutTruncated: false }
3470+
: yield* executeGit(
3471+
"GitVcsDriver.getReviewDiffPreview.commitPatch",
3472+
cwd,
3473+
[...diffArgs, "--patch", commit.parent, commit.sha, "--", ...pathArgs],
3474+
{ maxOutputBytes: patchLimit, appendTruncationMarker: true },
3475+
);
34723476
const diffHash = yield* hashDiff(patch.stdout, files);
34733477
return {
34743478
cwd: input.cwd,
@@ -3485,14 +3489,15 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
34853489
files,
34863490
diffHash,
34873491
truncated: patch.stdoutTruncated,
3492+
...(input.metadataOnly && !input.file ? { patchesDeferred: true } : {}),
34883493
},
34893494
],
34903495
};
34913496
}
34923497

3493-
const dirtyRef = input.file?.sourceKind === "branch-range" ? null : "HEAD";
3498+
const dirtyRef = sourceKind === "branch-range" ? null : "HEAD";
34943499
const review =
3495-
input.file?.sourceKind === "working-tree"
3500+
sourceKind === "working-tree"
34963501
? { baseRef: input.baseRef ?? null, mergeBase: null }
34973502
: yield* resolveReviewMergeBase(cwd, repository.currentBranch, input.baseRef);
34983503

@@ -3534,10 +3539,13 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
35343539
const readTrackedDiff = Effect.fn("GitVcsDriver.getReviewDiffPreview.tracked")(function* (
35353540
ref: string | null,
35363541
env?: NodeJS.ProcessEnv,
3542+
metadataOnly = false,
35373543
) {
35383544
if (ref === null) return { stdout: "", stdoutTruncated: false, files: [] };
35393545
const stat = yield* readStats(ref, env);
3540-
if (stat.files.length === 0) return { stdout: "", stdoutTruncated: false, files: [] };
3546+
if (metadataOnly || stat.files.length === 0) {
3547+
return { stdout: "", stdoutTruncated: false, files: stat.files };
3548+
}
35413549
const patch = yield* executeGit(
35423550
"GitVcsDriver.getReviewDiffPreview.patch",
35433551
cwd,
@@ -3547,16 +3555,19 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
35473555
return { ...patch, files: stat.files };
35483556
});
35493557
const [dirtyTrackedResult, baseResult] = yield* Effect.gen(function* () {
3550-
const untracked = yield* prepareUntrackedReviewIndex(cwd, pathArgs, input.file?.path);
3558+
const untracked = input.trackedOnly
3559+
? { env: undefined }
3560+
: yield* prepareUntrackedReviewIndex(cwd, pathArgs, input.file?.path);
3561+
const metadataOnly = input.metadataOnly === true && !input.file && untracked !== null;
35513562
const [dirty, base] =
35523563
review.mergeBase === dirtyRef
3553-
? yield* readTrackedDiff(dirtyRef, untracked?.env).pipe(
3564+
? yield* readTrackedDiff(dirtyRef, untracked?.env, metadataOnly).pipe(
35543565
Effect.map((result) => [result, result] as const),
35553566
)
35563567
: yield* Effect.all(
35573568
[
3558-
readTrackedDiff(dirtyRef, untracked?.env),
3559-
readTrackedDiff(review.mergeBase, untracked?.env),
3569+
readTrackedDiff(dirtyRef, untracked?.env, metadataOnly),
3570+
readTrackedDiff(review.mergeBase, untracked?.env, metadataOnly),
35603571
],
35613572
{ concurrency: 2 },
35623573
);
@@ -3585,6 +3596,9 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
35853596
...(dirtyFiles === undefined ? {} : { files: dirtyFiles }),
35863597
diffHash: dirtyDiffHash,
35873598
truncated: dirtyTrackedResult.stdoutTruncated,
3599+
...(input.metadataOnly && !input.file && dirtyFiles !== undefined
3600+
? { patchesDeferred: true }
3601+
: {}),
35883602
},
35893603
{
35903604
id: "branch-range",
@@ -3596,13 +3610,19 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
35963610
...(baseFiles === undefined ? {} : { files: baseFiles }),
35973611
diffHash: baseDiffHash,
35983612
truncated: baseResult.stdoutTruncated,
3613+
...(input.metadataOnly && !input.file && baseFiles !== undefined
3614+
? { patchesDeferred: true }
3615+
: {}),
35993616
},
36003617
];
36013618

36023619
return {
36033620
cwd: input.cwd,
36043621
generatedAt: yield* DateTime.now,
3605-
sources,
3622+
sources: input.sourceKind
3623+
? sources.filter((source) => source.kind === input.sourceKind)
3624+
: sources,
3625+
...(input.trackedOnly ? { incomplete: true } : {}),
36063626
};
36073627
});
36083628

0 commit comments

Comments
 (0)