Floating Octothorpe

Recovering deleted branches in Git

Git is one of the most commonly used version control systems, unfortunately like many tools, it's easy to accidentally make a mistake using git. Thankfully it's normally possible to recover from mistakes; this post is going to go over recovering deleted branches.

Accidentally deleting a branch

For the example in this post I'm going to start with two branches, master and dev:

$ git lg
* b55c4f7 - (25 seconds ago) Update readme file - Floatingoctothorpe (HEAD -> master)
| * 48263c7 - (2 minutes ago) Add doc string - Floatingoctothorpe (dev)
| * 3edbcb2 - (3 minutes ago) Add hashbang - Floatingoctothorpe
| * ef873d8 - (3 minutes ago) Add __future__ import - Floatingoctothorpe
| * 19e4cc6 - (4 minutes ago) Add hello world example - Floatingoctothorpe
* 5a2a844 - (5 minutes ago) First commit - Floatingoctothorpe

Note: git lg is an alias which runs git log with a number of options. Full details are in an earlier Git tips post.

If you try to delete a branch in git which hasn't been merged using -d or --delete, git will normally refuse:

$ git branch -d dev
error: The branch 'dev' is not fully merged.
If you are sure you want to delete it, run 'git branch -D dev'.

However once you use -D the branch will be gone:

$ git branch -D dev
Deleted branch dev (was 48263c7).

From this point on the branch will no longer be referenced, and won't appear when running git lg or git rev-list --all:

$ git lg
* b55c4f7 - (76 seconds ago) Update readme file - Floatingoctothorpe (master)
* 5a2a844 - (6 minutes ago) First commit - Floatingoctothorpe (HEAD -> dev)

$ git rev-list --all

Recreating the branch

When you delete a branch git will print the commit hash of the branch being removed. Assuming you've still got the message on screen, the quickest way to restore the branch is to create a new branch using the commit hash as a reference:

$ git checkout -b dev 5a2a844
Switched to a new branch 'dev'

This is great if you still know the commit hash, but what happens if you don't? This is where git reflog can be very helpful. Git keeps a reference log which records when the tip of branches or other references are updated:

$ git reflog
b55c4f7 HEAD@{0}: commit: Update readme file
5a2a844 HEAD@{1}: checkout: moving from dev to master
48263c7 HEAD@{2}: commit: Add doc string
3edbcb2 HEAD@{3}: commit: Add hashbang
ef873d8 HEAD@{4}: commit: Add __future__ import
19e4cc6 HEAD@{5}: commit: Add hello world example
5a2a844 HEAD@{6}: checkout: moving from master to dev
5a2a844 HEAD@{7}: commit (initial): First commit

The log above shows the last time HEAD was on the dev branch, HEAD pointed to 5a2a844 (HEAD@{1}). This is the commit hash needed to restore the dev branch.

Alternatively another way to find the commit hash is to use git fsck. The --unreachable and --no-reflogs options can be used to find all orphaned commits:

$ git fsck --unreachable --no-reflogs
Checking object directories: 100% (256/256), done.
unreachable blob 44159b3954c15250affceed3e0a6cfb2e39f8bc8
unreachable commit 48263c7aa495e1a4af59b72c618157aae0488b5f
unreachable commit 19e4cc6ecb643a1e298cdf37e53d7729c8a349b6
unreachable blob e1cae4354c4b6ca62e885eb95452f702760ac35e
unreachable blob 21e219c5f12f81efe5efb059b645f8a24bf82119
unreachable commit ef873d8cd4eb33a33d5139b4ae7a1aa8bcbb8fda
unreachable blob 2fa275f9c5ee8d13f550e7aa602bf2310dc80639
unreachable commit 3edbcb28f2a8e6b40aa22915043f4652345b92e0

git show can then be used to find the correct commit:

$ git show 48263c7aa495e1a4af59b72c618157aae0488b5f
commit 48263c7aa495e1a4af59b72c618157aae0488b5f
Author: Floatingoctothorpe <[email protected]>
Date:   Fri Feb 23 07:47:59 2018 +0000

    Add doc string

diff --git a/hello.py b/hello.py
index 2fa275f..e1cae43 100644
--- a/hello.py
+++ b/hello.py
@@ -1,4 +1,5 @@
 #!/usr/bin/env python
+"""Print "Hello world" to stdout"""

 from __future__ import print_function
 print("Hello world")