Skip to main content

๐Ÿ•ฐ๏ธ 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 log filters 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 bisect and git blame
  • Rewrite history safely with --amend and 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

GoalCommand
By authorgit log --author="Ray"
By date rangegit log --since="2 weeks ago" --until="yesterday"
By message textgit log --grep="bug fix"
By added/removed code stringgit log -S"function login"
By regex in changesgit log -G"TODO"
Touching one filegit 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
The reflog as a trail of recent HEAD positions Four commits labelled HEAD at 3, 2, 1, and 0, showing that HEAD@0 is current and older positions remain recoverable. D E F G HEAD@{3} HEAD@{2} HEAD@{1} HEAD@{0} โ€” now
Figure 1 โ€” Each entry in the reflog is a place HEAD used to be. Even after a bad reset or rebase, the old commit is still reachable by its 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.

Tags & Releases

Tags mark meaningful points in history โ€” almost always releases. Unlike branches, a tag never moves once created.

# Lightweight tag โ€” just a name for a commit
git tag v1.0.0

# Annotated tag โ€” carries a message, author, and date (use for releases)
git tag -a v1.0.0 -m "First stable release"

git tag                     # list tags
git show v1.0.0             # inspect a tag and its commit

# Tags are NOT pushed automatically:
git push origin v1.0.0      # push one tag
git push origin --tags      # push all tags

Pair tags with Semantic Versioning โ€” MAJOR.MINOR.PATCH โ€” so the number itself communicates the change:

  • MAJOR โ€” incompatible/breaking changes
  • MINOR โ€” new, backward-compatible functionality
  • PATCH โ€” backward-compatible bug fixes

On platforms like GitHub, pushing a tag lets you attach formal Releases โ€” release notes and downloadable binaries built on top of that tagged commit.

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:

  1. Create a repo, commit a file, then make a second commit you'll "lose".
  2. Run git reset --hard HEAD~1 to throw away the second commit.
  3. Confirm it's gone from git log.
  4. Use git reflog to find the lost commit's hash.
  5. 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 log is 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, ^N picks a parent), hashes, tags, or times โ€” then switch/restore to travel there.
  • bisect binary-searches for the commit that broke something; blame reveals who last changed each line.
  • Rewrite unshared history with --amend and interactive rebase; on shared history, communicate and use --force-with-lease.
  • The reflog is your safety net โ€” most "lost" work is one git reflog away from recovery.

๐Ÿ“š Further Reading

๐Ÿš€ 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.