Git — History & Inspection
Git stores a complete, immutable history of every change ever made to your project. The ability to inspect, search, and analyze that history is one of Git's greatest strengths. Whether you are tracking down a bug, auditing changes, or recovering lost work, Git's inspection tools are indispensable.
This guide covers git log formatting and filtering, git blame for finding change origins, git bisect for regression hunting, and git reflog for recovery.
The git log command is the primary tool for viewing commit history. It offers extensive formatting options to display exactly the information you need.
| 1 | # Standard log with full details |
| 2 | git log |
| 3 | |
| 4 | # One commit per line (hash + subject) |
| 5 | git log --oneline |
| 6 | |
| 7 | # Show commit graph with one-line format |
| 8 | git log --oneline --graph |
| 9 | # * a1b2c3d Add payment feature |
| 10 | # * e4f5g6h Merge branch 'feature/login' |
| 11 | # |\ |
| 12 | # | * i7j8k9l Add login validation |
| 13 | # * | m0n1o2p Fix header styling |
| 14 | |
| 15 | # Custom format with --pretty |
| 16 | git log --pretty=format:"%h %an %ar: %s" |
| 17 | # a1b2c3d Alice 2 hours ago: Add payment feature |
| 18 | # e4f5g6h Bob 3 hours ago: Merge branch |
| 19 | |
| 20 | # Common format placeholders: |
| 21 | # %h - abbreviated commit hash |
| 22 | # %H - full commit hash |
| 23 | # %an - author name |
| 24 | # %ae - author email |
| 25 | # %ar - author date (relative) |
| 26 | # %s - subject (first line) |
| 27 | # %d - ref names (branches, tags) |
| 28 | # %p - parent hashes |
| 29 | # %C() - color (e.g., %C(red)) |
| 30 | |
| 31 | # Colorized custom format |
| 32 | git log --pretty=format:"%C(yellow)%h%Creset %C(cyan)%an%Creset %C(green)%ar%Creset %s" |
| 33 | |
| 34 | # Show with diffs |
| 35 | git log -p |
| 36 | |
| 37 | # Show last N commits |
| 38 | git log -5 |
| 39 | |
| 40 | # Show commits since a date |
| 41 | git log --since="2 weeks ago" |
| 42 | git log --after="2024-01-01" --before="2024-03-01" |
info
Git log supports powerful filtering to find specific commits by author, date, file, content, and more.
| 1 | # Filter by author |
| 2 | git log --author="Alice" |
| 3 | git log --author="Alice|Bob" # multiple authors |
| 4 | |
| 5 | # Filter by committer (different from author) |
| 6 | git log --committer="CI Bot" |
| 7 | |
| 8 | # Filter by date |
| 9 | git log --after="2024-01-01" --before="2024-06-01" |
| 10 | git log --since="2 weeks ago" --until="yesterday" |
| 11 | |
| 12 | # Filter by file path (changes to specific files) |
| 13 | git log -- src/app.js |
| 14 | git log -- src/ # all files in a directory |
| 15 | |
| 16 | # Filter by commit message |
| 17 | git log --grep="bugfix" |
| 18 | git log --grep="Fixes #123" --all-match |
| 19 | |
| 20 | # Filter by content (commits that changed text) |
| 21 | git log -S "function validate" --pickaxe-all |
| 22 | # -S searches for string additions/removals |
| 23 | # -G searches with regex |
| 24 | |
| 25 | # Filter by branch topology |
| 26 | git log --all # all branches |
| 27 | git log main..feature # commits in feature not in main |
| 28 | git log --branches --not --remotes=origin |
| 29 | |
| 30 | # Combine filters |
| 31 | git log --author="Alice" --since="1 month ago" --grep="payment" -- src/ |
git blame annotates each line of a file with the commit, author, and date of the last modification. This is invaluable for understanding why a line was written and finding the right person to ask about a change.
| 1 | # Show blame for every line of a file |
| 2 | git blame src/app.js |
| 3 | |
| 4 | # Output: |
| 5 | # a1b2c3d (Alice 2024-03-01 10:00:00 +0000 1) const app = express(); |
| 6 | # e4f5g6h (Bob 2024-03-02 14:30:00 +0000 2) app.use(logger()); |
| 7 | # a1b2c3d (Alice 2024-03-01 10:00:00 +0000 3) app.use(routes); |
| 8 | |
| 9 | # Show email instead of name |
| 10 | git blame -e src/app.js |
| 11 | |
| 12 | # Show blame for specific line range |
| 13 | git blame -L 10,30 src/app.js |
| 14 | |
| 15 | # Show the original commit's diff alongside blame |
| 16 | git blame -L 10,30 -s src/app.js |
| 17 | |
| 18 | # Ignore whitespace changes |
| 19 | git blame -w src/app.js |
| 20 | |
| 21 | # Find the commit that introduced a specific line |
| 22 | git blame -L 15,15 src/app.js |
| 23 | |
| 24 | # Show blame with the commit hash in full |
| 25 | git blame -l src/app.js |
| 26 | |
| 27 | # Detect moved or copied lines from other files |
| 28 | git blame -C src/app.js # within same file |
| 29 | git blame -C -C src/app.js # across all files in same commit |
| 30 | git blame -C -C -C src/app.js # across all commits |
pro tip
git bisect performs a binary search through your commit history to find the exact commit that introduced a bug. With a history of N commits, bisect can find the culprit in log(N) steps — finding a bug among 1,000 commits takes only 10 steps.
| 1 | # Start bisect |
| 2 | git bisect start |
| 3 | |
| 4 | # Mark the current commit as bad (contains the bug) |
| 5 | git bisect bad |
| 6 | |
| 7 | # Mark an old commit as good (before the bug appeared) |
| 8 | git bisect good v1.0.0 |
| 9 | |
| 10 | # Git checks out a commit halfway between good and bad. |
| 11 | # Test it and mark: |
| 12 | git bisect good # if this commit works |
| 13 | # OR |
| 14 | git bisect bad # if this commit has the bug |
| 15 | |
| 16 | # Repeat until Git identifies the first bad commit: |
| 17 | # a1b2c3d is the first bad commit |
| 18 | # commit a1b2c3d... |
| 19 | # Author: Alice <alice@example.com> |
| 20 | # Date: Mon Mar 4 10:00:00 2024 +0000 |
| 21 | # Add payment validation |
| 22 | |
| 23 | # End bisect when done |
| 24 | git bisect reset |
| 25 | |
| 26 | # Automated bisect with a test script |
| 27 | git bisect start HEAD v1.0.0 |
| 28 | git bisect run npm test |
| 29 | # Git automatically runs "npm test" at each step |
| 30 | # npm test exit code 0 = good, non-zero = bad |
best practice
The reflog ("reference log") records every movement of HEAD and branch pointers in your local repository — including commits made, rebases, resets, and merges. It is your safety net for recovering lost work.
| 1 | # View the reflog |
| 2 | git reflog |
| 3 | # a1b2c3d HEAD@{0}: commit: Add payment validation |
| 4 | # e4f5g6h HEAD@{1}: rebase: Add login form |
| 5 | # i7j8k9l HEAD@{2}: reset: moving to HEAD~1 |
| 6 | # m0n1o2p HEAD@{3}: commit: Add login form |
| 7 | # q3r4s5t HEAD@{4}: checkout: moving from main to feature |
| 8 | |
| 9 | # Show reflog for a specific branch |
| 10 | git reflog show feature |
| 11 | |
| 12 | # Show reflog with dates |
| 13 | git reflog --date=iso |
| 14 | |
| 15 | # Recover from a bad reset (go back to where you were) |
| 16 | git reset HEAD@{1} |
| 17 | |
| 18 | # Recover a lost commit (e.g., after rebase gone wrong) |
| 19 | git cherry-pick a1b2c3d # from reflog output |
| 20 | |
| 21 | # Create a branch from a reflog entry |
| 22 | git branch recovery HEAD@{2} |
| 23 | |
| 24 | # View reflog for a specific time window |
| 25 | git reflog --since="1 hour ago" |
| 26 | |
| 27 | # Clean up reflog entries older than 30 days |
| 28 | git reflog expire --expire=30.days --all |
| 29 | |
| 30 | # Reflog retention: |
| 31 | # - Commits reachable from a ref: 90 days |
| 32 | # - Commits not reachable: 30 days |
| 33 | # (configurable with gc.reflogExpire and gc.reflogExpireUnreachable) |
warning
git shortlog summarizes commits grouped by author, making it useful for release notes and contribution analysis.
| 1 | # Summary of commits grouped by author |
| 2 | git shortlog |
| 3 | # Alice (5): |
| 4 | # Add payment validation |
| 5 | # Fix login redirect |
| 6 | # Update tests |
| 7 | # Bob (3): |
| 8 | # Add user model |
| 9 | # Fix database migration |
| 10 | |
| 11 | # Show number of commits per author |
| 12 | git shortlog -sn |
| 13 | # 5 Alice |
| 14 | # 3 Bob |
| 15 | # 2 Charlie |
| 16 | |
| 17 | # Shortlog for a specific range (release notes) |
| 18 | git shortlog v1.0.0..v2.0.0 |
| 19 | |
| 20 | # Show commit count per author with email |
| 21 | git shortlog -sne |
| 22 | |
| 23 | # Count total commits |
| 24 | git rev-list --count HEAD |
| 25 | |
| 26 | # Count commits per author |
| 27 | git shortlog -sn --all |
| 28 | git shortlog -sn --since="2024-01-01" |
| 29 | |
| 30 | # Show total lines changed by author |
| 31 | git shortlog -sn --numbered |
| 32 | |
| 33 | # Contribution statistics |
| 34 | git diff --shortstat HEAD~10..HEAD |
Git provides both built-in visualization tools and integration with external GUI tools for exploring repository history.
| 1 | # ASCII graph visualization (built-in) |
| 2 | git log --graph --oneline --all --decorate |
| 3 | git log --graph --pretty=format:"%h %s%d" --all |
| 4 | |
| 5 | # Launch gitk (Tcl/Tk GUI browser) |
| 6 | gitk |
| 7 | gitk --all # show all branches |
| 8 | gitk --since="2 weeks ago" # recent history |
| 9 | |
| 10 | # Launch git gui (basic GUI tool) |
| 11 | git gui |
| 12 | |
| 13 | # External tools: |
| 14 | # - GitKraken: cross-platform Git GUI |
| 15 | # - Sourcetree: free Git GUI (Windows/Mac) |
| 16 | # - VS Code GitLens extension |
| 17 | # - GitHub Desktop |
| 18 | |
| 19 | # Custom pretty format for terminal visualization |
| 20 | git log --graph --pretty=format:'%C(yellow)%h%Creset %C(cyan)%an%Creset %C(green)%ar%Creset%C(auto)%d%Creset %s' |
| 21 | |
| 22 | # Create an alias for daily use |
| 23 | git config --global alias.tree "log --graph --oneline --all --decorate --simplify-by-decoration" |
Master git log Aliases
Create aliases for your most-used log formats. A good default is git config --global alias.lg "log --oneline --graph --all --decorate". It makes browsing history effortless.
Use bisect Early and Often
When a bug appears, start a bisect immediately — the more commits you have, the more effective binary search is. Combine with git bisect run and an automated test for maximum efficiency.
Check Reflog Before Panicking
Accidentally reset to the wrong commit? Rebasing went wrong? The reflog has your back. Run git reflog to find the commit you were at and reset to it. Almost nothing in Git is permanently lost for 30 days.
Write Good Commit Messages
The power of git log --grep and git shortlog depends entirely on commit message quality. Invest in writing clear, descriptive commit messages that explain why a change was made.