mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 16:02:56 +00:00
* perf(git): cache sparse-checkout annotation on worktree listing `git worktree list` never reports sparse-checkout state, so every listing paid a per-worktree fs.stat + config read to detect it -- measured at ~9x the cost of the `git worktree list` call it decorates on a 1000-worktree repo. Cache the result per worktree path, invalidated by the existing worktree-change invalidator registry plus explicit remove/move hooks, with a 5-minute reconcile window bounding the one unwitnessed edge case (external `git sparse-checkout` toggle with extensions.worktreeConfig off), matching the precedent already accepted in readRepoWorktreeAdminFingerprint. * perf(git): normalize/scope sparse-checkout cache keys, add SWR Address independent-review follow-ups on the sparse-checkout annotation cache (#17859): - Extract canonicalWorktreePath() from areWorktreePathsEqual and key/invalidate the cache through it on both read and write, closing the disclosed path-spelling P2 outright instead of leaving it as a residual risk. - Scope cache entries and clears by repo path (derived from the invalidator registry's repoId via a store lookup, falling back to a full clear when the repo can't be resolved), so churn in one repo no longer evicts a sibling repo's warm cache. - Replace the hard 5-minute cutoff with stale-while-revalidate: past the window, callers get the cached value immediately while a deduplicated background probe corrects it and, on a flip, drives the existing worktrees-changed notification -- collapsing visible staleness from the full window to one refresh cycle at zero added listing latency. Also corrects a stale claim in the original PR description: newer Git does emit a `sparse` porcelain line (which annotateSparseCheckoutStatus already skips), but Orca's Git 2.25 compatibility baseline predates it, so the fallback detection this caches remains necessary. * fix(git): stop background sparse-checkout revalidation resurrecting invalidated entries Readiness-loop finding: a stale-while-revalidate probe in flight when a worktree is removed/moved (or a repo's cache is cleared) would still write its result back afterward, resurrecting an entry that was deliberately dropped. Guard the write with a presence check so an invalidated key stays absent until the next real read. * fix(git): identity-check the sparse-checkout SWR write-back guard The has()/presence guard from the previous commit only proved some entry existed at the key, not that it was the one this revalidation started from. A worktree removed and re-created at the same path while a background re-detect was in flight would repopulate the key with a fresh cold read, and the stale in-flight result would then overwrite it -- exactly the race greptile (P1) and pullfrog both flagged as still open. Compare the map's current entry by reference to the entry captured when the revalidation began; a mismatch means something else (invalidate, clear, or a fresh cold read) replaced it, and the stale result must not be written back. Added a regression test that fails against the old has() guard and passes with the identity check: invalidate and repopulate the key with a different value mid-flight, then let the stale revalidation settle and assert the fresh value survives.
46 lines
1.8 KiB
TypeScript
46 lines
1.8 KiB
TypeScript
import { posix, win32 } from 'node:path'
|
|
import type { GitWorktreeExecOptions } from './worktree-operation-options'
|
|
import { translateWslOutputPaths } from './runner'
|
|
|
|
/** Normalize a worktree path for cross-platform comparison/keying: resolved, and case-folded on Windows syntax. */
|
|
export function canonicalWorktreePath(pathValue: string, platform = process.platform): string {
|
|
return platform === 'win32' || looksLikeWindowsPath(pathValue)
|
|
? win32.normalize(win32.resolve(pathValue)).toLowerCase()
|
|
: posix.normalize(posix.resolve(pathValue))
|
|
}
|
|
|
|
export function areWorktreePathsEqual(
|
|
leftPath: string,
|
|
rightPath: string,
|
|
platform = process.platform
|
|
): boolean {
|
|
if (platform === 'win32' || looksLikeWindowsPath(leftPath) || looksLikeWindowsPath(rightPath)) {
|
|
return canonicalWorktreePath(leftPath, 'win32') === canonicalWorktreePath(rightPath, 'win32')
|
|
}
|
|
return canonicalWorktreePath(leftPath, platform) === canonicalWorktreePath(rightPath, platform)
|
|
}
|
|
|
|
function looksLikeWindowsPath(pathValue: string): boolean {
|
|
return /^[A-Za-z]:[\\/]/.test(pathValue) || pathValue.startsWith('\\\\')
|
|
}
|
|
|
|
export function resolveRevParsePath(repoPath: string, value: string): string {
|
|
if (posix.isAbsolute(value) || win32.isAbsolute(value)) {
|
|
return value
|
|
}
|
|
// Old git ignores `--path-format=absolute`, so resolve a relative toplevel/git-dir against the scanned repo path.
|
|
return looksLikeWindowsPath(repoPath)
|
|
? win32.resolve(repoPath, value)
|
|
: posix.resolve(repoPath, value)
|
|
}
|
|
|
|
export function translateWorktreePath(
|
|
worktreePath: string,
|
|
repoPath: string,
|
|
options: GitWorktreeExecOptions = {}
|
|
): string {
|
|
const prefix = 'worktree '
|
|
const translated = translateWslOutputPaths(`${prefix}${worktreePath}`, repoPath, options)
|
|
return translated.startsWith(prefix) ? translated.slice(prefix.length) : worktreePath
|
|
}
|