๐ฐ๏ธ History Management and Navigation
Every commit you've ever made is a searchable, navigable dataset โ a time machine for your codebase. This lesson shows you how to interrogate that history with git log filters, travel to any point in it, hunt down the exact commit that introduced a bug, safely rewrite messy history, and rescue work you thought was lost.
๐ฏ Learning Objectives
By the end of this lesson, you will be able to:
- Explore history with
git logfilters by author, date, content, file, and message - Reference and navigate to any commit using HEAD, relative refs (
HEAD~2,HEAD^2), and hashes - Find the commit that introduced a bug with
git bisectandgit blame - Rewrite history safely with
--amendand interactive rebase โ and know when not to - Recover lost commits, branches, and stashes using the reflog
Estimated Time: 35โ45 minutes โข Difficulty: Intermediate
Hands-on: Recover a commit destroyed by a hard reset using only the reflog.
In This Lesson
History as a Tool
Most beginners treat Git history as a passive receipt โ proof that they saved their work. But history is far more valuable than that. It records who changed what, when, and (if you write good messages) why. Learn to query it and you can answer questions like "when did this function stop working?", "who wrote this odd line and what were they thinking?", and "how do I get back the branch I deleted an hour ago?"
๐ก Reframe it: Git isn't a backup drive, it's a database of your project's evolution โ and git log is the query language. The rest of this lesson teaches you to read, search, travel through, and repair that database.
Exploring with git log
Plain git log lists commits newest-first with hash, author, date, and message. In a real repository that's a firehose, so the skill is filtering.
Shaping the output
git log -n 5 # only the 5 most recent
git log --oneline # one compact line per commit
git log --graph # ASCII branch/merge graph
git log --stat # files changed + insertion/deletion counts
git log -p # full patch (the actual diff) per commit
# A comprehensive, all-branch overview
git log --oneline --graph --all --decorate
Filtering to find exactly what you need
| Goal | Command |
|---|---|
| By author | git log --author="Ray" |
| By date range | git log --since="2 weeks ago" --until="yesterday" |
| By message text | git log --grep="bug fix" |
| By added/removed code string | git log -S"function login" |
| By regex in changes | git log -G"TODO" |
| Touching one file | git log -- path/to/file.js |
These compose. To see Ray's changes to the components folder in the last month:
git log --author="Ray" --since="last month" -- src/components/
๐ -S vs -G โ the "pickaxe"
-S"text" finds commits where the count of that exact string changed (it was added or removed) โ perfect for "when did this function first appear?" -G"regex" is broader: it matches any commit whose diff contains lines matching the pattern.
Custom formats and aliases
You can shape each line precisely with --pretty=format: and save frequent commands as aliases:
# Hash ยท relative date ยท subject ยท refs ยท author
git log --pretty=format:"%h %ar %s%d [%an]"
# Save it as a reusable alias: git lg
git config --global alias.lg "log --graph --oneline --decorate --all"
Common format specifiers: %h short hash, %an author name, %ar relative date, %s subject, %d ref names.
Finding Bugs: bisect & blame
Two commands turn history into a debugging superpower.
git blame โ who wrote this line, and when?
git blame path/to/file.js
For each line, blame shows the commit, author, and date that last changed it โ the fastest way to find the context (and the commit message) behind a puzzling line before you touch it.
git bisect โ binary-search for the breaking commit
Suppose something works in an old version but is broken now, and hundreds of commits sit between. Testing each one is hopeless. bisect does a binary search: mark one good and one bad commit and Git repeatedly checks out the midpoint for you to test, halving the search each round.
git bisect start
git bisect bad # current commit is broken
git bisect good v1.2.0 # this old tag worked
# Git checks out a midpoint. Test it, then tell Git:
git bisect good # ...if this one works
git bisect bad # ...or if it's broken
# Repeat until Git names the first bad commit, then:
git bisect reset # return to where you started
Over 1,000 commits, bisect finds the culprit in about 10 tests instead of 1,000. If you have a script that exits non-zero when broken, git bisect run ./test.sh automates the whole hunt.
Rewriting History Safely
Sometimes you need to fix history before sharing it: a typo in a message, a forgotten file, or five scratch commits that should be one. Git can rewrite history โ but remember the golden rule from the last lesson: only rewrite commits you haven't shared.
Amending the last commit
# Fix the most recent commit message
git commit --amend -m "Correct, clearer message"
# Add a file you forgot, keeping the same message
git add forgotten-file.js
git commit --amend --no-edit
--amend doesn't edit the old commit โ it replaces it with a new one, so never amend something already pushed to a shared branch.
Interactive rebase for deeper cleanup
git rebase -i HEAD~5 # curate the last 5 commits
In the editor you can reword messages, squash/fixup related commits together, drop commits, reorder lines to reorder commits, or edit a commit to stop and change its content:
# Squash three commits into one clean commit:
pick abc1234 Add login form
squash def5678 Fix typo in form
squash ghi9012 Adjust form styling
If you must touch shared history
- Communicate with the team first so no one is mid-work on it.
- Push with
git push --force-with-lease, never bare--forceโ it refuses if someone else has pushed, protecting their work. - Prefer creating a new branch with the cleaned history over rewriting the shared one.
โ ๏ธ Removing secrets from history is harder than deleting a file
If a password or key was ever committed, deleting it in a new commit is not enough โ it still lives in history. Purge it from every commit with git-filter-repo or the BFG Repo-Cleaner, then rotate the secret, because it must be assumed already compromised.
Recovery with the Reflog
Here's the reassuring truth that makes Git safe to experiment with: it's very hard to truly lose committed work. Git records every position HEAD has occupied in the reflog, even commits no branch points to anymore. That's your safety net.
git reflog # every place HEAD has been, most recent first
git reflog show main # the reflog for a specific branch
HEAD@{N} reference or hash.Common rescues
# Undo a hard reset that "lost" commits
git reflog # find the hash from before the reset
git switch -c recovery a1b2c3d # rebuild a branch at that commit
# Recover a branch you deleted before merging
git reflog # locate its last commit
git switch -c recovered-branch a1b2c3d
# Restore a file deleted in an earlier commit
git log --diff-filter=D --summary # find the deleting commit
git checkout a1b2c3d^ -- path/to/deleted.js # ^ = the commit BEFORE deletion
โ The mantra
Before panicking that you've destroyed work, run git reflog. Nine times out of ten the commit is sitting right there, waiting for a new branch to point at it.
Hands-on Exercise
๐๏ธ Rescue a Commit from a Hard Reset
Objective: Destroy a commit with git reset --hard, then bring it back using only the reflog.
Instructions:
- Create a repo, commit a file, then make a second commit you'll "lose".
- Run
git reset --hard HEAD~1to throw away the second commit. - Confirm it's gone from
git log. - Use
git reflogto find the lost commit's hash. - Create a branch at that hash to recover the work, and verify it's back.
๐ก Hint
After the reset, the "lost" commit still appears in git reflog as HEAD@{1}. Point a new branch at that reference (or its hash) and the commit is yours again.
โ Solution
mkdir reflog-demo && cd reflog-demo
git init -b main
echo "line 1" > notes.txt
git add notes.txt && git commit -m "First commit"
echo "line 2" >> notes.txt
git commit -am "Second commit (about to be lost)"
git reset --hard HEAD~1 # second commit gone from the branch
git log --oneline # only 'First commit' remains
git reflog # spot: HEAD@{1} ... Second commit
git switch -c recovered HEAD@{1}
git log --oneline # 'Second commit' is back on 'recovered'
๐ฏ Quick Quiz
Question 1: You want to find commits where the exact string connectToDatabase was added or removed. Which flag is built for that?
Question 2: A feature worked at tag v1.0 but is broken now, 400 commits later. What's the efficient way to find the breaking commit?
Question 3: You ran git reset --hard and a commit vanished from git log. What's your best first move?
Summary & Quiz
๐ Key Takeaways
git logis a query language: filter by author, date, message (--grep), and code (-S/-G), and save favourites as aliases.- Name any commit with HEAD, relative refs (
~walks back,^Npicks a parent), hashes, tags, or times โ thenswitch/restoreto travel there. - bisect binary-searches for the commit that broke something; blame reveals who last changed each line.
- Rewrite unshared history with
--amendand interactive rebase; on shared history, communicate and use--force-with-lease. - The reflog is your safety net โ most "lost" work is one
git reflogaway from recovery.
๐ Further Reading
- Pro Git โ Viewing the Commit History
- Pro Git โ Revision Selection
- git bisect documentation
- Semantic Versioning Specification
- git-filter-repo (rewriting history at scale)
๐ What's Next?
You've mastered Git locally โ branching, integrating, and navigating history. Next we move from your machine to the wider world: the GitHub platform, where repositories become collaborative and your work meets the rest of the team.
๐ Nice work!
You can now read, search, and repair your project's entire timeline with confidence.