mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 00:03:20 +00:00
Fix aborted git status scans being mistaken for completed empty results
- An aborted scan/numstat pass now always rejects instead of silently resolving, so a cancelled request can't look like a valid empty status. - Stop clearing the line-stats cache key on abort, since an aborted pass never wrote to it — clearing was evicting a concurrent scan's healthy snapshot and forcing a redundant numstat recompute.
This commit is contained in:
@@ -315,6 +315,8 @@ async function runGetStatus(
|
||||
didHitLimit = stoppedEarly
|
||||
statusSucceeded = true
|
||||
} catch (error) {
|
||||
// Why: an aborted scan must reject, not resolve — swallowing here would let
|
||||
// a cancelled request be mistaken for a completed (empty) status result.
|
||||
if (options.signal?.aborted) {
|
||||
throw error
|
||||
}
|
||||
@@ -557,6 +559,9 @@ async function runNumstat(
|
||||
)
|
||||
return parseNumstat(stdout)
|
||||
} catch (error) {
|
||||
// Why: an aborted pass must reject so a cancelled scan is never treated as
|
||||
// a completed one; only a genuine (non-abort) numstat failure degrades to
|
||||
// uncounted rows below.
|
||||
if (options.signal?.aborted) {
|
||||
throw error
|
||||
}
|
||||
|
||||
@@ -168,6 +168,8 @@ export async function getStatusOp(
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Why: an aborted scan must reject, not resolve — swallowing here would let
|
||||
// a cancelled request be mistaken for a completed (empty) status result.
|
||||
if (options.signal?.aborted) {
|
||||
throw error
|
||||
}
|
||||
@@ -218,6 +220,9 @@ async function runNumstat(
|
||||
)
|
||||
return parseNumstat(stdout)
|
||||
} catch (error) {
|
||||
// Why: an aborted pass must reject so a cancelled scan is never treated as
|
||||
// a completed one; only a genuine (non-abort) numstat failure degrades to
|
||||
// uncounted rows below.
|
||||
if (signal?.aborted) {
|
||||
throw error
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
clearGitStatusLineStatsCache,
|
||||
clearGitStatusLineStatsCacheKey,
|
||||
GIT_STATUS_LINE_STATS_CACHE_MAX_AGE_MS,
|
||||
reuseOrRecomputeGitStatusLineStats,
|
||||
storeGitStatusLineStats
|
||||
} from './git-status-line-stats-cache'
|
||||
|
||||
@@ -109,6 +110,33 @@ describe('git status line stats cache', () => {
|
||||
expect(entries[0]?.added).toBe(3)
|
||||
})
|
||||
|
||||
it('keeps a prior good snapshot reusable when an overlapping recompute is aborted', async () => {
|
||||
// A completed scan stored healthy counts.
|
||||
storeGitStatusLineStats({ cacheKey: 'native\0/repo', head: 'head-1', entries: cachedEntries })
|
||||
|
||||
// A later scan begins and recomputes but is aborted before it can store.
|
||||
const abortedWrite = beginGitStatusLineStatsCacheWrite('native\0/repo')
|
||||
await reuseOrRecomputeGitStatusLineStats({
|
||||
cacheKey: 'native\0/repo',
|
||||
head: 'head-1',
|
||||
entries: [{ path: 'src/a.ts', status: 'modified', area: 'unstaged' }],
|
||||
writeToken: abortedWrite,
|
||||
reuse: false,
|
||||
isAborted: () => true,
|
||||
recompute: async () => true
|
||||
})
|
||||
|
||||
// The aborted pass persisted nothing, so it must not evict the healthy
|
||||
// snapshot — a following safety read still reuses the stored counts.
|
||||
const entries: { path: string; status: string; area: string; added?: number }[] = [
|
||||
{ path: 'src/a.ts', status: 'modified', area: 'unstaged' }
|
||||
]
|
||||
expect(
|
||||
applyCachedGitStatusLineStats({ cacheKey: 'native\0/repo', head: 'head-1', entries })
|
||||
).toBe(true)
|
||||
expect(entries[0]?.added).toBe(3)
|
||||
})
|
||||
|
||||
it('rejects stale cache writes and writes started before invalidation', () => {
|
||||
const olderWrite = beginGitStatusLineStatsCacheWrite('native\0/repo')
|
||||
const latestWrite = beginGitStatusLineStatsCacheWrite('native\0/repo')
|
||||
|
||||
@@ -196,7 +196,9 @@ export async function reuseOrRecomputeGitStatusLineStats(input: {
|
||||
}
|
||||
const complete = await input.recompute()
|
||||
if (input.isAborted()) {
|
||||
clearGitStatusLineStatsCacheKey(input.cacheKey, input.writeToken)
|
||||
// Why: an aborted pass never reached storeGitStatusLineStats, so there is
|
||||
// nothing partial to undo; clearing here would instead evict a concurrent
|
||||
// scan's healthy snapshot and force a redundant numstat on the next read.
|
||||
return
|
||||
}
|
||||
if (!complete) {
|
||||
|
||||
Reference in New Issue
Block a user