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