Skip to main content

Git History: Log, Diff, Blame, and Bisect

·1618 words·8 mins
Linux Learning Lab
Author
Linux Learning Lab
Writing about code, tools, and workflows.
Table of Contents
git-essentials - This article is part of a series.
Part 3: This Article

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 log
commit 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 handling

Compact 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=short
9f4a2b1 Add password reset endpoint
3a1b2c3 Fix session timeout handling
a8b7c6d Refactor auth middleware

Limiting 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.js

Without --follow, log stops at the rename. With it, Git traces the file back through renames.

Custom format
#

git log --format="%h %an %ar %s"
PlaceholderShows
%hShort commit hash
%HFull commit hash
%anAuthor name
%aeAuthor email
%arRelative date (3 days ago)
%adAuthor date
%sSubject (first line of message)
%bBody (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.js

That 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 diff

Staging area vs last commit
#

# What's staged and ready to commit?
git diff --staged

Between two commits
#

git diff abc1234 def5678

Between 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 --stat

Single file between commits
#

git diff abc1234 def5678 -- src/auth.js

What changed in a specific commit
#

# Compare a commit to its parent
git diff 9f4a2b1~1 9f4a2b1

# Shorthand — same thing
git show 9f4a2b1

Word-level diff
#

# Highlight changed words, not whole lines
git diff --word-diff

Useful 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.js
9f4a2b1c (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.js

Ignore whitespace changes
#

git blame -w src/auth.js

If 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.js

To make this permanent for the repo:

git config blame.ignoreRevsFile .git-blame-ignore-revs

Dig deeper — who wrote it before that commit?
#

# Show blame before a specific commit
git blame 9f4a2b1~1 -- src/auth.js

Git 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
#

  1. You tell Git a “bad” commit (where the bug exists) and a “good” commit (where it didn’t)
  2. Git checks out the midpoint
  3. You test and report good or bad
  4. Git narrows the range and repeats
  5. 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.0

Git 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 good

Repeat 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 handling

Finish bisecting
#

git bisect reset

This 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.sh

The 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 uses

Skip untestable commits
#

If a commit doesn’t compile or can’t be tested:

git bisect skip

Git picks a nearby commit instead.

View bisect log
#

git bisect log

Shows 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 reflog
9f4a2b1 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 def5678

Recover 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 abc1234

Practical 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.js

The -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
#

TaskCommand
View historygit log --oneline
Show a commitgit show abc1234
File at a point in timegit show abc1234:path/file
Diff working vs stagedgit diff
Diff staged vs committedgit diff --staged
Diff two branchesgit diff main..feature
Blame a filegit blame file
Blame specific linesgit blame -L 10,20 file
Start bisectgit bisect start / bad / good
Auto bisectgit bisect run ./test.sh
End bisectgit bisect reset
View refloggit reflog
Search for string in diffsgit log -S "text"
Commits by authorgit log --author="name"
Commits touching filegit log -- path/file

Best Practices
#

  • Use git log --oneline --graph to quickly orient yourself in a project’s history
  • Use git blame to understand context before changing code — the commit message often explains why something is written that way
  • Set up .git-blame-ignore-revs for formatting commits — it keeps blame useful after large reformats
  • Use git bisect instead 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 reflog when something goes wrong — lost commits are almost always recoverable within 90 days
  • Use git diff --stat before reading a full diff — it tells you which files changed so you can focus on what matters
  • Combine git log with -- and a path when investigating a specific file — ignore all the noise from other parts of the project
git-essentials - This article is part of a series.
Part 3: This Article