git · level 2

History

git log, bisect, reflog, cherry-pick, and rewriting history safely.

225 XP

Reading and Rewriting History

Git's history tools are underused. Most engineers know git log and stop there. The commands below change how you debug.

Analogy

Reading history with git is like a detective reviewing a building's security footage. git log is skimming the timestamps of every camera event; git bisect is the binary-search move of jumping to the middle of the tape, asking "was the vase already broken here?", then jumping to the middle of whichever half still contains the break — ten jumps pinpoint one incident in a thousand hours of tape. The reflog is the building's private service-elevator log that security never publishes, but that records every floor anyone stepped to — your safety net when the public tape was edited. revert is filming a reverse-action clip and splicing it in after the bad scene; rebase -i is re-editing the master tape itself, which is fine on your own copy but catastrophic if everyone else already got a duplicate DVD of the original.

git log power options

git log --oneline --graph is the starting point. Go further:

# Show only commits that touched a specific file
git log --oneline -- src/auth/session.ts

# Show commits by author in the last month
git log --oneline --author="sam" --since="1 month ago"

# Search commit messages
git log --oneline --grep="fix(api)"

# Show what changed in each commit (patch mode)
git log -p --follow src/auth/session.ts

# Show statistics (files changed, insertions, deletions)
git log --stat

# Pretty-print with custom format
git log --format="%h %ae %s" --since="1 week ago"

The --follow flag is important: it tracks a file across renames, which is common during refactors.

git bisect

Bisect is a binary search over your commit history. You tell git which commit is "good" (no bug) and which is "bad" (has the bug), and it checks out the midpoint. You test and mark it. Repeat until git identifies the first bad commit.

git bisect start
git bisect bad                      # current commit is bad
git bisect good v1.2.0              # last known good tag or SHA

# git checks out the midpoint commit
# run your test
git bisect good                     # or: git bisect bad

# git checks out another midpoint
# repeat until git prints:
# "a1b2c3d is the first bad commit"

git bisect reset                    # return to HEAD

For large repos, automate the test:

git bisect run pnpm test -- --testPathPattern=auth.spec

Git will run the command at each step. A zero exit code means "good", non-zero means "bad". Bisecting 1,000 commits takes at most 10 steps (log₂ 1000 ≈ 10).

git reflog

The reflog is a local-only log of everywhere HEAD has been, in order. It records branch switches, rebases, resets, commits — including commits that no longer appear on any branch.

git reflog
# e7a3c2d HEAD@{0}: commit: fix(auth): guard nil session
# b4f9a1c HEAD@{1}: rebase: fast-forward
# 0d8a4b5 HEAD@{2}: checkout: moving from feat/a to main
# ...

If you accidentally reset to the wrong commit, or drop a stash that had work you needed, the reflog is where you recover it. Any SHA in the reflog can be checked out, branched from, or cherry-picked.

git cherry-pick

Cherry-pick applies the changes from one commit (or a range) onto the current branch, as a new commit with a new SHA.

git cherry-pick a1b2c3d             # apply one commit
git cherry-pick v1.2.0..v1.3.0     # apply a range
git cherry-pick a1b2c3d --no-commit # apply changes without committing (for editing)

Common use: a bug fix lands on main, but you need it backported to a release branch without merging all of main's other changes.

git checkout release/1.4
git cherry-pick a1b2c3d             # the specific fix commit

git revert

git revert creates a new commit that undoes the changes of a previous commit. It does not rewrite history — the bad commit stays, but its effect is cancelled.

git revert a1b2c3d                  # creates a revert commit
git revert HEAD~3..HEAD             # revert the last three commits
git revert a1b2c3d --no-commit      # stage the reversal without committing

Use revert on commits already pushed to shared branches. Revert is safe; reset is not (when the commits are shared).

Rewriting history safely

These commands rewrite history — they generate new SHAs. Only use them on commits that have not been pushed to a shared branch, or on your own fork.

Interactive rebase

git rebase -i HEAD~5    # open an editor for the last 5 commits

The editor shows a list of commits and actions:

pick a1b2c3d feat: add login page
pick e4f5a6b fix: correct redirect URL
pick c7d8e9f wip: half-done user profile
pick 0a1b2c3 fixup: typo in class name
pick d4e5f6a feat: add profile page

Change actions:

Action Effect
pick Keep the commit as-is
reword Keep changes, edit the message
edit Pause rebase here to amend the commit
squash Fold into the previous commit, combine messages
fixup Fold into the previous commit, discard this message
drop Delete this commit entirely

Fixup commits

A fixup is a micro-commit that belongs with a previous commit:

git add -p                              # stage the correction
git commit --fixup=a1b2c3d              # creates: fixup! feat: add login page
git rebase -i --autosquash HEAD~5       # automatically arranges and squashes fixups

--autosquash reorders the list so fixup commits end up right after the commit they belong to, then applies the fixup action automatically. This is the cleanest way to amend mid-stack commits.

The bisect simulator

The playground below gives you a seeded 12-commit repository with a regression planted at one commit. Use the bisect step-controller to mark commits good or bad and watch the state machine narrow in on the first bad commit.