Finding who to ask about a line
While fixing a bug, find the commit and author behind a suspicious line, then read that commit's message to understand why it was written.
Git · Command
git blame shows, for every line of a file, which commit last changed that line, along with its author and date. Use it to find out where a specific piece of code came from.
Last updated
Git starts from a revision (HEAD by default, or the <rev> you pass) and walks back through history. At each step it compares a commit's version of the file with its parent's version. Lines that are the same in both are passed on to the parent. Lines that differ are credited to that commit. This continues until every line has a commit or reaches a boundary, such as a range limit or the root commit. The default output puts the short commit hash, author, date and line number in front of each line. Options can limit the line range, detect moved or copied code, or skip commits you choose.
git blame [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental] [-L <range>] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>] [--ignore-rev <rev>] [--ignore-revs-file <file>] [--color-lines] [--color-by-age] [--progress] [--abbrev=<n>] [ --contents <file> ] [<rev> | --reverse <rev>..<rev>] [--] <file>| Name | Type | Description |
|---|---|---|
<file> | path | The file to annotate (required). |
<rev> | revision or range | The revision to start annotating from (HEAD if omitted). A range such as v2.6.18.. limits how far back blame looks. |
-L <start>,<end> | -L :<funcname> | line range | Annotates only the given lines. Each end can be a line number, a /regex/ or a +/-offset. A function-name regex can be used instead of a range. |
-M[<num>] / -C[<num>] | flag with optional threshold | Detect lines moved or copied within the file (-M, default threshold 20 alphanumeric characters) or from other files (-C, default threshold 40). -C can be repeated to search more widely. |
--ignore-rev <rev> / --ignore-revs-file <file> | revision / path | Skip the given commits when assigning blame. |
-p, --porcelain / --line-porcelain / --incremental | flag | Machine-readable output formats. |
--contents <file> | path or - | Annotate the given contents (use - for stdin) instead of the version stored in the repository. |
Writes to stdout one annotation per line of the file. By default each line has the abbreviated commit hash, the author ident and the line number, followed by the line's content. Porcelain and incremental modes print structured headers instead.
#!/usr/bin/env bash
set -euo pipefail
# Commit hashes of bulk reformatting commits to ignore (one per line)
cat > .git-blame-ignore-revs <<'EOF'
# example: prettier reformat
# 0123456789abcdef0123456789abcdef01234567
EOF
# Make every git blame in this repository use the ignore list
git config blame.ignoreRevsFile .git-blame-ignore-revs
# Blame only the body of main(); ignore whitespace; detect moved/copied code
git blame -w -C -L ':main' -- src/app.c
# Count how many lines each author is credited with
git blame --line-porcelain -- src/app.c \
| sed -n 's/^author //p' | sort | uniq -c | sort -rnThe script adds an ignore-revs file to the repository config, blames a single function with whitespace changes ignored and copy detection on, then counts lines per author from the line-porcelain output.
While fixing a bug, find the commit and author behind a suspicious line, then read that commit's message to understand why it was written.
After a project-wide formatter run, add that commit to an ignore file so blame keeps crediting the real authors.
Run git blame -C -C -f $commit^! -- file to see which lines of a newly added file were copied from existing files.
Use --reverse <start>..HEAD to find the last commit in which a line still existed.
Pass the commit with --ignore-rev, or list its hash in a file and point blame.ignoreRevsFile or --ignore-revs-file at that file. Blame then treats that commit as if it never happened and credits the earlier commit that changed each line. Adding -w also helps when the changes were whitespace-only.
Follow-up: How can you see which lines were affected by the ignored commit? Set blame.markIgnoredLines to mark lines that were re-credited with '?', and blame.markUnblamableLines to mark lines that couldn't be credited to any other commit with '*'.
-M finds blocks of lines that were moved or copied within the same file. -C does that too, and also finds lines that came from other files changed in the same commit. Each takes an optional minimum number of alphanumeric characters that must match: 20 by default for -M and 40 for -C.
Follow-up: What do -C -C and -C -C -C add? Given twice, blame also looks at other files in the commit that created the file. Given three times, it looks at other files in every commit, which is the most thorough but the slowest.
Use --porcelain or --line-porcelain. Each line gets a header with the full 40-character hash, the line number in the original and final file, and fields such as author and summary, and then the line's content after a tab. --line-porcelain repeats the commit details on every line, so the script doesn't need to remember them.
Follow-up: Why might an interactive viewer use --incremental instead? It prints results while blame is still running, newest commits first, and every entry ends with a filename line, so a viewer can display results gradually.
git annotategit loggit diffgit rev-listgit configgitattributesgitmailmapgit bisect