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.