feat(FN-4518): complete Step 3 — use rebase range in done-task diff fallback
Fusion-Task-Id: FN-4518 Fusion-Task-Lineage: 48749dec-b2c9-484b-a13e-ffacc1fa9014
This commit is contained in:
@@ -285,6 +285,65 @@ describe("FN-4308 multi-commit done task aggregation", () => {
|
||||
expect(response.body.files[0].path).toBe("healed.ts");
|
||||
});
|
||||
|
||||
it("uses rebaseBaseSha..commitSha fallback range for done task diff", async () => {
|
||||
const store = new MockStore();
|
||||
store.addTask(createTask({ column: "done", mergeDetails: { commitSha: "head-sha", rebaseBaseSha: "base-sha", filesChanged: 2 } }));
|
||||
|
||||
gitResponses({
|
||||
"merge-base --is-ancestor head-sha HEAD": "",
|
||||
"merge-base --is-ancestor base-sha head-sha": "",
|
||||
"diff --name-status -M base-sha..head-sha": "A\tone.ts\nA\ttwo.ts",
|
||||
"diff -M base-sha..head-sha -- one.ts": "+1\n",
|
||||
"diff -M base-sha..head-sha -- two.ts": "+2\n",
|
||||
});
|
||||
|
||||
const app = createServer(store as any);
|
||||
const response = await requestDiff(app);
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.stats.filesChanged).toBe(2);
|
||||
expect(response.body.files.map((f: any) => f.path).sort()).toEqual(["one.ts", "two.ts"]);
|
||||
});
|
||||
|
||||
it("falls back to single-commit range when rebaseBaseSha is unreachable", async () => {
|
||||
const store = new MockStore();
|
||||
store.addTask(createTask({ column: "done", mergeDetails: { commitSha: "head-sha", rebaseBaseSha: "base-sha" } }));
|
||||
|
||||
runGitCommandMock.mockImplementation(async (args: string[]) => {
|
||||
const key = args.join(" ");
|
||||
if (key === "merge-base --is-ancestor head-sha HEAD") return "";
|
||||
if (key === "merge-base --is-ancestor base-sha head-sha") throw new Error("unreachable");
|
||||
if (key === "rev-list --parents -n 1 head-sha") return "head-sha parent";
|
||||
if (key === "diff --name-status -M parent..head-sha") return "A\tfallback.ts";
|
||||
if (key === "diff -M parent..head-sha -- fallback.ts") return "+f\n";
|
||||
throw new Error(`Unexpected git command: ${key}`);
|
||||
});
|
||||
|
||||
const app = createServer(store as any);
|
||||
const response = await requestDiff(app);
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.files).toHaveLength(1);
|
||||
expect(response.body.files[0].path).toBe("fallback.ts");
|
||||
});
|
||||
|
||||
it("uses rebaseBaseSha..commitSha fallback range for done task file-diffs", async () => {
|
||||
const store = new MockStore();
|
||||
store.addTask(createTask({ column: "done", mergeDetails: { commitSha: "head-sha", rebaseBaseSha: "base-sha", filesChanged: 2 } }));
|
||||
|
||||
gitResponses({
|
||||
"merge-base --is-ancestor head-sha HEAD": "",
|
||||
"merge-base --is-ancestor base-sha head-sha": "",
|
||||
"diff --name-status -M base-sha..head-sha": "A\tone.ts\nA\ttwo.ts",
|
||||
"diff -M base-sha..head-sha -- one.ts": "+1\n",
|
||||
"diff -M base-sha..head-sha -- two.ts": "+2\n",
|
||||
});
|
||||
|
||||
const app = createServer(store as any);
|
||||
const response = await requestFileDiffs(app);
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toHaveLength(2);
|
||||
expect(response.body.map((f: any) => f.path).sort()).toEqual(["one.ts", "two.ts"]);
|
||||
});
|
||||
|
||||
it("uses parent-to-parent range for merge commits", async () => {
|
||||
const store = new MockStore();
|
||||
store.addTask(createTask({ column: "done", mergeDetails: { commitSha: "merge-sha" } }));
|
||||
|
||||
@@ -224,7 +224,13 @@ async function isReachableFromHead(sha: string, rootDir: string): Promise<boolea
|
||||
type DoneTaskAggregationTask = {
|
||||
id: string;
|
||||
lineageId?: string | null;
|
||||
mergeDetails?: { commitSha?: string; filesChanged?: number; insertions?: number; deletions?: number } | null;
|
||||
mergeDetails?: {
|
||||
commitSha?: string;
|
||||
rebaseBaseSha?: string;
|
||||
filesChanged?: number;
|
||||
insertions?: number;
|
||||
deletions?: number;
|
||||
} | null;
|
||||
};
|
||||
|
||||
type DoneTaskAggregationStore = {
|
||||
@@ -252,6 +258,19 @@ async function resolveCommitDiffSpec(sha: string, rootDir: string): Promise<
|
||||
return { mode: "single-parent", base: parents[0]!, range: `${parents[0]}..${sha}` };
|
||||
}
|
||||
|
||||
async function resolveRebaseDiffSpec(
|
||||
rebaseBaseSha: string,
|
||||
commitSha: string,
|
||||
rootDir: string,
|
||||
): Promise<{ mode: "rebase-range"; base: string; range: string } | null> {
|
||||
try {
|
||||
await runGitCommand(["merge-base", "--is-ancestor", rebaseBaseSha, commitSha], rootDir, 5000);
|
||||
return { mode: "rebase-range", base: rebaseBaseSha, range: `${rebaseBaseSha}..${commitSha}` };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function parseStatusCode(statusCode: string): DoneTaskFileStatus {
|
||||
if (statusCode.startsWith("A")) return "added";
|
||||
if (statusCode.startsWith("D")) return "deleted";
|
||||
@@ -637,12 +656,28 @@ export function registerSessionDiffRoutes(router: Router, deps: SessionDiffRoute
|
||||
const rootDir = scopedStore.getRootDir();
|
||||
const sha = resolvedMergeSha;
|
||||
|
||||
let diffSpec: Awaited<ReturnType<typeof resolveCommitDiffSpec>>;
|
||||
try {
|
||||
diffSpec = await resolveCommitDiffSpec(sha, rootDir);
|
||||
} catch {
|
||||
res.json({ files: [], stats: { filesChanged: 0, additions: 0, deletions: 0 } });
|
||||
return;
|
||||
let diffSpec: Awaited<ReturnType<typeof resolveCommitDiffSpec>> | { mode: "rebase-range"; base: string; range: string };
|
||||
const rebaseBaseSha = task.mergeDetails?.rebaseBaseSha?.trim();
|
||||
if (rebaseBaseSha) {
|
||||
const rebaseDiffSpec = await resolveRebaseDiffSpec(rebaseBaseSha, sha, rootDir);
|
||||
if (rebaseDiffSpec) {
|
||||
diffSpec = rebaseDiffSpec;
|
||||
} else {
|
||||
console.warn(`[diff] done task ${task.id}: mergeDetails.rebaseBaseSha ${rebaseBaseSha} is not ancestor of ${sha}; falling back to single-commit diff`);
|
||||
try {
|
||||
diffSpec = await resolveCommitDiffSpec(sha, rootDir);
|
||||
} catch {
|
||||
res.json({ files: [], stats: { filesChanged: 0, additions: 0, deletions: 0 } });
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
diffSpec = await resolveCommitDiffSpec(sha, rootDir);
|
||||
} catch {
|
||||
res.json({ files: [], stats: { filesChanged: 0, additions: 0, deletions: 0 } });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const doneFiles = await collectDoneRangeFiles(diffSpec.range, rootDir).catch(() => []);
|
||||
@@ -836,13 +871,29 @@ export function registerSessionDiffRoutes(router: Router, deps: SessionDiffRoute
|
||||
const rootDir = scopedStore.getRootDir();
|
||||
const sha = resolvedMergeSha;
|
||||
|
||||
let diffSpec: Awaited<ReturnType<typeof resolveCommitDiffSpec>>;
|
||||
let diffSpec: Awaited<ReturnType<typeof resolveCommitDiffSpec>> | { mode: "rebase-range"; base: string; range: string };
|
||||
const rebaseBaseSha = task.mergeDetails?.rebaseBaseSha?.trim();
|
||||
|
||||
try {
|
||||
diffSpec = await resolveCommitDiffSpec(sha, rootDir);
|
||||
} catch {
|
||||
res.json([]);
|
||||
return;
|
||||
if (rebaseBaseSha) {
|
||||
const rebaseDiffSpec = await resolveRebaseDiffSpec(rebaseBaseSha, sha, rootDir);
|
||||
if (rebaseDiffSpec) {
|
||||
diffSpec = rebaseDiffSpec;
|
||||
} else {
|
||||
console.warn(`[file-diffs] done task ${task.id}: mergeDetails.rebaseBaseSha ${rebaseBaseSha} is not ancestor of ${sha}; falling back to single-commit diff`);
|
||||
try {
|
||||
diffSpec = await resolveCommitDiffSpec(sha, rootDir);
|
||||
} catch {
|
||||
res.json([]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
diffSpec = await resolveCommitDiffSpec(sha, rootDir);
|
||||
} catch {
|
||||
res.json([]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user