History: Reading the Past
This chapter teaches you to read the past: log options, show, blame, bisect, and searching for when a bug entered the codebase.
git log, Properly This Time
Chapter 2 covered the basics. Here's more of what makes it useful.
Useful Options
git log --oneline # one commit per line
git log --graph # draw the branch graph
git log --all # include all branches, not just HEAD
git log --decorate # show branch and tag labels
git log --stat # file counts per commit
git log -p # show diffs (the patch)
git log -5 # last 5 commits
Combine them:
git log --oneline --graph --decorate --all
This is the single most useful git log invocation. Alias it (Chapter 11).
Filters
git log --since="2 weeks ago"
git log --until="yesterday"
git log --author="Ada"
git log --grep="rate limit" # match commit messages
git log -S"oldFunctionName" # commits that added or removed this string
git log -G"regex_here" # commits whose diff matches the regex
-S is called the pickaxe. It's the fastest way to answer "when did this function name appear or disappear?"
Paths
Restrict to a path:
git log -- src/auth/
git log -p -- src/auth/login.ts
git log --follow -- src/auth/login.ts # follow the file through renames
The -- separates paths from refs. It's often optional; always including it removes ambiguity.
Pretty Formats
For scripting or customized output:
git log --pretty=format:"%h %an %ar %s"
Common placeholders:
%H full commit hash
%h abbreviated hash
%an author name
%ae author email
%ad author date (respects --date= option)
%ar author date, relative ("2 weeks ago")
%s subject
%b body
%d ref names (like --decorate)
Set your favorite as an alias:
git config --global alias.lg "log --graph --pretty=format:'%C(auto)%h %ad | %s%d [%an]' --date=short"
Now git lg gives a dense, readable view.
git show: One Commit, In Detail
git show <sha> # message + full diff
git show <sha> --stat # message + file stats
git show <sha>:path # the file's content at that commit
That last one is handy: git show HEAD~3:src/auth.ts prints the file exactly as it was three commits ago. Pipe it to less, diff, whatever.
git blame: Who Changed This Line?
Line-by-line history:
git blame src/auth/login.ts
3f1a22c (Ada Lovelace 2026-04-19) export function login(email, password) {
9b2f11a (Grace Hopper 2026-04-21) if (!email || !password) {
9b2f11a (Grace Hopper 2026-04-21) throw new Error("missing credentials");
9b2f11a (Grace Hopper 2026-04-21) }
3f1a22c (Ada Lovelace 2026-04-19) ...
Each line shows the commit that last touched it, the author, and the date.
Blame a specific range:
git blame -L 10,20 src/auth/login.ts
Blame is for tracing a line back to its commit, not for finger-pointing. The commit tells you what was happening when the line was written; the PR usually tells you why.
When Blame Lies
git blame points at the commit that last touched the line. A pure reformat hides the real author:
git blame -w src/auth/login.ts # ignore whitespace-only changes
If someone moved a block of code, use --follow on git log, or look at the full history of the file:
git log -p --follow -- src/auth/login.ts
git bisect: Binary Search for a Bug
Something broke. You don't know when. bisect walks the history with binary search and finds the exact commit.
git bisect start
git bisect bad # current commit is bad
git bisect good v1.4.0 # this tag was known good
Git checks out a commit halfway between. Test whatever feature is broken:
npm test
Tell git:
git bisect good # or "bad"
Git narrows the range and checks out another commit. Repeat until git announces:
a1b2c3d is the first bad commit
Finish:
git bisect reset # go back to where you started
Automated Bisect
If you have a test script that returns 0 for "good" and non-zero for "bad", let git drive:
git bisect start HEAD v1.4.0
git bisect run ./test.sh
It runs your script on each candidate, finds the offending commit, and tells you which one. Bisecting 1000 commits this way takes about 10 test runs.
Searching Across History
Find when a line entered
git log -S"specificString" -- src/
Find every commit that touched a file
git log --follow -- path/to/file
Find commits by message content
git log --grep="migration"
git log --grep="migration" --oneline
Find the commit for a tag
git rev-list -n 1 v1.0.0
What's different between two branches?
git log main..feature/login # commits in feature/login not in main
git log feature/login..main # the other direction
git log main...feature/login # commits in either, not both (symmetric difference)
The two-dot and three-dot notations are distinct. Two dots: "reachable from right, not left". Three dots: "reachable from exactly one".
git shortlog
Group commits by author. Useful for release notes.
git shortlog -sn # author counts, sorted
git shortlog v1.0.0..HEAD # summary of changes since the last release
42 Ada Lovelace
19 Grace Hopper
8 Alan Turing
Common Pitfalls
Running git log in a huge repo and walking every commit. Use -n 20 or filters.
Confusing .. and ... ranges. Read the manual entry for "specifying revisions"; it's one of the best pages in git help.
Blaming without -w. Whitespace reformats wipe out author history. Always blame with -w unless whitespace is the point.
Treating blame as blame. Writing "who broke this" at the top of an email above git blame output is a career-limiting move. The commit tells you what, not why.
Next Steps
Continue to 06-rebase.md to reshape history before you share it.