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
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.jsOptions
| Flag | What it does |
|---|---|
| --porcelain | Stable machine-readable output (dedupes commit headers) |
| --line-porcelain | Like --porcelain but full header per line |
| -L <start>,<end> | Restrict to a line range |
| -C / -M | Detect copied / moved lines across or within files |
| --ignore-revs-file <f> | Skip commits listed in the file |
| -w | Ignore 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.
- 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