Skip to content
LatchkeyLatchkey home

git blame --porcelain and -L: Machine-Readable Blame

git blame --porcelain emits a stable, parseable format (one block per line with author, time, and SHA), while -L<start>,<end> scopes blame to a line range.

Automated ownership and regression tooling parses blame, and the human format is not stable. --porcelain (and --line-porcelain) give a documented machine format; -L, -C/-M, and --ignore-revs-file refine what it reports.

What it does

git blame annotates each line of a file with the commit that last changed it. --porcelain outputs a stable format suitable for scripts: a header line with SHA and line numbers, followed by author/committer/summary fields. -L limits to a line range. -C/-M detect copied/moved lines. --ignore-revs-file skips noise commits (like a mass reformat).

Common usage

Terminal
git blame --porcelain -L 40,60 src/app.js
# follow code moved between files
git blame -C -M src/app.js
# one record per line (easiest to parse)
git blame --line-porcelain src/app.js
# ignore a bulk-reformat commit
git blame --ignore-revs-file .git-blame-ignore-revs src/app.js

Options

FlagWhat it does
--porcelainStable machine-readable output (dedupes commit headers)
--line-porcelainLike --porcelain but full header per line
-L <start>,<end>Restrict to a line range
-C / -MDetect copied / moved lines across or within files
--ignore-revs-file <f>Skip commits listed in the file
-wIgnore whitespace changes when assigning blame

In CI

Blame needs history: on a shallow clone, lines from before the boundary are attributed to the boundary commit, giving wrong owners, so fetch-depth: 0 for ownership tooling. Commit a .git-blame-ignore-revs file and reference it (or set blame.ignoreRevsFile) so mass-reformat commits do not pollute blame in every job.

Common errors in CI

"fatal: no such path '<file>' in HEAD" means the path is wrong or absent at that commit. "fatal: file <x> has only N lines" means an -L range exceeds the file. Blame pointing everything at one recent commit is the classic shallow-clone symptom: unshallow the clone.

Using this in CI

CI checkouts are shallow and detached by default, which changes the answer this command gives you. Commands that read history, branch names, or tags need the checkout configured for it.

.github/workflows/ci.yml
- uses: actions/checkout@v4
  with:
    fetch-depth: 0   # history, tags, and git describe all need this

- run: |
    git rev-parse --is-shallow-repository   # expect false
    git rev-parse --abbrev-ref HEAD          # prints HEAD when detached

Frequently asked questions

git blame --porcelain and -L: Machine-Readable Blame?
Automated ownership and regression tooling parses blame, and the human format is not stable. --porcelain (and --line-porcelain) give a documented machine format; -L, -C/-M, and --ignore-revs-file refine what it reports.
What it does?
git blame annotates each line of a file with the commit that last changed it. --porcelain outputs a stable format suitable for scripts: a header line with SHA and line numbers, followed by author/committer/summary fields. -L limits to a line range. -C/-M detect copied/moved lines.
In CI?
Blame needs history: on a shallow clone, lines from before the boundary are attributed to the boundary commit, giving wrong owners, so fetch-depth: 0 for ownership tooling. Commit a .git-blame-ignore-revs file and reference it (or set blame.ignoreRevsFile) so mass-reformat commits do not pollute blame in every job.
Common errors in CI?
"fatal: no such path '<file>' in HEAD" means the path is wrong or absent at that commit. "fatal: file <x> has only N lines" means an -L range exceeds the file. Blame pointing everything at one recent commit is the classic shallow-clone symptom: unshallow the clone.

Related guides

References

Run this faster and cheaper on Latchkey managed runners - self-healing included. Start free → 30-day trial · No credit card