Files
orca/src/main/ipc/filesystem-search-git.ts
T

104 lines
3.1 KiB
TypeScript

import { SearchSubprocessLineAccumulator } from '../../shared/search-subprocess-lines'
import type { SearchOptions, SearchResult } from '../../shared/code-search-types'
import {
buildGitGrepArgs,
buildSubmatchRegex,
createAccumulator,
finalize,
ingestGitGrepLine,
SEARCH_TIMEOUT_MS
} from '../../shared/text-search'
import { gitSpawnAfterWindowsEnvironmentReady } from '../git/runner'
import {
isWslLinkedWorktreeGitRoutingCandidate,
prepareWslLinkedWorktreeGitRouting
} from '../git/wsl-linked-worktree-git-routing'
/**
* Fallback text search using git grep. Used when rg is not available.
*
* Why: On Linux, rg may not be installed or may not be in PATH when the app
* is launched from a desktop entry (which inherits a minimal system PATH).
* git grep is always available since this is a git-focused app.
*/
export async function searchWithGitGrep(
rootPath: string,
args: SearchOptions,
maxResults: number,
localGitOptions: { wslDistro?: string } = {}
): Promise<SearchResult> {
if (isWslLinkedWorktreeGitRoutingCandidate(rootPath, localGitOptions.wslDistro)) {
await prepareWslLinkedWorktreeGitRouting(rootPath, localGitOptions.wslDistro)
}
const gitArgs = buildGitGrepArgs(args.query, args)
const child = await gitSpawnAfterWindowsEnvironmentReady(gitArgs, {
cwd: rootPath,
admissionTier: 'interactive',
...(localGitOptions.wslDistro ? { wslDistro: localGitOptions.wslDistro } : {}),
stdio: ['ignore', 'pipe', 'pipe']
})
return new Promise((resolve) => {
const matchRegex = buildSubmatchRegex(args.query, args)
const acc = createAccumulator()
const lines = new SearchSubprocessLineAccumulator(Number.MAX_SAFE_INTEGER)
let done = false
let killTimeout: ReturnType<typeof setTimeout>
function resolveOnce(): void {
if (done) {
return
}
done = true
lines.clear()
clearTimeout(killTimeout)
// Why: child.kill() is advisory. If git ignores it, detach our
// closures so repeated fallback searches do not retain old scans.
child.stdout!.off('data', handleStdoutData)
child.stderr!.off('data', handleStderrData)
child.off('error', handleError)
child.off('close', handleClose)
resolve(finalize(acc))
}
function processLine(line: string): void {
const verdict = ingestGitGrepLine(line, rootPath, matchRegex, acc, maxResults)
if (verdict === 'stop') {
child.kill()
}
}
function handleStdoutData(chunk: string): void {
lines.push(chunk, processLine)
}
function handleStderrData(): void {
/* drain */
}
function handleError(): void {
resolveOnce()
}
function handleClose(): void {
const tail = lines.finish()
if (tail !== null) {
processLine(tail)
}
resolveOnce()
}
child.stdout!.setEncoding('utf-8')
child.stdout!.on('data', handleStdoutData)
child.stderr!.on('data', handleStderrData)
child.once('error', handleError)
child.once('close', handleClose)
killTimeout = setTimeout(() => {
acc.truncated = true
child.kill()
resolveOnce()
}, SEARCH_TIMEOUT_MS)
})
}