| 148 | * Only stores first MAX_FILES entries in perFileStats. |
| 149 | */ |
| 150 | export function parseGitNumstat(stdout: string): NumstatResult { |
| 151 | const lines = stdout.trim().split('\n').filter(Boolean) |
| 152 | let added = 0 |
| 153 | let removed = 0 |
| 154 | let validFileCount = 0 |
| 155 | const perFileStats = new Map<string, PerFileStats>() |
| 156 | |
| 157 | for (const line of lines) { |
| 158 | const parts = line.split('\t') |
| 159 | // Valid numstat lines have exactly 3 tab-separated parts: added, removed, filename |
| 160 | if (parts.length < 3) continue |
| 161 | |
| 162 | validFileCount++ |
| 163 | const addStr = parts[0] |
| 164 | const remStr = parts[1] |
| 165 | const filePath = parts.slice(2).join('\t') // filename may contain tabs |
| 166 | const isBinary = addStr === '-' || remStr === '-' |
| 167 | const fileAdded = isBinary ? 0 : parseInt(addStr ?? '0', 10) || 0 |
| 168 | const fileRemoved = isBinary ? 0 : parseInt(remStr ?? '0', 10) || 0 |
| 169 | |
| 170 | added += fileAdded |
| 171 | removed += fileRemoved |
| 172 | |
| 173 | // Only store first MAX_FILES entries |
| 174 | if (perFileStats.size < MAX_FILES) { |
| 175 | perFileStats.set(filePath, { |
| 176 | added: fileAdded, |
| 177 | removed: fileRemoved, |
| 178 | isBinary, |
| 179 | }) |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | return { |
| 184 | stats: { |
| 185 | filesCount: validFileCount, |
| 186 | linesAdded: added, |
| 187 | linesRemoved: removed, |
| 188 | }, |
| 189 | perFileStats, |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | /** |
| 194 | * Parse unified diff output into per-file hunks. |