Skip to main content
RunBook Academy

Git, CI/CD & GitOpsVII · Repository InspectionInspection

git blame and annotation tracking — per-line authorship and code archaeology

Intermediate⏱ ~19 mingit

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

Not yet marked complete on this device.

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-email to also see the email; add --show-signature to 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=&lt;format&gt;.
  • 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 &lt;start&gt;,&lt;end&gt; 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 &lt;path&gt;"] --> 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 &lt;rev&gt; 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

  1. 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.
  2. Use -L &lt;range&gt; to keep the output focused. A full blame of a large file is unreadable; a range blame is the human size.
  3. Commit a .git-blame-ignore-revs file for every bulk-reformat commit. The audit trail is preserved without forcing the engineer to manually re-attribute every line.
  4. Add --show-email for post-mortem contact. The engineer who introduced the line is the right first call.
  5. Use --show-signature for 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 -L as 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-revs as the canonical post-reformat audit query.

Quiz

Knowledge check · 4 questions

  1. 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?

  2. 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.

  3. Q3. Name the three flags that turn blame into a code-archaeology tool and explain what each one does.

  4. 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.