1import { execSync } from 'child_process' 4const parseGitStatusPorcelain = (stdout: string): string[] => 5 stdout.split('\n').map(line => { 6 const match = line.match(/^.{2}\s(.+?)(?:\s->\s.+)?$/) 8 }).filter(Boolean) as string[] 10export const getUncommittedInPathSet = (pathsA: string[]): string[] => { 11 const stdout = execSync('git status --porcelain', { cwd: ptDir, encoding: 'utf8' }) 12 if (!stdout.trim()) return [] 13 const changedFiles = parseGitStatusPorcelain(stdout) 14 const pathsSet = new Set(pathsA) 15 return changedFiles.filter(f => pathsSet.has(f) || pathsA.some(p => f.startsWith(p + '/'))) 18export const getGitignoredInPathSet = (pathsA: string[]): string[] => { 19 if (pathsA.length === 0) return [] 20 // Batch to avoid pipe buffer limits; skip paths in submodules (git check-ignore exits 128 on them) 21 const batchSize = 1000 22 const ignoredA: string[] = [] 23 for (let i = 0; i < pathsA.length; i += batchSize) { 24 const batch = pathsA.slice(i, i + batchSize) 25 const input = batch.join('\n') 27 const stdout = execSync('git check-ignore --stdin', { cwd: ptDir, input, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], maxBuffer: 10 * 1024 * 1024 }).trim() 28 if (stdout) ignoredA.push(...stdout.split('\n').filter(Boolean)) 30 if (e.status === 1) continue // exit 1 = no paths ignored in this batch 31 if (e.status === 128) continue // exit 128 = fatal (e.g. submodule paths) — skip batch, filtered later by isExcludedPath 38export const gitDiffQuietInPaths = (fromSha: string, toSha: string, pathsA: string[]): boolean => { 41 `git diff --quiet ${fromSha}..${toSha} -- ${pathsA.join(' ')}`, 42 { cwd: ptDir, stdio: 'pipe', maxBuffer: 10 * 1024 * 1024 }, 44 return true // exit 0 = no diff 46 return false // exit 1 = has diff