Every line has a story
Sooner or later you will stare at a line of code that makes no sense, a strange constant or a check for a case that looks impossible, and you will need to know why it exists before daring to touch it. Deleting a line nobody understands is how bugs that someone already fixed come back. This investigation happens weekly in real jobs, and Git has a command dedicated to it.
git blame <file> prints the file with, for each line, the last commit that touched it.
$ git blame pancakes.txt a1b2c3d (Ada Lovelace 2026-03-03 1) Pancakes a1b2c3d (Ada Lovelace 2026-03-03 2) - flour a1b2c3d (Ada Lovelace 2026-03-03 3) - eggs f0e9d8c (Ada Lovelace 2026-03-04 4) - oat milk
Line 4 was last changed by commit f0e9d8c, and the first three came from a1b2c3d. Feeding that hash to git show from lesson 3-1 produces the commit's message and full change, which is the why you were after.
Despite the accusatory name, blame is mostly used for context rather than fault-finding, and very often on your own lines from a year ago.
This is also where lesson 3-3 pays off. Blame leads you to a commit message, and that message is either Guard against empty carts, they crash the total or fixed stuff. Only one of those ends the investigation.
Searching history when blame is not enough
Blame answers who last touched what is there now. Two other searches cover the cases it cannot reach.
| Command | What it finds |
|---|---|
git log --oneline -- pancakes.txt | every commit that touched one file, across renames |
git log -S "oat milk" --oneline | commits whose changes added or removed that exact text |
The first form takes a -- to separate the file name from other arguments, which matters when a file and a branch share a name. It reports the file's whole history even if the lines have since been rewritten.
The second is known as the pickaxe, and its distinctive power is finding deletions. Blame cannot locate a deleted line, because the line no longer exists to be annotated, while -S scans the changes themselves and so finds the commit that removed the text.
Together with git log and git show, these turn history into a searchable database of decisions rather than a pile of old versions, which is the real payoff for all those careful commits.
A recorded session
This session investigates a single line. The recipe now uses oat milk, and the history explains why.
Each step below shows the command and the output it printed.
Step 1. Show, for each line of pancakes.txt, the last commit that touched it.
~/recipe-book $ git blame pancakes.txt a1b2c3d (Ada Lovelace 2026-03-03 1) Pancakes a1b2c3d (Ada Lovelace 2026-03-03 2) - flour a1b2c3d (Ada Lovelace 2026-03-03 3) - eggs f0e9d8c (Ada Lovelace 2026-03-04 4) - oat milk
Step 2. Line 4 came from f0e9d8c. Read that commit.
~/recipe-book $ git show f0e9d8c commit f0e9d8c7b6a5d4c3b2a1f0e9d8c7b6a5d4c3b2a1 Author: Ada Lovelace <ada@example.com> Date: Wed Mar 4 09:12:44 2026 -0800 Switch to oat milk for dairy-free version diff --git a/pancakes.txt b/pancakes.txt -- milk +- oat milk
Step 3. List only the commits that ever touched pancakes.txt.
~/recipe-book $ git log --oneline -- pancakes.txt
f0e9d8c Switch to oat milk for dairy-free version
a1b2c3d Add pancake recipeIn git blame output, the hash at the start of each line names the last commit that changed that particular line.
Blame maps every line currently in the file to the most recent commit that touched it, which makes the annotation per-line rather than per-file.
The consequence is that one file's output routinely points at many different commits from many different years. A file created in 2019 and patched twice since will show three distinct hashes, and the useful one is whichever sits beside the line you are actually puzzled by.
For a line deleted months ago, git log -S "the deleted text" is the right tool because blame annotates only the lines that exist now, and a deleted line has no entry to annotate.
git blame explains a file's current contents by walking backwards from them. Once a line is gone, it is absent from that starting point and blame has nothing to say about it.
git log -S, the pickaxe, works from the other direction. It scans every commit's changes looking for the given text being added or removed, so a commit that deleted the line matches just as readily as the one that introduced it. That makes it the standard way to answer why did this disappear, which is often a more urgent question than why is this here.
The full command is git blame config.py.
Each line of the file is printed with the hash, author, and date of the last commit that modified it, so a confusing line is immediately traceable to one commit.
That hash is the start of the trail rather than the end of it. Passing it to git show prints the commit's message and its complete diff, which supplies the reasoning that the annotation alone cannot.