Fix #19: support --summary in show, log and diff

Merged
HA
5d ago
+900-89

Fixes #19

Summary

git show, git log and git diff now accept --summary. Before this change all three rejected it with Unknown option "--summary". The summary lists the files a change created, deleted, renamed or changed mode on, which is often all an agent needs from a commit.

Supported surface:

git show --summary [--stat | --shortstat | --numstat | -p] [<object>]
git log  --summary [--stat | --shortstat | --numstat | -p] [--oneline | --format=<fmt>] [--graph] ...
git diff --summary [--stat | --shortstat | --numstat] [--cached] [<commit> [<commit>]]

The lines match git 2.34.1 byte for byte:

 create mode 100644 n
 delete mode 100755 m
 mode change 100644 => 100755 n
 mode change 100755 => 120000 n          (type change)
 create mode 120000 link
 rename z => z2 (100%)
 rename dir/sub/{a => b} (100%)
 rename big => big2 (70%)
 mode change 100644 => 100755            (rename that also changed the mode)
 create mode 100644 "q\"uote"
 create mode 100644 "\303\251.txt"

Ordering follows git:

  • --summary on its own replaces the patch.
  • After --stat, --shortstat or --numstat it prints right after their output.
  • With -p it prints before the patch, with a blank line between them.
  • --name-only and --name-status drop it, and so does show --no-patch.
  • log prints nothing for merges, since it doesn't diff them by default.
  • A commit with changes but no summary lines, such as a content-only edit, still gets the blank line after its message. That is why git log --summary shows two blank lines after such a commit in medium format.

Before and after

$ git show --summary HEAD
# before (1.9.1): Unknown option "--summary".   rc=1
# after:
commit e91cbdcf157f110f5a97fdc92e2c9b0a1c21fab9
Author: a <a@b>
Date:   ...

    sum

 delete mode 100755 m
 create mode 100644 n
 rename z => z2 (100%)

How it works

formatSummaryLines in src/lib/commit-summary.ts is the only formatter for these lines. commit, merge, show, log and diff all call it. computeDiffStats used to build the create/delete lines for commit and merge as a side output. It now returns only the file stats, and the summary lines come from the new function.

Sharing the formatter changes the output of commit and merge in three cases, and in each case the new output is what git prints:

  • A mode change used to print as create mode 100755 n plus delete mode 100644 n. It now prints mode change 100644 => 100755 n.
  • A rename that also changed the mode now prints the pathless mode change line under the rename.
  • Paths that need it are C-quoted the way git's quote_c_style quotes them. If either side of a rename needs quoting, both sides print in full ("a" => "b") without the {a => b} form, as git's pprint_rename does.

In show and log the diff options now resolve to a main format plus a summary flag, and withSummary puts the two in git's order. log's formatCommitDiff returns null when it shows nothing at all (no diff option, a merge, an empty diff) and "" when the commit has changes but the output is empty. The difference is the blank line described above. diff maps its items to the same inputs and appends the summary.

The PR has two more commits, split out so they are easy to review or drop:

  • log --graph --format=<fmt> blank line. With --graph and a custom format, the diff output came right under the formatted line. Git puts a padded blank line there, the same as without --graph. Only --oneline skips it. This showed up as soon as I compared log --graph --format=%s --summary with git, and --stat had the same bug.
  • show on a merge. Git shows a merge's patch as a combined diff, but --stat, --shortstat, --numstat and --summary compare the merge with its first parent. just-git printed nothing for those formats on a merge, with a comment saying that matched git. It doesn't in 2.34.1, so show --summary <merge> would have printed nothing where git lists the merged-in files. Both now use the first parent. The name formats still print nothing on a merge.

Tests

  • In-memory tests with exact stdout, stderr and exit code. test/commands/show.test.ts has 11 new tests: summary alone, after --stat/--shortstat/--numstat, before -p, symlinks and quoted names, dir/sub/{a => b}, a type change, the blank line on a content-only commit, dropped by the name formats and --no-patch, through an annotated tag, and merges against the first parent. log.test.ts has 6, diff.test.ts has 6 (two commits, --cached, unstaged deletions, stat formats) and commit.test.ts has 1 for the mode-change lines. They share a history builder, setupSummaryHistory in test/util.ts.
  • test/interop/summary.test.ts builds a repo with real git, including an executable delete, a 70% rename that also gets +x, a symlink, a gitlink, a type change, names with a space, a quote, a tab and é, and a merge on its own branch. It runs 39 invocations of show, log and diff through both and asserts identical stdout, stderr and exit code. I checked that it catches defects. Turning off path quoting failed 9 cases, and dropping the pathless mode line under renames failed 14.
  • test/graph/scenarios/16-graph-with-summary.ts compares log --graph --summary with real git in medium, --oneline, --format and --stat forms. Scenario 09 gets two --graph --format cases for the blank-line fix.
  • Every new test failed before its change.
  • bun test: 3576 pass, 29 skip, 0 fail. On origin/main (b509001) it was 3512 pass, 29 skip, 0 fail, with local git 2.34.1. bun check is clean, and bun gen-docs updated docs/CLI.md and docs/FILE_REFERENCE.md.

Not included

These are gaps I hit while comparing with git. None of them come from --summary, and --stat or --name-status already show them on main:

  • git diff <commit> (commit against worktree) and git diff HEAD don't run rename detection, so a staged rename shows as a delete plus a create. --name-status does the same today.
  • git diff and git diff --cached don't see mode-only changes in the worktree or index. #15 fixes that in diff.ts, and the summary lines will show them once it lands.
  • -q with --summary prints the summary, the same way just-git already handles -q --stat. Git 2.34.1 prints nothing for both. I left this alone because just-git's -q handling follows newer git.
  • --stat, --numstat and -p fail on a commit that adds or removes a gitlink, because they try to read the submodule commit as a blob. --summary alone works (create mode 160000 gm).
  • The -p part of show -p --summary / log -p --summary inherits the existing patch gaps: no old mode/new mode lines, and combined diffs drop the hunk for a file both parents lack. The summary part matches.
  • show --name-status on a merge prints nothing where git prints combined status (MM f).
  • --compact-summary isn't supported, and show -p --stat still prints only the stat.