Skip to content

Commit b94c8b1

Browse files
authored
fix: include untracked files in diff patch for working-tree runs (#45)
* fix: include untracked files in diff patch for working-tree runs * perf: parallelize untracked file diff generation
1 parent 9d62659 commit b94c8b1

2 files changed

Lines changed: 51 additions & 4 deletions

File tree

packages/cli/src/git.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ export function getUntrackedFiles(): string[] {
146146
return out ? out.split("\n") : [];
147147
}
148148

149-
function hasStringStdout(err: unknown): err is { stdout: string } {
149+
export function hasStringStdout(err: unknown): err is { stdout: string } {
150150
return (
151151
typeof err === "object" && err !== null && "stdout" in err && typeof err.stdout === "string"
152152
);

packages/cli/src/routes/diff.ts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,49 @@ import { eq } from "drizzle-orm";
77
import type { StageDb } from "../db/client.js";
88
import type { ChapterRunRow } from "../db/schema/chapter-run.js";
99
import { chapterRun } from "../db/schema/index.js";
10-
import { buildDiffArgs } from "../git.js";
10+
import { buildDiffArgs, hasStringStdout } from "../git.js";
1111
import { SCOPE_KIND, WORKING_TREE_REF } from "../schema.js";
1212
import type { Route } from "../server.js";
1313
import { writeJson } from "./json.js";
1414

1515
const execFileAsync = promisify(execFile);
1616

1717
const MAX_FILE_BYTES = 5 * 1024 * 1024;
18+
const MAX_DIFF_BYTES = 50 * 1024 * 1024;
19+
20+
async function buildUntrackedPatch(cwd: string): Promise<string> {
21+
const { stdout } = await execFileAsync("git", ["ls-files", "--others", "--exclude-standard"], {
22+
cwd,
23+
encoding: "utf8",
24+
});
25+
const files = stdout.trim() ? stdout.trim().split("\n") : [];
26+
if (files.length === 0) return "";
27+
28+
const patches = await Promise.all(
29+
files.map(async (file) => {
30+
try {
31+
await execFileAsync(
32+
"git",
33+
[
34+
"diff",
35+
"--no-index",
36+
"--no-color",
37+
"--src-prefix=a/",
38+
"--dst-prefix=b/",
39+
"--",
40+
"/dev/null",
41+
file,
42+
],
43+
{ cwd, encoding: "utf8", maxBuffer: MAX_DIFF_BYTES },
44+
);
45+
return "";
46+
} catch (err: unknown) {
47+
return hasStringStdout(err) ? err.stdout : "";
48+
}
49+
}),
50+
);
51+
return patches.filter(Boolean).join("\n");
52+
}
1853

1954
export function diffRoutes(db: StageDb): Route[] {
2055
return [
@@ -47,11 +82,23 @@ export function diffRoutes(db: StageDb): Route[] {
4782
run.scopeKind === SCOPE_KIND.COMMITTED ? "private, max-age=300" : "no-store";
4883

4984
try {
50-
const { stdout: patch } = await execFileAsync("git", args, {
85+
const { stdout: trackedPatch } = await execFileAsync("git", args, {
5186
cwd: repoRoot,
5287
encoding: "utf8",
53-
maxBuffer: 50 * 1024 * 1024,
88+
maxBuffer: MAX_DIFF_BYTES,
5489
});
90+
91+
let patch = trackedPatch;
92+
if (
93+
run.scopeKind === SCOPE_KIND.WORKING_TREE &&
94+
run.workingTreeRef === WORKING_TREE_REF.WORK
95+
) {
96+
const untrackedPatch = await buildUntrackedPatch(repoRoot);
97+
if (untrackedPatch) {
98+
patch = patch ? `${patch}\n${untrackedPatch}` : untrackedPatch;
99+
}
100+
}
101+
55102
const fileContents = await buildFileContents(run, repoRoot, patch);
56103
const body: DiffResponse = { patch, fileContents };
57104
res.writeHead(200, {

0 commit comments

Comments
 (0)