DEV Community

a2n
a2n

Posted on

Recovering a deleted Git branch without crying too much

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 reflog and git 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
Enter fullscreen mode Exit fullscreen mode

We will use main as our base branch.

If your Git does not create main by default, you can run:

git branch -M main
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Now we create a new branch.

git checkout -b deleteme
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

At this point, our commit exists only on the deleteme branch.

Now let's go back to main.

git checkout main
Enter fullscreen mode Exit fullscreen mode

And now we do the bad thing.

git branch -D deleteme
Enter fullscreen mode Exit fullscreen mode

Git will say something like:

Deleted branch deleteme (was abc1234).
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Here, abc1234 is the commit we want.

You can use the SHA directly:

git show abc1234
Enter fullscreen mode Exit fullscreen mode

Or sometimes use the reflog reference:

git show HEAD@{1}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

This will show the commit object.

Something like:

tree 1234567
parent def5678
author ...
committer ...

add file that will be deleted
Enter fullscreen mode Exit fullscreen mode

The important part here is the tree SHA.

Now we can inspect the tree:

git cat-file -p 1234567
Enter fullscreen mode Exit fullscreen mode

And Git will show the files in that commit.

You may see something like:

100644 blob 9876543    README.md
100644 blob 5555555    imdeleted.md
Enter fullscreen mode Exit fullscreen mode

Now we found the blob SHA for imdeleted.md.

So we can print the content:

git cat-file -p 5555555
Enter fullscreen mode Exit fullscreen mode

And it gives us:

oupsy
Enter fullscreen mode Exit fullscreen mode

At this point, we can recreate the file manually:

echo "oupsy" > imdeleted.md
git add imdeleted.md
git commit -m "recover deleted file"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

If everything looks good, you can commit:

git commit
Enter fullscreen mode Exit fullscreen mode

But if you realize that this is not what you wanted, you can abort the merge:

git merge --abort
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Let's say the commit is:

abc1234
Enter fullscreen mode Exit fullscreen mode

Instead of merging the whole branch/history, we can cherry-pick only this commit:

git cherry-pick abc1234
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

And we get:

oupsy
Enter fullscreen mode Exit fullscreen mode

Nice.

If the deleted branch had multiple commits, you could cherry-pick them one by one:

git cherry-pick abc1234
git cherry-pick def5678
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Or safer:

git merge --no-commit --no-ff <commit>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)