fix(worktrees): skip Git sweeps for missing repo folders

This commit is contained in:
Brennan Benson
2026-10-14 12:25:55 -07:00
parent b0308f24ef
commit 82ccf05a4d
2 changed files with 52 additions and 35 deletions
+38 -34
View File
@@ -1,9 +1,16 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const { listWorktreeGraphMock, listWorktreesMock, listWorktreesStrictMock } = vi.hoisted(() => ({
listWorktreeGraphMock: vi.fn(),
listWorktreesMock: vi.fn(),
listWorktreesStrictMock: vi.fn()
const { listWorktreeGraphMock, listWorktreesMock, listWorktreesStrictMock, statMock } = vi.hoisted(
() => ({
listWorktreeGraphMock: vi.fn(),
listWorktreesMock: vi.fn(),
listWorktreesStrictMock: vi.fn(),
statMock: vi.fn()
})
)
vi.mock('./local-worktree-filesystem', () => ({
getLocalWorktreePathAccess: () => ({ statPath: statMock })
}))
vi.mock('./git/worktree', () => ({
@@ -23,11 +30,32 @@ import {
} from './repo-worktrees'
import { WorktreeCatalogUnavailableError } from '../shared/worktree/worktree-catalog-availability'
const testGitRepo = {
id: 'repo-1',
path: '/workspace/repo',
displayName: 'repo',
badgeColor: '#000',
addedAt: 0,
kind: 'git' as const
}
describe('repo-worktrees', () => {
beforeEach(() => {
listWorktreeGraphMock.mockReset()
listWorktreesMock.mockReset()
listWorktreesStrictMock.mockReset()
statMock.mockReset().mockResolvedValue({})
})
it('skips Git on each missing-folder scan and resumes when the folder returns', async () => {
statMock.mockRejectedValueOnce(Object.assign(new Error('missing'), { code: 'ENOENT' }))
statMock.mockRejectedValueOnce(Object.assign(new Error('missing'), { code: 'ENOENT' }))
expect(await listRepoWorktreesForDetectedScan(testGitRepo)).toEqual([])
expect(await listRepoWorktreesForDetectedScan(testGitRepo)).toEqual([])
expect(listWorktreesStrictMock).not.toHaveBeenCalled()
listWorktreesStrictMock.mockResolvedValueOnce([])
await listRepoWorktreesForDetectedScan(testGitRepo)
expect(listWorktreesStrictMock).toHaveBeenCalledOnce()
})
it('creates a stable synthetic worktree for folder repos', () => {
@@ -100,17 +128,7 @@ describe('repo-worktrees', () => {
])
const signal = new AbortController().signal
const result = await listRepoWorktrees(
{
id: 'repo-1',
path: '/workspace/repo',
displayName: 'repo',
badgeColor: '#000',
addedAt: 0,
kind: 'git'
},
{ signal }
)
const result = await listRepoWorktrees(testGitRepo, { signal })
expect(listWorktreesMock).toHaveBeenCalledWith('/workspace/repo', { signal })
expect(result).toHaveLength(1)
@@ -122,14 +140,7 @@ describe('repo-worktrees', () => {
{ path: '/workspace/repo', head: 'abc', branch: '', isBare: false, isMainWorktree: true }
])
const result = await listRepoWorktreeGraph({
id: 'repo-1',
path: '/workspace/repo',
displayName: 'repo',
badgeColor: '#000',
addedAt: 0,
kind: 'git'
})
const result = await listRepoWorktreeGraph(testGitRepo)
expect(listWorktreeGraphMock).toHaveBeenCalledWith('/workspace/repo')
expect(listWorktreesMock).not.toHaveBeenCalled()
@@ -165,17 +176,10 @@ describe('repo-worktrees', () => {
])
const signal = new AbortController().signal
const result = await listLocalRepoWorktreesStrict(
{
id: 'repo-1',
path: '/workspace/repo',
displayName: 'repo',
badgeColor: '#000',
addedAt: 0,
kind: 'git'
},
{ signal, wslDistro: 'Ubuntu' }
)
const result = await listLocalRepoWorktreesStrict(testGitRepo, {
signal,
wslDistro: 'Ubuntu'
})
expect(listWorktreesStrictMock).toHaveBeenCalledWith('/workspace/repo', {
signal,
+14 -1
View File
@@ -12,6 +12,8 @@ import { getRepoExecutionHostId, LOCAL_EXECUTION_HOST_ID } from '../shared/execu
import { resolveGitRouteForHost } from './providers/execution-host-provider-dispatch'
import { areWorktreePathsEqual } from './ipc/worktree-logic'
import { WorktreeCatalogUnavailableError } from '../shared/worktree/worktree-catalog-availability'
import { isDefinitiveAbsence } from '../shared/definitive-filesystem-absence'
import { getLocalWorktreePathAccess } from './local-worktree-filesystem'
type LocalRepoWorktreeListOptions = {
wslDistro?: string
@@ -61,7 +63,18 @@ export async function listRepoWorktreesForDetectedScan(
repo: Repo,
options?: LocalRepoWorktreeListOptions
): Promise<GitWorktreeInfo[]> {
return listRoutedRepoWorktrees(repo, options, listWorktreesSharedStrictAllowingTrueEmpty)
return listRoutedRepoWorktrees(repo, options, async (repoPath, localOptions) => {
try {
// The local route runs on the execution host, including the server for SSH repositories.
await getLocalWorktreePathAccess(localOptions).statPath(repoPath)
} catch (error) {
if (isDefinitiveAbsence(error)) {
return []
}
throw error
}
return listWorktreesSharedStrictAllowingTrueEmpty(repoPath, localOptions)
})
}
async function listRoutedRepoWorktrees(