Theory
Reading history: messages, diffs and blame
How to open a file's history, read a diff line by line and use blame to trace any line back to its commit, pull request and issue.
~10 min read · 10 exercises · 250 points
Before you start
Who changed this, and why?
Back on CatChow's product team. Last time you tracked down the delivery price change through the file list, the long way round. Questions like "who changed this and why?" will keep coming, so today you'll learn to answer them properly.
You'll open the history of a single file, read a diff line by line and trace any line on the site back to the commit, pull request and issue it came from.
A file's own history
On CatChow's product team the most common question is "who changed this, and why?". The fastest way in is usually through one file.
Open the file, for example config/prices.json. In its top right corner there's a History button. It lists every commit that ever changed this one file, grouped by date, each with its message, author and short hash. The whole repository may have hundreds of commits, but a file's history is short and focused: here, eleven.

Remember the direction: GitHub lists history newest first, while the diagrams in this course run oldest first. Engineers see the same list in a terminal with git log -- config/prices.json; the -- tells Git that what follows is a file path.
Other doors lead elsewhere. The 412 Commits counter opens the history of the whole branch, the README introduces the project, and the Actions tab shows automatic checks and builds. When your question is about one file, start from that file.
- DoOpen
config/prices.json→HistoryEleven relevant commits instead of hundreds.
- Don'tScroll the whole
412 Commitslist looking for price changesSlow, and easy to miss one.
File history vs branch history
The file view and the branch view show the same commits, filtered differently. Out of the last six commits on main, maybe only two touched config/prices.json; the file's History shows just those two, while the branch history shows all six.
5b8f3a2Update cat food photos77c1e90Add express delivery price9d04e61Fix typo in footera41c09eRaise delivery price to 59 UAHe7c3b19Add summer promo bannerf02a6d4Update FAQ answersmain
Commit graph, oldest first: Last six commits on `main` 1. commit 5b8f3a2 on main "Update cat food photos" 2. commit 77c1e90 on main "Add express delivery price" (highlighted) 3. commit 9d04e61 on main "Fix typo in footer" 4. commit a41c09e on main "Raise delivery price to 59 UAH" (highlighted) 5. commit e7c3b19 on main "Add summer promo banner" 6. commit f02a6d4 on main "Update FAQ answers"
Choose by what you already know:
- You know the file ("why did the delivery price change?"): open the file and press
History. - You know who or when, but not where ("Daniel changed something last Tuesday"): open the branch history through the commit counter, then use its filters. GitHub lets you pick an author and a date range, so the list shrinks to a handful of commits. Each one names the files it touched.
In a terminal, engineers do the same with git log --author="Daniel" --since="2026-10-06" --until="2026-10-07", or with git log -- config/prices.json for one file.
Picking the right view first saves more time than reading fast. A good habit is to say your question out loud and notice which of the three you have: the file, the person or the date.
- Do"Something changed last Tuesday" → branch history, filter by author and date
- Do"Why did this price change?" → the file's
History - Don'tOpen file after file hoping to spot the change
Start from what you know, not from the folder tree.
How to read a diff
Click any commit and you get its diff: what changed compared with the previous version. Here's the diff of config/prices.json in a41c09e:
"currency": "UAH",
- "delivery": 49,
+ "delivery": 59,
"freeDeliveryFrom": 800
Three kinds of lines:
- A line starting with
-, red on GitHub, is the line as it was before; this commit removed it. - A line starting with
+, green, was added in this commit. - A grey line with no sign is unchanged context, shown so you know where you are in the file.
Above each file GitHub shows counters such as +12 −3: twelve lines added and three removed in that file. They tell you the size of a change before you read it. A small +1 −1 is usually a one-value tweak; +300 −2 is a new page or feature.
In a terminal the same view comes from git show a41c09e, with the same plus and minus signs.
- Do
- "delivery": 49,then+ "delivery": 59,→ "delivery went from 49 to 59" - Don'tRead the grey context lines as part of the change
No sign means nothing changed there.
An edit is a minus and a plus
A diff works line by line and knows only two operations: a line was removed or a line was added. There's no separate "edited" type. So when Daniel changes 49 to 59, the diff shows the old line in red with - and the new line in green with +, one under the other.
To make edits easy to spot, GitHub also highlights the exact characters that differ inside the pair, so the 4 and the 5 stand out in a darker shade. That's your cue that this is one edited line, not two unrelated ones.
Two viewing options help with longer changes. The default unified view stacks old and new lines in one column. The Split view puts the old version on the left and the new one on the right, which many people find easier for edited paragraphs of copy.
When you report a change to someone, translate the pair back into plain words: "the delivery fee went from 49 to 59 UAH", not "one line removed, one added".
- Do"The delivery fee went from 49 to 59 UAH."
The minus/plus pair translated into plain words.
- Don't"Someone deleted the delivery price and then added a new one."
It's one edit, not a delete plus a separate addition.
Reading a bigger diff
Prices are one-liners; copy changes look richer. A reliable routine for any diff:
- Check the file name above the diff. The same sentence can live in two places, and a diff always belongs to one file.
- Read the minus lines first: what was there before.
- Read the plus lines: what's there now. A minus right before a similar plus is an edit; a plus with no partner is new text.
- Count and compare with the counters above the file.
Take this diff of pages/faq/delivery.md:
## Delivery
-Orders arrive in 2–3 days.
+Orders arrive in 1–2 days in Kyiv and 2–4 days elsewhere.
Free delivery from 800 UAH.
+Express delivery: 99 UAH.
The delivery-time sentence was rewritten (one minus, one plus), and the express-delivery line is new (a plus with no partner). The heading and the free-delivery line carry no sign, so they didn't change. Two lines added and one removed: +2 −1.
- DoMinus lines, then plus lines, then count; check the result against
+2 −1 - Don'tReport the unchanged "Free delivery from 800 UAH" line as part of the change
No sign, no change.
Blame: where did this line come from?
History works when you know the file. When you're staring at one specific line and want its story, switch the file view from Code to Blame.
Next to every line GitHub shows the last commit that changed it: the author, the commit message and the date. A colour strip down the side runs from older changes to newer ones, so recent edits stand out at a glance. Lines changed in the same commit are grouped together.

The name sounds harsh, but nobody uses it to point fingers. Developers run blame on their own code all the time, mostly to remember what they were thinking months ago. Read it as "where did this line come from?". In a terminal it's git blame config/prices.json; on GitHub it's one button.
Two things blame does not show: who first created the file (only the latest change per line), and who approved the change in review (that's in the pull request). And like everything in Git, it knows nothing about customers.
- Do"Where did
"delivery": 59come from?" →Blame→a41c09e, Daniel, 3 June - Don't"Blame says Daniel, so the delivery fee was his idea."
Blame says who changed the line, not who decided. The why is in the pull request and issue.
From a line to the reason
Blame gives you a commit, but a commit tells you only what and when. The why and who agreed live one or two clicks further. For CatChow's delivery price the full chain is:
- Open
config/prices.json. - Switch to
Blame: the line"delivery": 59points to commita41c09e. - Click the commit message. It ends with
(#128), a link to the pull request. - Open pull request #128: the discussion, the review and who approved it. Its description says "Closes #120".
- Read issue #120, opened by finance: "Courier rates up from 1 June".
a41c09e Raise delivery price to 59 UAH
What changed, who and when (found with Blame)
Raise delivery price after courier rate change#128
The discussion: why, who reviewed it, when it was merged
Courier rates up from 1 June#120
Where the request came from: finance
Line, commit, pull request, issue. Four clicks, and you have the whole story with finance's reasoning attached, instead of a week of asking around.
The chain only works if people leave links. That's why good developers end commit messages with the PR number and mention the issue in the pull request. When you write issues yourself, give them clear titles: you are the start of someone else's trail.
- DoLine →
Blame→ commita41c09e→ PR #128 → issue #120From what changed to why.
- Don'tStop at the commit and guess the reason
The reason is in the PR and the issue.
Links that keep the trail alive
The line → commit → pull request → issue chain depends on links, and GitHub makes them cheap.
Any # followed by a number, like #128, becomes a link to the issue or pull request with that number. Issues and pull requests share one numbering in a repository, so #120 might be either; open it to see which.
Some words do more than link. If a pull request description says Closes #120 (or Fixes #120, Resolves #120), GitHub links the two, and when the pull request is merged into the default branch, usually main, it closes issue #120 automatically. Closing isn't deleting: the issue stays, marked as completed, with a pointer to the pull request that finished it.
For a PM this is a free status update. You can watch the issue you care about instead of chasing developers, and when it closes, the linked pull request shows exactly what shipped. When you write issues, ask the team to reference them this way; it keeps the trail intact for the next person who asks "why?".
- DoPR description: "Raise delivery price after courier rate change. Closes #120"
Linked, and the issue closes on merge.
- Don'tPR description: "see the finance thing"
No link, so the trail stops here.
The blame trap
Blame shows the last commit that touched a line, and sometimes that commit didn't change the meaning at all. If Daniel re-indents the whole prices.json file, blame will show him on every line with "Reformat prices.json", even though he didn't change a single price.
So when blame points to a commit whose message says "reformat", "rename", "move" or "lint", don't stop there. Two ways to dig deeper:
- Next to each commit in the blame view there's a small icon,
View blame prior to this change. It shows the same file as it was just before that commit, with the earlier authors next to each line. Repeat until you reach a commit that really changed the value. - Or open the file's
Historyand look one step further back, reading the diffs.

Engineers have terminal options too, such as git blame -w, which ignores whitespace-only changes. The principle is the same everywhere: the history is all still there, and the latest touch isn't always the real change.
- DoBlame shows "Reformat prices.json" →
View blame prior to this change→ the commit that set 1000 - Don't"Blame shows the reformat, so Daniel set the threshold to 1000."
The latest touch isn't always the real change.
Ready to practise?
Answer the lesson's exercises and earn 250 points the first time you finish it.
Start the exercises