Git Reflog: Recover Deleted Branches and Lost Commits
Table of Contents
Deleted a branch with commits on it? Botched a rebase? Reset something you shouldn't have? The git reflog command is Git's last resort: it records every move your references make, which means "lost" commits are almost never actually lost. In this article you'll learn how to read the reflog, and then use it to recover a deleted branch, first the hard way with plumbing commands, then the easy way with a single merge.
All the content from our Boot.dev courses are available for free here on the blog. This one is the "Reflog" chapter of Learn Git 2. If you want to try the far more immersive version of the course, do check it out!
What Is HEAD in Git?
Before the reflog makes any sense, you need to know what HEAD is. See, branches are references to commits, and HEAD is a reference to the branch you're currently on. All this to say, in Grug speak:
HEADmean where me at now
If you're on the main branch, HEAD is pointing to main. Like all things in Git's internals, HEAD is just stored in a file. You can check it yourself from the root of any repo:
cat .git/HEAD
# ref: refs/heads/main
Every time you switch branches, commit, reset, or rebase, HEAD moves. The reflog is the record of all that moving around.
What Is Git Reflog?
The git reflog command (pronounced ref-log, not re-flog) is kinda like git log but stands for "reference log" and specifically logs the changes to a "reference" that have happened over time.
Click to play video
Reflog uses a different format to show the history of a branch or HEAD: one that's more concerned with the number of steps back in time. For example:
| reflog | meaning |
|---|---|
HEAD@{0} |
where HEAD is now |
HEAD@{1} |
where HEAD was 1 move ago |
HEAD@{2} |
where HEAD was 2 moves ago |
HEAD@{3} |
where HEAD was 3 moves ago |
Say you create a new branch called slander off of main and commit a new file, slander.md, to it. The reflog reads like a play-by-play of everywhere HEAD has been:
git reflog
# 971a99c (HEAD -> slander) HEAD@{0}: commit: add slander.md
# f9c9f51 (main) HEAD@{1}: checkout: moving from main to slander
# f9c9f51 (main) HEAD@{2}: commit: add README.md
Git Reflog vs Git Log
git log walks the commit history of your current branch: each commit's parents, all the way back. git reflog doesn't care about ancestry, it cares about movement. It records checkouts, commits, resets, merges, and rebases, including moves to commits that no longer belong to any branch.
That last part is the important bit. If a commit isn't reachable from any branch, git log can't show it to you. The reflog still can.
One more difference worth knowing: the reflog is local only. It's not part of the repository history, it never gets pushed, and everyone's reflog is different. It's your personal paper trail, not your entire team's.
How Do You Lose a Commit in the First Place?
Have you ever:
- Shipped a bug to production (
mainbranch) - Created a new branch to fix the bug (
fix-bug) - Fixed the bug and committed the changes
- Switched back to
main - Accidentally deleted the
fix-bugbranch
Yeah, me neither...
For, uh, educational purposes, here's how that plays out with our slander branch. To delete a branch locally while on a different branch, use git branch -D:
git switch main
git branch -D slander
# Deleted branch slander (was 971a99c).
The 971a99c commit is now unreachable: no branch points to it, and it won't show up in git log. All is lost!
Or is it?
How to Recover a Deleted Branch With Git Reflog
The reflog keeps track of where all of your references have been, and deleting a branch doesn't delete its commits. Time to put that to the test.
First, find the commit hash that corresponds to the change you lost:
git reflog
# f9c9f51 (HEAD -> main) HEAD@{0}: checkout: moving from slander to main
# 971a99c HEAD@{1}: commit: add slander.md
# f9c9f51 (main) HEAD@{2}: checkout: moving from main to slander
There it is: 971a99c, safe and sound. Now we can drop down to Git's plumbing with git cat-file -p, which pretty-prints any object in the Git database. Give it the commit hash to find its tree:
git cat-file -p 971a99c
# tree 5b21d4f
# parent f9c9f51
# author ThePrimeagen <[email protected]>
Then the tree hash to find the blob for the file we lost:
git cat-file -p 5b21d4f
# 100644 blob 9d8e7f6 README.md
# 100644 blob a1b2c3d slander.md
And finally the blob hash to get the file contents back, redirected straight into a recreated file:
git cat-file -p a1b2c3d > slander.md
git add .
git commit -m "recover slander.md"
The deleted file is back on main, contents intact.
The Easy Way: Merge a Commitish
Using Git internals is exceptionally inconvenient. We had to copy/paste and use the cat-file command 3 times!
I would not recommend doing it that way, but I wanted to drive home the point that you can always drop down to the plumbing commands if needed.
Luckily, there is a better way. The git merge command actually takes a "commitish" as an argument:
git merge <commitish>
A "commitish" is something that looks like a commit (branch, tag, commit, HEAD@{1}).
In other words, instead of:
git reflog # find the commit sha at HEAD@{1}
git cat-file -p <commit sha>
git cat-file -p <tree sha>
git cat-file -p <blob sha> > slander.md
git add .
git commit -m "recover slander.md"
We could have just done:
git merge HEAD@{1}
The more you know!
Since reflog entries are commitishes, this trick works anywhere a commit does: you could also recreate the branch itself with git branch slander 971a99c, or cherry-pick the lost commit onto another branch. If you want to get comfortable with these recovery workflows (plus stash, bisect, and worktrees), that's exactly what Learn Git 2 is for, and if you're still shaky on the fundamentals, start with Learn Git.
Frequently Asked Questions
What does git reflog show?
git reflog shows every move a reference like HEAD has made: commits, checkouts, resets, merges, and rebases. It includes moves to commits that no longer belong to any branch, which git log can't show you.
What is the difference between git log and git reflog?
git log walks the commit ancestry of your current branch. git reflog records the movement of your references over time, including commits that are no longer reachable from any branch.
Can git reflog recover a deleted branch?
Yes. Find the lost commit's hash in the reflog output, then run git merge with that hash, recreate the branch with git branch branch_name hash, or cherry-pick the commit onto another branch.
How long does git reflog keep entries?
By default, reachable reflog entries expire after 90 days and unreachable entries after 30 days. Git prunes them with git reflog expire and git gc, so don't wait months to recover lost work.
Is the reflog shared when I push to a remote?
No. The reflog is local only. It never gets pushed, and every clone of a repository has its own independent reflog.
