What are refs & git reflog

Amit Prajapati
MindOrks
Published in
7 min readFeb 3, 2020

Today’s Inspiration

“Strive for progress, not perfection.”

Let’s get started… 👩‍💻

In the previous blog, we’ve seen how git store objects internally, which makes it fast and flexible. In this blog, we’ll talk about refs and reflog.

Refs

We want to manipulate objects quite often in Git, it’s important to know their hashes. You could run all your Git commands referencing each object’s hash, like git show d377d67, but that would require you to remember the hash of every object you want to manipulate.

To save you from having to memorize these hashes, Git has references, or refs. A reference is simply a file stored somewhere in .git/refs, containing the hash of a commit object.

Refs are stored as normal text files in the .git/refs directory. To explore the refs in one of your repositories, navigate to .git/refs. You should see the following structure, but it will contain different files depending on what branches, tags, and remotes you have in your repo:

Inside .git/refs directory

The heads directory defines all of the local branches in your repository. Each filename matches the name of the corresponding branch, and inside the file, you’ll find a commit hash. This commit hash is the location of the tip of the branch. To verify this, try running the following two commands from the root of the Git repository:

hash returned by the cat command

The commit hash returned by the cat command should match the commit ID displayed by git log.

To change the location of the master branch, all Git has to do is change the contents of the refs/heads/master file. Similarly, creating a new branch is simply a matter of writing a commit hash to a new file. This is part of the reason why Git branches are so lightweight compared to SVN.

Git consistently uses the term rev in plumbing commands as short for "revision" and generally meaning the 40-character SHA1 hash for a commit. The command rev-list for example prints a list of 40-char commit hashes for a branch or whatever.

The tags directory works the exact same way, but it contains tags instead of branches.

The remotes directory lists all remote repositories that you created with git remote as separate subdirectories. Inside each one, you’ll find all the remote branches that have been fetched into your repository.

Special Refs

In addition to the refs directory, there are a few special refs that reside in the top-level .git directory. They are listed below:

  • HEAD – The currently checked-out commit/branch.
  • FETCH_HEAD – The most recently fetched branch from a remote repo.
  • ORIG_HEAD – A backup reference to HEAD before drastic changes to it.
  • MERGE_HEAD – The commit(s) that you’re merging into the current branch with git merge.
  • CHERRY_PICK_HEAD – The commit that you’re cherry-picking.

These refs are all created and updated by Git when necessary. For example, The git pull command first runs git fetch, which updates the FETCH_HEAD reference. Then, it runs git merge FETCH_HEAD to finish pulling the fetched branches into the repository. Of course, you can use all of these like any other ref, as I’m sure you’ve done with HEAD.

These files contain different content depending on their type and the state of your repository. The HEAD ref can contain either a symbolic ref, which is simply a reference to another ref instead of a commit hash, or a commit hash. For example, take a look at the contents of HEAD when you’re on the master branch:

This will give output ref: refs/heads/master, which means that HEAD points to the refs/heads/master ref. This is how Git knows that the master branch is currently checked out. If you were to switch to another branch, the contents HEAD would be updated to reflect the new branch. But, if you were to check out a commit instead of a branch, HEAD would contain a commit hash instead of a symbolic ref. This is how Git knows that it’s in a detached HEAD state.

Have you ever faced a problem, where you have lost a branch, whose source code was not yet merged in the ‘release’ branch or the ‘main’ branch? What if you want to regenerate a deleted branch though its work has already been merged into the main branch? Well, the only solution to such scenarios is Git Reflog.”

Through this article on Git Reflog, I will help you understand the scenario in which your work on a branch could be lost and how to recover the branch. Also, this article will highlight the approach you could take to prevent the unintended loss of a branch while working in a large project.

Consider a scenario, where a maintainer has to merge many features from different collaborators and then delete them eventually; but the branch is deleted accidentally before the work could be merged?

What is Git Reflog?

The reflog is Git’s safety net. It records almost every change you make in your repository such as creating (branches or tags). You can think of it is a chronological history of everything you’ve done in your local repo. Reference logs such as the commit snapshot of when the branch was created or cloned, checked-out, renamed, or any commits made on the branch are maintained by Git and listed by the ‘reflog’ command.

A Git reflog is a list of hashes, which represent where you have been during commits. Each time a branch is updated to point to a new reference, an entry is written in the reflog to say where you were. Since the branch is updated whenever you commit, the git reflog has a nice effect of storing your local developer’s history.

Note: The branch will be recoverable from your working directory only if the branch ever existed in your local repository i.e. the branch was either created locally or checked-out from a remote repository in your local repository for Git to store its reference history logs.

This command has to be executed in the repository that had the lost branch. If you consider the remote repository situation, then you have to execute the reflog command on the developer’s machine who had the branch.

Let’s understand the concept with an example:

Initializes a repo with one commit
Created a new ‘dev-branch’

Created another branch named “dev-branch” with one commit.

Now, let us list the branches which are not merged into master.

no merged branch

We have a ‘dev-branch’ branch which is not yet merged.

If you try to delete one of the branches with un-finished work say “dev-branch” branch, git display a warning message.

Warning while deleting the branch

Consider a scenario where a developer merges the feature branch into the main branch locally and then deletes the feature branch using the ‘git branch’ command with the “-d” flag as seen in the earlier screenshots.

It could also happen that the developer decides to trash the changes on the branch and decides to delete the branch without merging it with any other branch using the following command:

git branch -D  ‘branch_name’

With the above command, the developer forcefully deletes the branch by overriding the git warning.

branch deleted

Now, the ‘dev-branch’ branch will no longer be listed when you run the ‘git branch’ command. So, your work saved on this branch will be lost.

We left with the only master branch

Recover a deleted branch using Git Reflog

If the maintainer cannot recover the branch, then the owner of the branch who deleted it must recover from his/her local reflogs.

Step 1: History logs of all the references

Get a list of all the local recorded history logs for all the references (‘master’ and ‘dev-branch’) in this repository.

local repo history

Step 2: Identify the history stamp

As you can refer from the above snapshot, the Highlighted commit id: 8db9460 along with the HEAD pointer index:1 is the one when ‘dev-branch’ branch was created from the current HEAD pointer pointing to your latest work with the latest commit in ‘dev-branch’ branch.

Step 3: Recover

To recover back the ‘dev-branch’ branch use the command ‘git checkout’ passing the HEAD pointer reference with the index id — 1. This is the pointer reference when ‘dev-branch’ branch was created long commit id highlighted in the output screenshot.

‘dev-branch’ branch recovered

Yay! ‘dev-branch’ branch is recovered back with all of our source code.

Now, that you know how to restore a branch, let me tell you what work is restored when a deleted branch is recovered.

What work is restored when the deleted branch is recovered?

The files which were stashed and saved in the stash index list will be recovered back. Any untracked files will be lost. Also, it is a good idea to always stage and commit your work or stash them.

To open reflog manual page use the command git reflog --help .

Thanks for reading. Soon I will post some more articles on git.

Till then happy learning and keep reading!

--

--