* `--stat`: a per-file line with a `+`/`-` bar plus a summary * footer. The graph is scaled-down only when the widest file's * total exceeds the column budget, mirroring real git closely * enough for a human to read and a script to grep the footer.
(entries: DiffSummary[])
| 406 | * enough for a human to read and a script to grep the footer. |
| 407 | */ |
| 408 | function formatDiffStat(entries: DiffSummary[]): string { |
| 409 | if (entries.length === 0) return ""; |
| 410 | const nameWidth = Math.max(...entries.map((e) => e.path.length)); |
| 411 | const maxTotal = Math.max(...entries.map((e) => e.insertions + e.deletions)); |
| 412 | // Cap the bar at 60 columns the way git's default terminal |
| 413 | // width does; scale proportionally when any file exceeds it. |
| 414 | const budget = 60; |
| 415 | const scale = maxTotal > budget ? budget / maxTotal : 1; |
| 416 | |
| 417 | const lines: string[] = []; |
| 418 | let totalIns = 0; |
| 419 | let totalDel = 0; |
| 420 | for (const e of entries) { |
| 421 | totalIns += e.insertions; |
| 422 | totalDel += e.deletions; |
| 423 | const total = e.insertions + e.deletions; |
| 424 | const plus = Math.round(e.insertions * scale); |
| 425 | const minus = Math.round(e.deletions * scale); |
| 426 | const bar = `${"+".repeat(plus)}${"-".repeat(minus)}`; |
| 427 | lines.push(` ${e.path.padEnd(nameWidth)} | ${String(total).padStart(4)} ${bar}`); |
| 428 | } |
| 429 | |
| 430 | const fileWord = entries.length === 1 ? "file" : "files"; |
| 431 | const parts = [`${entries.length} ${fileWord} changed`]; |
| 432 | if (totalIns > 0) { |
| 433 | parts.push(`${totalIns} ${totalIns === 1 ? "insertion(+)" : "insertions(+)"}`); |
| 434 | } |
| 435 | if (totalDel > 0) { |
| 436 | parts.push(`${totalDel} ${totalDel === 1 ? "deletion(-)" : "deletions(-)"}`); |
| 437 | } |
| 438 | lines.push(` ${parts.join(", ")}`); |
| 439 | return `${lines.join("\n")}\n`; |
| 440 | } |
| 441 | |
| 442 | // --------------------------------------------------------------- |
| 443 | // init |