Git blame alternatives: how to find why code changed
git blame answers one narrow question: which commit last touched this line. That's often not what you want. The last change was a reformat, a rename or a file move, and the real decision is five commits further back. Here are the tools that answer the question you're actually asking: why did this change, and when?
First, make blame itself less useless
Before reaching for alternatives, turn on the flags most people never use:
git blame -wignores whitespace-only changes.git blame -C -C -Cdetects lines moved or copied from other files, even across commits.git blame --ignore-rev <sha>skips a specific noisy commit. Put bulk reformat commits in.git-blame-ignore-revsand setgit config blame.ignoreRevsFile .git-blame-ignore-revs; GitHub and GitLab both respect that file in their blame views.git blame <sha>^ -- pathre-runs blame as of the parent of a commit, so you can step backwards one layer at a time.
1. git log -L: the history of a range or a function
This is the most underrated command in git. It shows every commit that touched a range of lines, with the diff for just those lines:
git log -L 40,60:src/auth/session.ts
Or, for a whole function, if git can find its boundaries:
git log -L :refreshToken:src/auth/session.ts
Blame gives you one layer. -L gives you the whole stack, oldest change at the bottom, and it follows the lines as they shift around the file.
2. The pickaxe: -S and -G
When you know the value but not where it used to live, search the diffs themselves:
git log -S "SESSION_TTL" --onelinefinds commits where the number of occurrences of that string changed, which is to say where it was added or removed.git log -G "retry.*backoff" --onelinefinds commits whose diff has a line matching a regex, including ones that only modified it.
Add -p to see the patches, and --all to search branches that were never merged. The pickaxe is how you find the commit that deleted something, which blame can't show at all.
3. git log --follow for renamed files
git log --follow -p -- src/billing/invoice.py keeps tracking a single file across renames. Without --follow, history stops at the rename and it looks as if the file was born yesterday.
4. git bisect when you know behaviour, not code
Sometimes you don't know which line changed. You only know that something behaved differently in March. git bisect binary-searches the history: mark one commit good, one bad, and test the midpoints until it names the commit that changed the behaviour. With a script, git bisect run ./check.sh does it unattended. It finds changes that no text search will.
5. From commit to pull request
A commit tells you what changed. The reason is usually in the pull request and its review. To find it:
gh pr list --search <sha> --state mergedon GitHub.- The commit page on GitHub or GitLab links to the merge request that contained it.
- Squash merges usually carry the PR number in the title, like
(#2210).
Then read the review comments, including resolved threads. That's where "we tried X and it broke Y" tends to live. The full walkthrough is in why is this code like this?
6. Editor and GUI tools
| Tool | Good for | Limits |
|---|---|---|
| GitHub / GitLab blame view | Clicking back through older blame layers; links straight to the PR | One file at a time; no pickaxe |
| GitLens (VS Code) | Inline blame, line history, file history without leaving the editor | Still shows commits, not the discussion behind them |
| JetBrains "Annotate" and "Show History for Selection" | The git log -L experience with a UI | Same: commit-level, not PR-level |
tig | Fast terminal blame; press , to jump to the parent commit's blame | Terminal-only, learning curve |
A workflow that usually works
- Blame with
-w -C -C. If it lands on noise, ignore that rev. - Still unclear?
git log -Lon the range to see every layer. - Know the value but not the place? Pickaxe with
-S. - Know the behaviour but not the code?
git bisect. - Found the commit? Go to the PR and read the review thread.
- Found the reason? Leave a comment with the PR link next to the code.
The catch: every tool above finds a commit. None of them finds the reason. That's in PR reviews, tickets and chat, which git doesn't know about.
Where Enhanciar fits
That last hop, from commit to reason, is what Enhanciar is for. It reads commits, pull requests, review comments and docs, and answers "why did this change?" in plain language with the file:line and PR it's based on, so you can open the source and judge it. It's in early access. If you're new to a codebase, how to understand a legacy codebase fast covers the wider playbook.
Join the waitlist at enhanciar.in →
Related guides
- Why is this code like this? How to find the reason behind any line
- How to understand a legacy codebase fast
- Onboarding engineers to a large codebase
FAQ
What is a better alternative to git blame?
git log -L shows every change to a line range or function, not just the last one. The pickaxe (git log -S or -G) finds commits that added or removed a string, and git bisect finds the commit that changed behaviour.
How do I find why code changed, not just who changed it?
Find the commit with blame, log -L or the pickaxe, then find the pull request that merged it and read its review comments and linked ticket. The reason is usually there rather than in the commit message.
How do I make git blame skip formatting commits?
List those commits in a .git-blame-ignore-revs file and run git config blame.ignoreRevsFile .git-blame-ignore-revs. Also pass -w to ignore whitespace and -C -C to follow moved code.
How do I find a commit that deleted a line?
Use git log -S with the deleted text. Blame cannot show deleted lines, but the pickaxe finds the commit where that string disappeared.