Git, CI/CD & GitOpsVII · Repository InspectionInspection
git blame and annotation tracking — per-line authorship and code archaeology
What you'll learn
- Read the annotate output of git blame and identify the introducing commit for each line
- Use -L <start>,<end> to restrict blame to a range of lines in a file
- Use --ignore-rev and --ignore-rev-file to exclude a known-noisy commit from the blame
- Use --show-email and --show-signature to add identifying metadata to the annotation
- Recognise blame as the write-side primitive of the audit chain: who wrote this line?
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
git log answers “what happened in this repository?”. git show
answers “what changed in this one commit?”. git diff answers
“what changed between these two snapshots?”. git blame answers
the most pointed question of all: “who wrote this line, and in
which commit?”.
The question is not academic. It is the question every incident
post-mortem asks first, and the question every compliance audit
asks second. A line in terraform/main.tf that opens a security
group to 0.0.0.0/0 has a commit hash, an author, a date, and a
rationale. git blame finds the first three; the audit trail
follows from there. The flag set is small: the default, -L,
--ignore-rev, and --show-email cover ninety percent of
production use.
What blame displays
git blame <path> annotates every line of the file with the
metadata of the commit that introduced the line. The output
format is:
<hash> (<author> <date> <line>) <content>
Specifically:
- Hash — the abbreviated commit hash that introduced the line.
Default 7 characters, configurable with
core.abbrev. - Author — the author name. Add
--show-emailto also see the email; add--show-signatureto also see the GPG signature status of the commit if the commit is signed. - Date — the author date, formatted as the default short form
(
YYYY-MM-DD). Change with--date=<format>. - Line number — the line number in the original commit’s version of the file (the commit that introduced the line).
- Content — the line itself.
git blame terraform/main.tf
# 8a3f9d2c (Ada Lovelace 2026-08-18 1) module "router" {
# 8a3f9d2c (Ada Lovelace 2026-08-18 2) source = "git::ssh://git@github.com/example/router.git"
# 8a3f9d2c (Ada Lovelace 2026-08-18 3) version = "3.2.7"
# 1e0d9c4a (Augusta King 2026-08-15 4) }
# 1e0d9c4a (Augusta King 2026-08-15 5)
# 5b6f3a2b (Bertrand Russell 2026-08-17 6) region = "us-east-1"
# 9c4a8b1d (Ada Lovelace 2026-08-14 7)
# 9c4a8b1d (Ada Lovelace 2026-08-14 8) resource "aws_security_group" "router" {
The line number in the parenthetical is the position of the line
in the original commit’s version of the file, not the position
of the line in the current version. This is critical for
navigating the commit: a line with hash 8a3f9d2c and original
line 3 is line 3 of terraform/main.tf at commit 8a3f9d2c,
which is what git show 8a3f9d2c:terraform/main.tf will show on
line 3.
Restricting blame to a range
The -L <start>,<end> flag restricts the annotation to a range
of lines in the current version of the file. The start and end
can be line numbers, regex patterns, or line offsets:
# Lines 1 through 10 of the current file
git blame -L 1,10 terraform/main.tf
# From the line matching "^module \"router\"" to the end of the file
git blame -L '/^module "router"/,/^}/' terraform/main.tf
# A single line and the 5 lines after it (the "function context")
git blame -L 42,+5 terraform/main.tf
The regex form is the production primitive for “blame this configuration block”. The pattern is anchored to the lines of the current file, not the historical file, so the regex can be written in terms of the current shape of the configuration.
# Blame just the resource block for the security group
git blame -L '/resource "aws_security_group" "router"/,/^}/' terraform/main.tf
The -L flag is the right answer to “who wrote this block?” when
the file is large enough that the full blame output is
unreadable. It is also the right answer to “who introduced this
TODO?” when the TODO is a single line or a small region.
flowchart LR
B["git blame <path>"] --> Full["all lines"]
B -->|-L 1,10| Range1["line range"]
B -->|-L '/regex/,/regex/| Regex["regex range"]
B -->|--ignore-rev| Ignore["exclude noisy commit"]
B -->|--show-email| Email["author email visible"]
B -->|--show-signature| Sig["GPG signature visible"]
Full --> Use1["full audit of a file"]
Regex --> Use2["blame one block"]
Ignore --> Use3["blame ignoring a reformat"]
Email --> Use4["post-mortem contact"]
Sig --> Use5["supply-chain audit"]
Ignoring noisy commits
A common reason a blame is hard to read is a bulk-reformat
commit: a commit that changed whitespace, ran a code formatter,
or did a mass rename. The lines move forward in the diff, but no
semantic information changed. The --ignore-rev <rev> flag
hides such commits from the output: blame skips the commit and
re-attributes the lines to the commit that introduced the
semantically-relevant change.
# Blame the file, ignoring the bulk-reformat commit
git blame --ignore-rev 5b6f3a2b terraform/main.tf
The flag can be repeated for multiple ignored commits. The
production pattern is to record the set of ignored commits in a
file (one OID per line) and use --ignore-rev-file to point at
it. The file is typically committed to the repository as
.git-blame-ignore-revs, and editors and CI tools can be
configured to consult it automatically.
# Record the ignore list in a file
git blame --ignore-rev-file .git-blame-ignore-revs -L 1,50 terraform/main.tf
The convention of committing .git-blame-ignore-revs to the
repository (with an entry for every bulk-reformat commit) is the
production way to keep blame readable. The file is not consulted
by Git itself, but most editors (VS Code, vim, IntelliJ) and CI
tools read it when configured.
Production discipline
- Use blame for the audit question, not the design question. Blame points at the introducing commit; the design rationale is in the commit message and the pull request. The two together are the audit trail.
- Use
-L <range>to keep the output focused. A full blame of a large file is unreadable; a range blame is the human size. - Commit a
.git-blame-ignore-revsfile for every bulk-reformat commit. The audit trail is preserved without forcing the engineer to manually re-attribute every line. - Add
--show-emailfor post-mortem contact. The engineer who introduced the line is the right first call. - Use
--show-signaturefor supply-chain audits. The signed-status column shows whether the introducing commit was signed and whether the signature is valid.
Cross-course references
- Linux for Production Sysadmins - Part XXVII (BackupAndRecovery) covers the same discipline: the audit chain is the metadata that connects a file on disk to the change that put it there.
- Ansible for Production Sysadmins - Part XXXVII (RepoArch)
uses
git blame -Las the canonical “who wrote this playbook task?” query for an incident post-mortem. - Terraform for Production Sysadmins - Part IX (State)
uses
git blame --ignore-rev-file .git-blame-ignore-revsas the canonical post-reformat audit query.
Quiz
Knowledge check · 4 questions
Q1. A team ran a bulk-reformat commit (whitespace only) across the entire `terraform/` tree. The audit now needs to know who originally wrote a specific resource block. Which flag combination is the right primitive?
Q2. The line number in the parenthetical of a `git blame` output is the position of the line in the current version of the file at HEAD.
Q3. Name the three flags that turn blame into a code-archaeology tool and explain what each one does.
Q4. A post-mortem needs to identify the original author of a misconfigured security group. Build the right `git blame` query and explain why the obvious one is wrong.
An incident has revealed that `terraform/main.tf` opens a security group to `0.0.0.0/0` in production. The security team needs to know: who introduced the line, when, and under what rationale. The team has a `.git-blame-ignore-revs` file at the repository root with the OID of a bulk-reformat commit that the team ran six months ago. The engineer on call runs `git blame terraform/main.tf | grep 0.0.0.0` and finds the line, but the commit hash is the bulk-reformat commit and the author is the engineer who ran the reformat, not the engineer who introduced the misconfiguration.
Passing score: 75%. Answers are checked in this browser.