This is a story about a mistake I made, the five worst seconds of my career, and the command that saved me.
I tell it because everyone who works with Git long enough eventually does something like this. And most people don’t know about git reflog until after they’ve needed it.
What happened
It was a Friday. (Of course it was a Friday.)
I’d been working on a feature branch for most of the week. Real work — a payment flow rewrite that touched 15 files. Not committed cleanly, just a pile of in-progress changes that I was going to clean up before opening the PR.
My terminal had three tabs open. Main repo, a worktree I’d made for a hotfix, and a third tab I’d forgotten about that was sitting on main.
I wanted to get a clean slate on the hotfix worktree. I ran:
git reset --hard HEAD
Then looked up at my terminal and realized.
Wrong tab. I was on main in the main repo.
The reset didn’t touch the hotfix. It wiped my working tree. Everything that wasn’t committed — an entire week of payment flow changes — was gone.
I stared at it for a moment. Ran git status. Clean working tree. No, not clean. Empty. Five days of work, just gone.
The five seconds of panic
Here’s the thing about git reset --hard: it feels permanent. The command doesn’t ask if you’re sure. It just does it. And the changes you lose weren’t committed, so they don’t show up in git log. They’re just… not there.
I opened GitHub. Nothing — I hadn’t pushed. I checked my editor’s undo history. It had reset when VS Code reloaded the files. I checked if there was an autosave somewhere. There wasn’t.
Then I remembered something I’d read once, somewhere, about git reflog.
What git reflog actually is
git log shows you the history of commits on a branch. git reflog shows you the history of where HEAD has pointed — every checkout, every reset, every commit, every merge, going back typically 90 days.
git reflog
Output:
a1b2c3d HEAD@{0}: reset: moving to HEAD
f4e5d6c HEAD@{1}: commit: WIP: payment flow -- checkout page
9g8h7i6 HEAD@{2}: commit: WIP: payment flow -- cart integration
2j3k4l5 HEAD@{3}: commit: WIP: payment flow -- Stripe setup
...
There they were.
The reset was HEAD@{0}. Before that, my last commit was HEAD@{1} — “WIP: payment flow — checkout page”. I’d been committing as WIP. Messy WIP commits, but commits. Recent ones.
I ran:
git reset --hard HEAD@{1}
And then stared at my working tree.
Everything was back. The checkout page changes. The cart integration. The Stripe setup. All of it.
I opened my terminal and just sat there for a minute.
How to use git reflog properly
# See the full reflog
git reflog
# See reflog for a specific branch
git reflog show feature/my-branch
# Recover to a specific point
git reset --hard HEAD@{3} # go back 3 positions
# Or create a new branch from a reflog entry (safer)
git checkout -b recovery-branch HEAD@{3}
# If you need to find lost commits by content
git fsck --lost-found
# Look in .git/lost-found/commit/ for dangling commits
Reflog entries look like HEAD@{N} where N is how many moves ago it happened. HEAD@{0} is the current state. HEAD@{1} is one move back.
What reflog can and can’t recover
Can recover:
- Commits you made and then reset away
- Branches you deleted
- States from stash pops gone wrong
- Anything that was committed, even briefly
Cannot recover:
- Untracked files (things you created but never added to git)
- Changes that were never committed (if you had changes not yet committed before the reset)
This is the important part. My changes were in WIP commits — ugly, unfinished, but committed. If I hadn’t committed anything, git reflog couldn’t have helped me. The changes would be gone.
What I changed after
1. Commit more aggressively, even ugly WIP commits
The lesson: committed is recoverable. Uncommitted is not. I now commit WIP even when the code is embarrassing. I clean it up with interactive rebase before opening a PR.
git commit -m "WIP: do not merge"
# Later, before PR:
git rebase -i HEAD~5 # squash and clean up
2. Use worktrees instead of multiple terminal tabs
The mistake was possible because I had the same repo open in multiple tabs and lost track of which branch I was on. Worktrees make it visually distinct — different folders, different terminal windows, different PS1 prompts.
git worktree add ../myapp-hotfix -b hotfix/thing main
Now the hotfix has its own folder. I can’t accidentally run commands against the wrong branch.
3. Add a shell alias that shows the branch
My PS1 now shows the current git branch. Before this incident, it didn’t.
Add to ~/.zshrc or ~/.bashrc:
# Show git branch in prompt
parse_git_branch() {
git branch 2>/dev/null | sed -n '/\* /s///p'
}
export PS1="\u@\h \W \[\033[32m\]\$(parse_git_branch)\[\033[00m\] $ "
Or use a framework like Oh My Zsh that includes this by default.
4. Protected main with pre-push hooks
For the main branch specifically, I added a pre-push hook that asks for confirmation:
# .git/hooks/pre-push
#!/bin/bash
current_branch=$(git symbolic-ref HEAD | sed 's!refs/heads/!!')
if [ "$current_branch" = "main" ]; then
read -p "You're pushing to main. Are you sure? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
chmod +x .git/hooks/pre-push
The commands to know before you need them
# See everything that happened to HEAD
git reflog
# Go back N steps
git reset --hard HEAD@{N}
# Safer: create new branch from a reflog point, inspect, then decide
git checkout -b rescue HEAD@{N}
# Find the timestamp of each reflog entry
git reflog --date=iso
# If a branch was deleted
git reflog | grep "branch-name"
git checkout -b branch-name HEAD@{N}
The thing nobody tells you
Git is not as destructive as it feels. Most of the time, the data is still there — it’s just not where you were looking for it. git reset --hard feels catastrophic. It isn’t, as long as you’ve been committing.
git reflog is your undo button for the last 90 days of git operations. Learn it before you need it, so when you do need it you remember it exists.
It’s there. It saved me a week of work. On a Friday.
I committed everything after that.
Related: Git Cheat Sheet | Git Worktree: Work on Two Branches at the Same Time
Related Reading.
Git Worktree: Work on Two Branches at the Same Time
Stop stashing mid-feature to fix a hotfix. Git worktree lets you check out multiple branches into separate folders from the same repo — with real scenarios and examples.
Git Cheat Sheet: Every Command You Actually Need
Complete Git reference with copy-paste commands for setup, staging, branching, merging, rebasing, stashing, undoing mistakes, remotes, and log inspection.
I Used Claude to Review My Code for a Week. Here Is What It Caught.
A week-long experiment using Claude as a daily code reviewer on a real Node.js project — bugs found, security issues caught, where it was wrong, and what changed.