Skip to content
shellmap

Count files changed since a commit

Get the number of distinct files that differ between a reference commit (a tag, SHA, or `origin/main`) and the working tree — for CI gates and PR-size summaries.

How to count files changed since a commit in each shell

Bashunix
git diff --name-only HEAD~5 | wc -l

`HEAD~5` = 5 commits back. Other useful refs: `origin/main` (vs upstream), `v1.0..HEAD` (since tag), `--since="2 weeks ago"` (commit-range filter). `--name-only` returns ONE file per line — safe to pipe to `wc -l`.

Zshunix
git diff --name-only HEAD~5 | wc -l
Fishunix
git diff --name-only HEAD~5 | wc -l
PowerShellwindows
(git diff --name-only HEAD~5 | Measure-Object).Count

pwsh pipeline captures git stdout as an array of strings (one per line) — `.Count` works directly without `Measure-Object`: `@(git diff --name-only HEAD~5).Count` is shorter. The `@()` array-wraps the result so single-file output still returns 1 (not the string itself).

cmd.exewindows
git diff --name-only HEAD~5 | find /v "" /c

`find /v "" /c` — same line-counting idiom as the standalone task. Counts every line that does NOT contain the empty string (i.e., every line). cmd has no `wc` analog.

Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.

Gotchas & notes

  • **`git diff` vs `git diff --cached` vs `git diff HEAD`** — three meanings, easy to confuse: `git diff` (no args) = working tree vs INDEX (uncommitted unstaged); `git diff --cached` (or `--staged`) = INDEX vs HEAD (uncommitted staged); `git diff HEAD` = working tree vs HEAD (both staged + unstaged combined). For "since a specific commit" use `git diff <ref>` which is "working tree vs <ref>". For commit-to-commit diff (no working tree involvement): `git diff <from> <to>` or `git diff <from>..<to>` — the two-dot form is equivalent for diff; the three-dot form `<from>...<to>` means "merge-base of from+to, vs to" (the GitHub PR convention).
  • `--name-only` vs `--name-status` vs `--stat` — `--name-only` returns just paths (one per line, suitable for `wc -l`); `--name-status` prepends `A`/`M`/`D`/`R`/`C` for Added/Modified/Deleted/Renamed/Copied (good for "how many added vs deleted" gates: `git diff --name-status HEAD~5 | grep -c "^A"`); `--stat` produces a human-readable summary with insertions/deletions per file (NOT machine-parseable — for grepping, use `--numstat` which gives tab-separated `additions deletions filename` per line).
  • Renames + binary files: by default `git diff --name-only` reports a renamed file ONCE under its new name (rename detection is on if files moved <50% changed — `git config diff.renames true` is default). To force NO rename detection (count the delete + add separately, giving 2 instead of 1): `git diff --no-renames --name-only`. Binary files appear in `--name-only` output but show "Binary files differ" in `--stat`. `git diff --shortstat HEAD~5` is the most compact "X files changed, Y insertions(+), Z deletions(-)" one-liner.
  • pwsh array semantics gotcha: `git diff --name-only` returning 0 files leaves `$x` as `$null` (NOT empty array) — `($null | Measure-Object).Count` is `0` but `$null.Count` THROWS (or returns 0 silently depending on pwsh version). Always wrap: `@(git diff --name-only HEAD~5).Count`. Same trap for: any external command output, `Get-ChildItem` with no matches, `Where-Object` that filters out everything. Set `$ErrorActionPreference = "Stop"` to surface these instead of swallowing silently.

Related commands

Related tasks