feat(FN-4527): complete Step 1 — harden done-task diff stats fallback
Fusion-Task-Id: FN-4527 Fusion-Task-Lineage: de75c453-dc86-43d2-9cc5-50b20ee7014d
This commit is contained in:
@@ -353,6 +353,96 @@ describe("FN-4524 done-task diff stats", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("uses live shortstat for done tasks even when mergeDetails stats are stale", async () => {
|
||||
const rootDir = mkdtempSync(join(tmpdir(), "fn-4527-done-stale-merge-details-"));
|
||||
|
||||
try {
|
||||
git(rootDir, "init", "-b", "main");
|
||||
git(rootDir, "config", "user.email", "fusion@example.com");
|
||||
git(rootDir, "config", "user.name", "Fusion");
|
||||
|
||||
commitFile(rootDir, "base.ts", "export const base = 1;\n", "base");
|
||||
|
||||
git(rootDir, "checkout", "-b", "task-branch");
|
||||
commitFile(rootDir, "one.ts", "export const one = 1;\n", "task one");
|
||||
commitFile(rootDir, "two.ts", "export const two = 2;\n", "task two");
|
||||
|
||||
git(rootDir, "checkout", "main");
|
||||
git(rootDir, "merge", "--squash", "task-branch");
|
||||
git(rootDir, "commit", "-m", "squash merge task");
|
||||
const squashCommit = git(rootDir, "rev-parse", "HEAD");
|
||||
const expected = shortstatForShow(rootDir, squashCommit);
|
||||
expect(expected).toEqual({ filesChanged: 2, additions: 2, deletions: 0 });
|
||||
|
||||
const store = new RealGitStore(rootDir);
|
||||
store.addTask({
|
||||
id: "FN-4524",
|
||||
title: "stale mergeDetails test",
|
||||
description: "stale mergeDetails test",
|
||||
column: "done",
|
||||
dependencies: [],
|
||||
steps: [],
|
||||
currentStep: 0,
|
||||
log: [],
|
||||
createdAt: "2026-05-14T00:00:00.000Z",
|
||||
updatedAt: "2026-05-14T00:00:00.000Z",
|
||||
columnMovedAt: "2026-05-14T00:00:00.000Z",
|
||||
baseBranch: "main",
|
||||
mergeDetails: {
|
||||
commitSha: squashCommit,
|
||||
filesChanged: 108,
|
||||
insertions: 999,
|
||||
deletions: 999,
|
||||
},
|
||||
} as Task);
|
||||
|
||||
const response = await getDoneDiff(store);
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.stats).toEqual(expected);
|
||||
expect(response.body.stats).not.toEqual({ filesChanged: 108, additions: 999, deletions: 999 });
|
||||
} finally {
|
||||
rmSync(rootDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("returns zero stats when merge SHA is unresolvable even if mergeDetails has stored counts", async () => {
|
||||
const rootDir = mkdtempSync(join(tmpdir(), "fn-4527-done-no-merge-sha-"));
|
||||
|
||||
try {
|
||||
git(rootDir, "init", "-b", "main");
|
||||
git(rootDir, "config", "user.email", "fusion@example.com");
|
||||
git(rootDir, "config", "user.name", "Fusion");
|
||||
commitFile(rootDir, "base.ts", "export const base = 1;\n", "base");
|
||||
|
||||
const store = new RealGitStore(rootDir);
|
||||
store.addTask({
|
||||
id: "FN-4524",
|
||||
title: "missing sha test",
|
||||
description: "missing sha test",
|
||||
column: "done",
|
||||
dependencies: [],
|
||||
steps: [],
|
||||
currentStep: 0,
|
||||
log: [],
|
||||
createdAt: "2026-05-14T00:00:00.000Z",
|
||||
updatedAt: "2026-05-14T00:00:00.000Z",
|
||||
columnMovedAt: "2026-05-14T00:00:00.000Z",
|
||||
baseBranch: "main",
|
||||
mergeDetails: {
|
||||
filesChanged: 108,
|
||||
insertions: 999,
|
||||
deletions: 999,
|
||||
},
|
||||
} as Task);
|
||||
|
||||
const response = await getDoneDiff(store);
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.stats).toEqual({ filesChanged: 0, additions: 0, deletions: 0 });
|
||||
} finally {
|
||||
rmSync(rootDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("matches shortstat when hunks include ++/-- content lines", async () => {
|
||||
const rootDir = mkdtempSync(join(tmpdir(), "fn-4524-done-plusminus-"));
|
||||
|
||||
|
||||
@@ -197,6 +197,17 @@ type AggregatedDoneTaskFile = {
|
||||
patch: string;
|
||||
};
|
||||
|
||||
function parseGitShortstat(output: string): { filesChanged: number; additions: number; deletions: number } {
|
||||
const filesMatch = output.match(/(\d+) files? changed/);
|
||||
const additionsMatch = output.match(/(\d+) insertions?\(\+\)/);
|
||||
const deletionsMatch = output.match(/(\d+) deletions?\(-\)/);
|
||||
return {
|
||||
filesChanged: filesMatch ? Number(filesMatch[1]) : 0,
|
||||
additions: additionsMatch ? Number(additionsMatch[1]) : 0,
|
||||
deletions: deletionsMatch ? Number(deletionsMatch[1]) : 0,
|
||||
};
|
||||
}
|
||||
|
||||
function statusPriority(status: DoneTaskFileStatus): number {
|
||||
switch (status) {
|
||||
case "added":
|
||||
@@ -641,13 +652,14 @@ export function registerSessionDiffRoutes(router: Router, deps: SessionDiffRoute
|
||||
}
|
||||
|
||||
if (!resolvedMergeSha) {
|
||||
const md = task.mergeDetails;
|
||||
// FN-4527: mergeDetails summary stats can be stale after post-merge
|
||||
// rebase-and-push (FN-4526). Never echo stored values from /diff.
|
||||
res.json({
|
||||
files: [],
|
||||
stats: {
|
||||
filesChanged: md?.filesChanged ?? 0,
|
||||
additions: md?.insertions ?? 0,
|
||||
deletions: md?.deletions ?? 0,
|
||||
filesChanged: 0,
|
||||
additions: 0,
|
||||
deletions: 0,
|
||||
},
|
||||
});
|
||||
return;
|
||||
@@ -697,17 +709,13 @@ export function registerSessionDiffRoutes(router: Router, deps: SessionDiffRoute
|
||||
return;
|
||||
}
|
||||
|
||||
const patch = await runGitCommand(["diff", diffSpec.range], rootDir, 10000).catch(() => "");
|
||||
const filesChanged = (await runGitCommand(["diff", "--name-only", diffSpec.range], rootDir, 10000)
|
||||
.then((output) => output.split("\n").filter(Boolean).length)
|
||||
.catch(() => 0));
|
||||
const shortstat = await runGitCommand(["show", "--shortstat", "--format=", resolvedMergeSha], rootDir, 10000)
|
||||
.then((output) => parseGitShortstat(output))
|
||||
.catch(() => ({ filesChanged: 0, additions: 0, deletions: 0 }));
|
||||
|
||||
res.json({
|
||||
files: [],
|
||||
stats: {
|
||||
filesChanged,
|
||||
...countPatchLines(patch),
|
||||
},
|
||||
stats: shortstat,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user