So today let's talk about one of those little Git moment where you go:
"Ok, I deleted the branch… wait… there was something in it."
The good news is: Git is usually not deleting your commit immediately. The branch name is gone, but the commit object can still be found for a while. And with a little bit of Git archaeology, we can bring it back.
We are going to create the problem first, then fix it in three ways:
- the wizard way with
git reflogandgit cat-file - the merge way
- the cherry-pick way, which is probably the best one here
Setting up the disaster
First, let's create a fresh Git repository.
mkdir git-recover-branch
cd git-recover-branch
git init
We will use main as our base branch.
If your Git does not create main by default, you can run:
git branch -M main
Let's create a first commit so we have something clean to come back to.
echo "hello" > README.md
git add README.md
git commit -m "initial commit"
Now we create a new branch.
git checkout -b deleteme
And we add a very important file.
Well… "important".
echo "oupsy" > imdeleted.md
git add imdeleted.md
git commit -m "add file that will be deleted"
At this point, our commit exists only on the deleteme branch.
Now let's go back to main.
git checkout main
And now we do the bad thing.
git branch -D deleteme
Git will say something like:
Deleted branch deleteme (was abc1234).
And now the branch is gone.
But the commit is not necessarily gone.
That is the important part.
The wizard way
The first way is the "wizard" way.
Not because it's the best way, but because it looks like you're doing some kind of forbidden Git magic.
We can use:
git reflog
git reflog is basically Git saying:
"I remember where HEAD has been recently."
So even if the branch name is deleted, Git may still remember when you were on that branch, and what commit you made.
You should see something like:
abc1234 HEAD@{1}: commit: add file that will be deleted
def5678 HEAD@{2}: checkout: moving from main to deleteme
Here, abc1234 is the commit we want.
You can use the SHA directly:
git show abc1234
Or sometimes use the reflog reference:
git show HEAD@{1}
Now let's say we want to inspect what is inside this commit without restoring it yet.
We can use:
git cat-file -p abc1234
This will show the commit object.
Something like:
tree 1234567
parent def5678
author ...
committer ...
add file that will be deleted
The important part here is the tree SHA.
Now we can inspect the tree:
git cat-file -p 1234567
And Git will show the files in that commit.
You may see something like:
100644 blob 9876543 README.md
100644 blob 5555555 imdeleted.md
Now we found the blob SHA for imdeleted.md.
So we can print the content:
git cat-file -p 5555555
And it gives us:
oupsy
At this point, we can recreate the file manually:
echo "oupsy" > imdeleted.md
git add imdeleted.md
git commit -m "recover deleted file"
And boom, we recovered the content.
This is cool because you understand what Git is actually storing: commits, trees, and blobs.
But also… let's be honest, this is not the way I want to recover files every day.
It works, but it feels like opening Git with a screwdriver.
The merge way
Since we found the commit SHA with git reflog, another option is to merge that commit.
For example:
git merge abc1234
This will bring the changes from that commit into your current branch.
And in our small example, it works pretty well.
But there are some problems with this solution.
First, git merge is not really saying:
"I want this one commit."
It is saying:
"I want to merge this history into my current branch."
In our case, this is simple because there is only one commit. But in a real project, the deleted branch may contain multiple commits, merge commits, or changes you do not really want.
So you can end up bringing more than expected.
If you want to prepare the merge but not commit it directly, you can use:
git merge --no-commit --no-ff abc1234
This tells Git:
- do the merge
- do not create the merge commit yet
- do not fast-forward
So now you can inspect the result.
git status
git diff --staged
If everything looks good, you can commit:
git commit
But if you realize that this is not what you wanted, you can abort the merge:
git merge --abort
So the merge way is useful, but maybe a little bit too big of a hammer for this problem.
Especially when all we want is one commit.
The better way: cherry-pick
And now we get to the clean solution.
We already found the commit SHA with:
git reflog
Let's say the commit is:
abc1234
Instead of merging the whole branch/history, we can cherry-pick only this commit:
git cherry-pick abc1234
And that is pretty much the perfect tool for this situation.
git cherry-pick means:
"Take this commit and apply it here."
Not the branch.
Not the full history.
Just this commit.
So in our case, Git will apply the commit that added imdeleted.md onto main.
You can verify it:
ls
cat imdeleted.md
And we get:
oupsy
Nice.
If the deleted branch had multiple commits, you could cherry-pick them one by one:
git cherry-pick abc1234
git cherry-pick def5678
Or even a range, if that makes sense for your case.
But the nice thing is that you stay in control.
You decide exactly what you want to recover.
So what should I use?
For me, the order is pretty simple.
If I only need to inspect the old content, I can use:
git reflog
git show <commit>
git cat-file -p <sha>
That is the wizard way. Great to understand Git. Great when you want to look inside objects. Maybe not the cleanest workflow.
If I want to bring back a whole deleted branch or a bigger piece of history, I can use:
git merge <commit>
Or safer:
git merge --no-commit --no-ff <commit>
Then inspect, commit, or abort.
But if I deleted a branch and I just want to recover one commit, I will usually use:
git cherry-pick <commit>
Because it does exactly what I mean.
And that is the nice thing with Git: most of the time, when something looks deleted, it's not really deleted yet.
You just need to know where Git put the little map.
And for that, git reflog is your friend.

Top comments (0)