Files
orca/src/main/ipc/filesystem-search-git.ts
T
0ec3882cb8 Add project Windows runtime selection (#5519)
* Add project Windows runtime selection

* Fix project Windows runtime selection

Co-authored-by: Orca <help@stably.ai>

* fix: preserve WSL shell variables

---------

Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Neil <neil@stably.ai>
2026-06-17 16:08:14 -07:00

98 lines
2.7 KiB
TypeScript

import type { SearchOptions, SearchResult } from '../../shared/types'
import {
buildGitGrepArgs,
buildSubmatchRegex,
createAccumulator,
finalize,
ingestGitGrepLine,
SEARCH_TIMEOUT_MS
} from '../../shared/text-search'
import { gitSpawn } from '../git/runner'
/**
* 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 function searchWithGitGrep(
rootPath: string,
args: SearchOptions,
maxResults: number,
localGitOptions: { wslDistro?: string } = {}
): Promise<SearchResult> {
return new Promise((resolve) => {
const gitArgs = buildGitGrepArgs(args.query, args)
const matchRegex = buildSubmatchRegex(args.query, args)
const acc = createAccumulator()
let stdoutBuffer = ''
let done = false
const child = gitSpawn(gitArgs, {
cwd: rootPath,
...(localGitOptions.wslDistro ? { wslDistro: localGitOptions.wslDistro } : {}),
stdio: ['ignore', 'pipe', 'pipe']
})
let killTimeout: ReturnType<typeof setTimeout>
function resolveOnce(): void {
if (done) {
return
}
done = true
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 {
stdoutBuffer += chunk
const lines = stdoutBuffer.split('\n')
stdoutBuffer = lines.pop() ?? ''
for (const l of lines) {
processLine(l)
}
}
function handleStderrData(): void {
/* drain */
}
function handleError(): void {
resolveOnce()
}
function handleClose(): void {
if (stdoutBuffer) {
processLine(stdoutBuffer)
}
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)
})
}