( absoluteFilePath: string, )
| 405 | * Returns null if not in a git repo or if git commands fail. |
| 406 | */ |
| 407 | export async function fetchSingleFileGitDiff( |
| 408 | absoluteFilePath: string, |
| 409 | ): Promise<ToolUseDiff | null> { |
| 410 | const gitRoot = findGitRoot(dirname(absoluteFilePath)) |
| 411 | if (!gitRoot) return null |
| 412 | |
| 413 | const gitPath = relative(gitRoot, absoluteFilePath).split(sep).join('/') |
| 414 | const repository = getCachedRepository() |
| 415 | |
| 416 | // Check if the file is tracked by git |
| 417 | const { code: lsFilesCode } = await execFileNoThrowWithCwd( |
| 418 | gitExe(), |
| 419 | ['--no-optional-locks', 'ls-files', '--error-unmatch', gitPath], |
| 420 | { cwd: gitRoot, timeout: SINGLE_FILE_DIFF_TIMEOUT_MS }, |
| 421 | ) |
| 422 | |
| 423 | if (lsFilesCode === 0) { |
| 424 | // File is tracked - diff against merge base for PR-like view |
| 425 | const diffRef = await getDiffRef(gitRoot) |
| 426 | const { stdout, code } = await execFileNoThrowWithCwd( |
| 427 | gitExe(), |
| 428 | ['--no-optional-locks', 'diff', diffRef, '--', gitPath], |
| 429 | { cwd: gitRoot, timeout: SINGLE_FILE_DIFF_TIMEOUT_MS }, |
| 430 | ) |
| 431 | if (code !== 0) return null |
| 432 | if (!stdout) return null |
| 433 | return { |
| 434 | ...parseRawDiffToToolUseDiff(gitPath, stdout, 'modified'), |
| 435 | repository, |
| 436 | } |
| 437 | } |
| 438 | |
| 439 | // File is untracked - generate synthetic diff |
| 440 | const syntheticDiff = await generateSyntheticDiff(gitPath, absoluteFilePath) |
| 441 | if (!syntheticDiff) return null |
| 442 | return { ...syntheticDiff, repository } |
| 443 | } |
| 444 | |
| 445 | /** |
| 446 | * Parse raw unified diff output into the structured ToolUseDiff format. |
no test coverage detected