Why History Matters#
Git records every change ever made to your project. That history isn’t just a backup — it’s a debugging tool, an audit trail, and a way to understand why code looks the way it does today.
When something breaks, you can find exactly when it broke. When code looks wrong, you can find out who wrote it and why. When you need to undo a change from three weeks ago, you can extract just that change without losing everything since.
Git Log#
git log is the starting point for exploring history. It shows commits in reverse chronological order — most recent first.
Basic log#
git logcommit 9f4a2b1c8d3e5f6a7b8c9d0e1f2a3b4c5d6e7f8a
Author: Mike <mike@example.com>
Date: Tue Jul 29 10:30:00 2026 -0400
Add password reset endpoint
commit 3a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b
Author: Alice <alice@example.com>
Date: Mon Jul 28 15:45:00 2026 -0400
Fix session timeout handlingCompact formats#
# One commit per line
git log --oneline
# One line with graph
git log --oneline --graph
# One line with date
git log --oneline --format="%h %ad %s" --date=short9f4a2b1 Add password reset endpoint
3a1b2c3 Fix session timeout handling
a8b7c6d Refactor auth middlewareLimiting output#
# Last 10 commits
git log -10
# Commits from the last week
git log --since="1 week ago"
# Commits between dates
git log --after="2026-07-01" --before="2026-07-15"
# Commits by author
git log --author="Mike"
# Commits with a keyword in the message
git log --grep="fix"
# Commits that changed a specific file
git log -- src/auth.js
# Commits that changed a specific function (searches diff content)
git log -p -S "getUserById"Following file renames#
git log --follow -- src/new-name.jsWithout --follow, log stops at the rename. With it, Git traces the file back through renames.
Custom format#
git log --format="%h %an %ar %s"| Placeholder | Shows |
|---|---|
%h | Short commit hash |
%H | Full commit hash |
%an | Author name |
%ae | Author email |
%ar | Relative date (3 days ago) |
%ad | Author date |
%s | Subject (first line of message) |
%b | Body (rest of message) |
Git Show#
git show displays the details of a single commit — the metadata and the diff:
# Show the latest commit
git show
# Show a specific commit
git show 9f4a2b1
# Show just the files changed (no diff)
git show --stat 9f4a2b1
# Show a file at a specific commit
git show 9f4a2b1:src/auth.jsThat last form is powerful — you can view any file as it existed at any point in history without switching branches or checking anything out.
Git Diff#
git diff compares things. What you compare depends on the arguments.
Working directory vs staging area#
# What have I changed but not staged?
git diffStaging area vs last commit#
# What's staged and ready to commit?
git diff --stagedBetween two commits#
git diff abc1234 def5678Between branches#
# What's in feature that isn't in main?
git diff main..feature/login
# Just the file names
git diff main..feature/login --name-only
# Summary (insertions/deletions per file)
git diff main..feature/login --statSingle file between commits#
git diff abc1234 def5678 -- src/auth.jsWhat changed in a specific commit#
# Compare a commit to its parent
git diff 9f4a2b1~1 9f4a2b1
# Shorthand — same thing
git show 9f4a2b1Word-level diff#
# Highlight changed words, not whole lines
git diff --word-diffUseful for prose, documentation, or long lines where a small change makes a whole-line diff hard to read.
Stat output#
git diff --stat src/auth.js | 15 +++++++++------
src/routes.js | 3 +++
tests/auth.js | 28 ++++++++++++++++++++++++++++
3 files changed, 40 insertions(+), 6 deletions(-)Quick overview of what changed and where without reading the full diff.
Git Blame#
git blame shows who last modified each line of a file and when. The name sounds harsh — it’s really about finding context.
git blame src/auth.js9f4a2b1c (Mike 2026-07-29 10:30) function resetPassword(email) {
3a1b2c3d (Alice 2026-07-28 15:45) const user = findUser(email);
3a1b2c3d (Alice 2026-07-28 15:45) if (!user) throw new Error('Not found');
a8b7c6d5 (Mike 2026-07-25 09:00) const token = generateToken();
9f4a2b1c (Mike 2026-07-29 10:30) sendResetEmail(user, token);
9f4a2b1c (Mike 2026-07-29 10:30) return { success: true };Each line shows: commit hash, author, date, and the line content.
Blame a specific range#
# Only lines 10-25
git blame -L 10,25 src/auth.js
# From line 10 to end of function
git blame -L 10,+15 src/auth.jsIgnore whitespace changes#
git blame -w src/auth.jsIf someone reformatted the file, -w skips those commits and shows who wrote the actual logic.
Ignore specific revisions#
Large refactors or formatting commits make blame less useful. Skip them:
# Ignore a specific commit
git blame --ignore-rev abc1234 src/auth.js
# Ignore multiple (list them in a file)
echo "abc1234" >> .git-blame-ignore-revs
echo "def5678" >> .git-blame-ignore-revs
git blame --ignore-revs-file .git-blame-ignore-revs src/auth.jsTo make this permanent for the repo:
git config blame.ignoreRevsFile .git-blame-ignore-revsDig deeper — who wrote it before that commit?#
# Show blame before a specific commit
git blame 9f4a2b1~1 -- src/auth.jsGit Bisect#
git bisect performs a binary search through history to find the exact commit that introduced a bug. Instead of checking commits one by one, it halves the search space each step.
How it works#
- You tell Git a “bad” commit (where the bug exists) and a “good” commit (where it didn’t)
- Git checks out the midpoint
- You test and report good or bad
- Git narrows the range and repeats
- After log₂(n) steps, it finds the exact commit
Manual bisect#
# Start bisecting
git bisect start
# Current commit (HEAD) is broken
git bisect bad
# This older commit was working fine
git bisect good v1.0.0Git checks out a commit halfway between. Test it, then:
# If the bug is present in this commit
git bisect bad
# If the bug is NOT present
git bisect goodRepeat until Git identifies the first bad commit:
abc1234 is the first bad commit
commit abc1234
Author: Mike <mike@example.com>
Date: Wed Jul 23 14:00:00 2026 -0400
Refactor session handlingFinish bisecting#
git bisect resetThis returns you to where you were before bisecting.
Automated bisect#
If you have a test or script that can detect the bug:
git bisect start
git bisect bad HEAD
git bisect good v1.0.0
git bisect run ./test-for-bug.shThe script should exit 0 (good) or 1 (bad). Git runs it automatically at each step and reports the offending commit without any manual intervention.
# Example test script
#!/bin/bash
make test-auth 2>/dev/null
# Exit code from the test is what bisect usesSkip untestable commits#
If a commit doesn’t compile or can’t be tested:
git bisect skipGit picks a nearby commit instead.
View bisect log#
git bisect logShows the steps taken so far — useful if you make a mistake and need to replay.
Reflog — Your Safety Net#
git reflog records every time HEAD moves — commits, checkouts, rebases, resets. Even if you lose a commit, it’s in the reflog for 90 days.
git reflog9f4a2b1 HEAD@{0}: commit: Add password reset endpoint
3a1b2c3 HEAD@{1}: checkout: moving from feature to main
abc1234 HEAD@{2}: commit: WIP experiment
def5678 HEAD@{3}: reset: moving to def5678Recover a lost commit#
If you accidentally reset or deleted a branch:
# Find the commit in reflog
git reflog
# Recover it
git checkout abc1234
# Or create a branch pointing to it
git branch recovered-work abc1234Practical Investigations#
“When did this line change?”#
git blame src/config.js -L 42,42
# Shows who last touched line 42 and when
# See the full commit
git show abc1234“What changed in the last release?”#
git log v1.1.0..v1.2.0 --oneline
git diff v1.1.0..v1.2.0 --stat“Who’s been working on this file?”#
git shortlog -sn -- src/auth.js 8 Mike
3 Alice
1 Bob“What was this file like last week?”#
git show HEAD@{1.week.ago}:src/auth.js“Find the commit that deleted a function”#
git log -p -S "function oldHelper" -- src/utils.jsThe -S flag (pickaxe) finds commits where the given string was added or removed.
“What commits are on main that aren’t deployed?”#
git log deployed-tag..main --oneline“Find all commits that touched the auth module”#
git log --oneline -- src/auth/ tests/auth/Quick Reference#
| Task | Command |
|---|---|
| View history | git log --oneline |
| Show a commit | git show abc1234 |
| File at a point in time | git show abc1234:path/file |
| Diff working vs staged | git diff |
| Diff staged vs committed | git diff --staged |
| Diff two branches | git diff main..feature |
| Blame a file | git blame file |
| Blame specific lines | git blame -L 10,20 file |
| Start bisect | git bisect start / bad / good |
| Auto bisect | git bisect run ./test.sh |
| End bisect | git bisect reset |
| View reflog | git reflog |
| Search for string in diffs | git log -S "text" |
| Commits by author | git log --author="name" |
| Commits touching file | git log -- path/file |
Best Practices#
- Use
git log --oneline --graphto quickly orient yourself in a project’s history - Use
git blameto understand context before changing code — the commit message often explains why something is written that way - Set up
.git-blame-ignore-revsfor formatting commits — it keeps blame useful after large reformats - Use
git bisectinstead of manually checking commits — it’s logarithmic, not linear, so it handles even thousands of commits quickly - Use
-S "string"(pickaxe) to find when something was added or removed — faster than reading every diff - Use
git reflogwhen something goes wrong — lost commits are almost always recoverable within 90 days - Use
git diff --statbefore reading a full diff — it tells you which files changed so you can focus on what matters - Combine
git logwith--and a path when investigating a specific file — ignore all the noise from other parts of the project


