From b8593cf8fd1a7d02c809e6ee615e3d8983f2fc60 Mon Sep 17 00:00:00 2001 From: Alex Lucero Date: Sun, 31 May 2026 08:49:20 +0200 Subject: [PATCH] fix: compare linked repo status by path --- apps/daemon/src/repo-changes.ts | 18 ++++++++-- apps/daemon/tests/repo-changes.test.ts | 48 ++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/apps/daemon/src/repo-changes.ts b/apps/daemon/src/repo-changes.ts index 4f3cc5477..d0d121d81 100644 --- a/apps/daemon/src/repo-changes.ts +++ b/apps/daemon/src/repo-changes.ts @@ -77,8 +77,11 @@ export function summarizeLinkedRepoChanges( const beforeByPath = new Map(before.linkedDirs.map((dir) => [dir.path, dir])); const linkedDirs: LinkedRepoChangeDirectorySummary[] = after.linkedDirs.map((dir) => { const baseline = beforeByPath.get(dir.path); - const baselineLines = new Set(baseline?.statusLines ?? []); - const newStatusLineCount = dir.statusLines.filter((line) => !baselineLines.has(line)).length; + const baselinePaths = new Set((baseline?.statusLines ?? []).flatMap(statusLinePaths)); + const newStatusLineCount = dir.statusLines.filter((line) => { + const paths = statusLinePaths(line); + return paths.length === 0 || paths.every((path) => !baselinePaths.has(path)); + }).length; const preexistingChangeCount = Math.min( dir.statusLineCount, Math.max(0, dir.statusLineCount - newStatusLineCount), @@ -184,6 +187,17 @@ function splitLines(value: string): string[] { .filter((line) => line.trim().length > 0); } +function statusLinePaths(line: string): string[] { + const value = line.length > 3 ? line.slice(3).trim() : line.trim(); + if (!value) return []; + const renameSeparator = ' -> '; + if (!value.includes(renameSeparator)) return [value]; + return value + .split(renameSeparator) + .map((part) => part.trim()) + .filter(Boolean); +} + function errorMessage(err: unknown): string { if (!err) return 'Unknown git error.'; if (err instanceof Error && err.message.trim()) return err.message.trim(); diff --git a/apps/daemon/tests/repo-changes.test.ts b/apps/daemon/tests/repo-changes.test.ts index d7b7b1535..2359dce73 100644 --- a/apps/daemon/tests/repo-changes.test.ts +++ b/apps/daemon/tests/repo-changes.test.ts @@ -90,6 +90,54 @@ describe('linked repo change summaries', () => { }); }); + it('treats status-only transitions on the same path as pre-existing changes', () => { + const before: LinkedRepoSnapshot = { + generatedAt: 1, + linkedDirs: [ + { + path: '/repo', + status: 'changed', + branch: 'main', + headSha: 'abc1234', + statusLines: [' M src/app.ts'], + statusLineCount: 1, + untrackedFileCount: 0, + diffStat: 'src/app.ts | 2 ++', + error: null, + }, + ], + }; + const after: LinkedRepoSnapshot = { + generatedAt: 2, + linkedDirs: [ + { + path: '/repo', + status: 'changed', + branch: 'main', + headSha: 'abc1234', + statusLines: ['M src/app.ts'], + statusLineCount: 1, + untrackedFileCount: 0, + diffStat: 'src/app.ts | 2 ++', + error: null, + }, + ], + }; + + const summary = summarizeLinkedRepoChanges(before, after); + + expect(summary).toMatchObject({ + changedFileCount: 1, + newStatusLineCount: 0, + preexistingChangeCount: 1, + }); + expect(summary.linkedDirs[0]).toMatchObject({ + changedFileCount: 1, + newStatusLineCount: 0, + preexistingChangeCount: 1, + }); + }); + it('reports a linked dir as not_git when git cannot read it as a repository', async () => { const runGit: RunGit = async () => { throw new Error('fatal: not a git repository');