fix(worktree): refresh changed discovery metadata

This commit is contained in:
Bohdan Triapitsyn
2026-07-12 00:49:28 +03:00
parent 711289a606
commit fb173067f5
2 changed files with 27 additions and 10 deletions
@@ -153,6 +153,18 @@ describe('worktreeMapsEqual', () => {
expect(worktreeMapsEqual(a, b)).toBe(false); expect(worktreeMapsEqual(a, b)).toBe(false);
}); });
test('returns false when head state changes without a branch change', () => {
const a = new Map([['/repo', [wt('/r/main', '', { headState: 'unborn' })]]]);
const b = new Map([['/repo', [wt('/r/main', '', { headState: 'detached' })]]]);
expect(worktreeMapsEqual(a, b)).toBe(false);
});
test('returns false when discovered display metadata changes', () => {
const a = new Map([['/repo', [wt('/r/main', '', { name: 'old', label: 'old' })]]]);
const b = new Map([['/repo', [wt('/r/main', '', { name: 'new', label: 'new' })]]]);
expect(worktreeMapsEqual(a, b)).toBe(false);
});
test('returns false when paths differ', () => { test('returns false when paths differ', () => {
const a = new Map([['/repo', [wt('/r/main', 'main')]]]); const a = new Map([['/repo', [wt('/r/main', 'main')]]]);
const b = new Map([['/repo', [wt('/r/other', 'main')]]]); const b = new Map([['/repo', [wt('/r/other', 'main')]]]);
@@ -231,7 +231,7 @@ const toCreatePayload = (args: {
/** /**
* Compare two worktree-by-project maps for equality. * Compare two worktree-by-project maps for equality.
* Compares per-element `path` and `branch` (not reference equality) * Compares discovery-owned metadata (not reference equality)
* because readStableProjectWorktrees creates new object instances on * because readStableProjectWorktrees creates new object instances on
* each call, making reference checks always report changed. * each call, making reference checks always report changed.
* *
@@ -246,22 +246,27 @@ const toCreatePayload = (args: {
* flow through `setStoredWorktreeStatus`, which writes a new Map * flow through `setStoredWorktreeStatus`, which writes a new Map
* reference that the persist subscriber picks up directly. * reference that the persist subscriber picks up directly.
* *
* Generic over `T extends { path: string; branch: string }` so the
* helper documents its equality contract at the type level and
* stays reusable for any future map-of-arrays shape that has both
* fields.
*/ */
export const worktreeMapsEqual = <T extends { path: string; branch: string }>( export const worktreeMapsEqual = (
a: Map<string, T[]>, a: Map<string, WorktreeMetadata[]>,
b: Map<string, T[]>, b: Map<string, WorktreeMetadata[]>,
): boolean => { ): boolean => {
if (a.size !== b.size) return false; if (a.size !== b.size) return false;
for (const [key, value] of a) { for (const [key, value] of a) {
const existing = b.get(key); const existing = b.get(key);
if (!existing || existing.length !== value.length) return false; if (!existing || existing.length !== value.length) return false;
for (let i = 0; i < value.length; i++) { for (let i = 0; i < value.length; i++) {
if (value[i].path !== existing[i].path) return false; const next = value[i];
if (value[i].branch !== existing[i].branch) return false; const current = existing[i];
if (next.path !== current.path
|| next.branch !== current.branch
|| next.name !== current.name
|| next.label !== current.label
|| next.projectDirectory !== current.projectDirectory
|| next.worktreeRoot !== current.worktreeRoot
|| next.headState !== current.headState
|| next.worktreeSource !== current.worktreeSource
|| next.source !== current.source) return false;
} }
} }
return true; return true;