fix(merger,dashboard): handle empty-squash commitSha to keep card and Changes tab in sync
When a branch contained commits already on main (duplicate cherry-picks), the merger's local squash collapsed to an empty commit. The merger then recorded that empty commit's SHA on mergeDetails.commitSha. The actual content landed later on main as a different SHA via PR merge, but the task kept pointing at the orphaned empty commit. Symptom: TaskCard showed "N files changed" (falling back to task.modifiedFiles), but the Changes tab in the modal showed nothing because the API hit `git diff sha^..sha` on the empty commit and returned no files. Two fixes: 1. merger.ts: detect empty squash commits and skip storing commitSha, logging clearly. recoverInterruptedMergingTasks → findLandedTaskCommit already exists to backfill the right SHA when the real commit lands; a missing commitSha is a known fallback path the UI already handles. 2. TaskChangesTab.tsx: when the API returns no files for a done task, fall back to task.modifiedFiles (paths only, no patches) with a clear note. Mirrors the existing 3-tier fallback in TaskCard.tsx:1090-1124 so card and modal always agree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2292,8 +2292,25 @@ export async function aiMergeTask(
|
||||
deletions = deletionsMatch ? Number.parseInt(deletionsMatch[1], 10) : 0;
|
||||
} catch { /* non-fatal */ }
|
||||
|
||||
// Guard: if the squash collapsed to an empty commit, recording its SHA
|
||||
// misleads every consumer (TaskChangesTab shows "no changes" even though
|
||||
// modifiedFiles is non-empty). Real cause: the branch contained commits
|
||||
// already on main (duplicate cherry-picks), and conflict resolution
|
||||
// dropped them. The actual landing typically happens later via PR merge
|
||||
// on a different SHA — let recoverInterruptedMergingTasks /
|
||||
// findLandedTaskCommit populate the right SHA when that lands. Until
|
||||
// then, store mergeDetails without commitSha so the UI falls back to
|
||||
// task.modifiedFiles instead of a broken diff.
|
||||
const isEmptyCommit = filesChanged === 0;
|
||||
const recordedSha = isEmptyCommit ? undefined : commitSha;
|
||||
if (isEmptyCommit) {
|
||||
mergerLog.warn(
|
||||
`${taskId}: local squash produced an empty commit (${commitSha?.slice(0, 8)}) — branch likely contained dupes of main. Skipping commitSha; recovery will backfill when real commit lands.`,
|
||||
);
|
||||
}
|
||||
|
||||
const mergeDetails: MergeDetails = {
|
||||
commitSha,
|
||||
commitSha: recordedSha,
|
||||
filesChanged,
|
||||
insertions,
|
||||
deletions,
|
||||
@@ -2307,7 +2324,7 @@ export async function aiMergeTask(
|
||||
};
|
||||
|
||||
await store.updateTask(taskId, { mergeDetails });
|
||||
mergerLog.log(`${taskId}: merge details stored (commitSha: ${commitSha?.slice(0, 8)})`);
|
||||
mergerLog.log(`${taskId}: merge details stored (commitSha: ${recordedSha?.slice(0, 8) ?? "<deferred>"})`);
|
||||
} catch (err: any) {
|
||||
mergerLog.warn(`${taskId}: failed to collect/store merge details: ${err.message}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user