FN-5666: exclude base commit from done-task diff ranges

Prevent done-task diff endpoints from counting base-commit-only lineage as task-owned changes.

- add base-boundary handling in done-task merge SHA resolution so `baseCommitSha` is excluded by default and only consulted for boundary checks
- short-circuit `/diff` and `/file-diffs` responses to empty results when resolved done-task SHA is at or below `baseCommitSha`
- export and use an ancestry helper to detect base-boundary cases, while preserving normal and rebase range behavior
- extend done-task diff route tests with FN-5666 cases for no-op merge SHA, lineage association at base commit, normal post-base commits, and rebase ranges
- add a patch changeset for `@runfusion/fusion`

Files changed:
 .changeset/fn-5666-base-commit-exclusive.md        |   5 +
 .../src/__tests__/routes-diff-done-tasks.test.ts   | 217 ++++++++++++++++++++-
 .../src/routes/register-session-diff-routes.ts     |  57 +++++-
 3 files changed, 273 insertions(+), 6 deletions(-)

Fusion-Task-Id: FN-5666
Fusion-Task-Lineage: 782f9875-856a-4d31-9505-0bcd638c9c29
This commit is contained in:
gsxdsm
2026-05-29 10:13:54 -07:00
parent 32c877a16a
commit dbb0804490
3 changed files with 273 additions and 6 deletions

View File

@@ -378,17 +378,43 @@ async function resolveAuditCommitSha(taskId: string, scopedStore: DoneTaskAggreg
return undefined;
}
async function resolveDoneTaskMergeSha(task: DoneTaskAggregationTask, scopedStore: DoneTaskAggregationStore): Promise<string | undefined> {
export async function isAtOrBelowTaskBase(sha: string, baseCommitSha: string, rootDir: string): Promise<boolean> {
if (sha === baseCommitSha) return true;
try {
await runGitCommand(["merge-base", "--is-ancestor", sha, baseCommitSha], rootDir, 5000);
return true;
} catch {
return false;
}
}
async function resolveDoneTaskMergeSha(
task: DoneTaskAggregationTask & { baseCommitSha?: string | null },
scopedStore: DoneTaskAggregationStore,
options?: { includeBaseCommitSha?: boolean },
): Promise<string | undefined> {
const rootDir = scopedStore.getRootDir();
const baseCommitSha = task.baseCommitSha?.trim();
const includeBaseCommitSha = options?.includeBaseCommitSha === true;
const existing = task.mergeDetails?.commitSha?.trim();
if (existing) return existing;
if (existing) {
if (!includeBaseCommitSha && baseCommitSha && existing === baseCommitSha) return undefined;
return existing;
}
const auditSha = await resolveAuditCommitSha(task.id, scopedStore);
if (auditSha) return auditSha;
if (auditSha) {
if (!includeBaseCommitSha && baseCommitSha && auditSha === baseCommitSha) return undefined;
return auditSha;
}
if (!task.lineageId) return undefined;
const associations = await scopedStore.getTaskCommitAssociationsByLineageId(task.lineageId);
for (const association of associations) {
if (association.commitSha && (await isReachableFromHead(association.commitSha, scopedStore.getRootDir()))) {
if (!association.commitSha) continue;
if (!includeBaseCommitSha && baseCommitSha && association.commitSha === baseCommitSha) continue;
if (await isReachableFromHead(association.commitSha, rootDir)) {
return association.commitSha;
}
}
@@ -670,7 +696,19 @@ export function registerSessionDiffRoutes(router: Router, deps: SessionDiffRoute
}
if (task.column === "done") {
const mergeShaForBaseBoundary = await resolveDoneTaskMergeSha(task, scopedStore, { includeBaseCommitSha: true });
const resolvedMergeSha = await resolveDoneTaskMergeSha(task, scopedStore);
if (mergeShaForBaseBoundary && task.baseCommitSha) {
// FN-5666: `git diff A..B` is exclusive of A, and baseCommitSha is the
// task fork point, so when resolved SHA is at/below base the task has
// no owned changes to display.
const atOrBelowBase = await isAtOrBelowTaskBase(mergeShaForBaseBoundary, task.baseCommitSha, scopedStore.getRootDir());
if (atOrBelowBase) {
res.json({ files: [], stats: { filesChanged: 0, additions: 0, deletions: 0 } });
return;
}
}
const doneTaskForDiff = resolvedMergeSha
? {
...task,
@@ -931,7 +969,18 @@ export function registerSessionDiffRoutes(router: Router, deps: SessionDiffRoute
}
if (task.column === "done") {
const mergeShaForBaseBoundary = await resolveDoneTaskMergeSha(task, scopedStore, { includeBaseCommitSha: true });
const resolvedMergeSha = await resolveDoneTaskMergeSha(task, scopedStore);
if (mergeShaForBaseBoundary && task.baseCommitSha) {
// FN-5666: baseCommitSha is the branch fork point and must remain
// exclusive in per-task display diffs.
const atOrBelowBase = await isAtOrBelowTaskBase(mergeShaForBaseBoundary, task.baseCommitSha, scopedStore.getRootDir());
if (atOrBelowBase) {
res.json([]);
return;
}
}
const doneTaskForDiff = resolvedMergeSha
? {
...task,