The reflog is git's breadcrumb trail, leading you back home

It's very easy to get lost—or feel like you're going to get lost—when you're embarking on more complicated branch operations in git. What branch are you on? What branch will you be on, once you've done this next step? Where are you at the end of a rebase? How do you find where you were, before you started rebasing?

What you need ideally is a "browser history" or "trail of breadcrumbs" detailing all the steps you've (recently) made in your local git repository. Git provides this, and it's called the reflog.

Accessing the reflog is simple enough: you type git reflog and you get a long list of—especially if you've been working on this repository a lot—all manner of confusing items. Some of them look like commit messages; some of them are clearly added in some automated fashion: but while advanced users can prune and manage the list in all sorts of complex ways, there's clearly a lot that needs explaining before we get to that stage.

This Atlassian tutorial explains the reflog thus:

Every time the current HEAD gets updated (by switching branches, pulling in new changes, rewriting history or simply by adding new commits) a new entry will be added to the reflog.

You should note that this definition is both:

  • more inclusive than you'd think: changes to HEAD that you don't explicitly instigate (but which git internals need to perform certain tasks) can add extra items into the reflog
  • less inclusive than you'd think: operations on branch and tag references, that don't actually impact on HEAD at all, won't appear in the reflog.

However, the fact that each movement of HEAD (which is, after all, "what have I got checked out right now?") is tracked in the reflog makes it a very powerful history of your movements up, down and around the repository's commit tree.

Here's an example of how different git actions can add items to the reflog, starting with a new repository and then merging, rebasing and eventually backing out of changes:

# 1. Creating a repository, making two commits, then moving between them
# Adds 4 items to reflog (=4)
git init
touch README.txt
git add README.txt
git commit # adds to reflog
vim README.txt
git commit -a # adds to reflog
git checkout HEAD~ # adds to reflog
git checkout master # adds to reflog
 
# 2. Checking out a new branch, making a commit, then merging into master
# Adds 4 items to reflog (=8)
git checkout -b alpha # adds to reflog
vim README.txt
git commit -a # adds to reflog
git checkout master # adds to reflog
git merge alpha # adds to reflog
 
# 3. Checking out a new branch; make two commits; rebase onto master as one
# Adds 6 items to reflog (=14)
git checkout alpha # adds to reflog
vim README.txt
git commit -a # adds to reflog
vim README.txt
git commit -a # adds to reflog
git rebase -i master # squashing 2nd commit; adds 3 items to reflog
 
# 4. Make a new commit on beta by mistake, then force branch back and
# create a new branch for that mistaken commit
# Adds 4 items to reflog (=18)
git checkout -b beta
vim README.txt
git commit # adds to reflog
git checkout HEAD~ # adds to reflog
git branch -f beta # does NOT add to reflog! HEAD is not moved.
# Now if you try to run gitk, gitx or git-log, you find you can no longer
# see the mistaken commit. But if it's in the reflog, you can check it out
# using the following reflog-specific syntax.
git checkout HEAD@{1} # adds to reflog
git checkout -b gamma # adds to reflog

Here's the resulting commit tree from the above operations:

       master   alpha   gamma
            |     |     |
o-----o-----o-----o-----o <- HEAD
            \     |
             \   beta
              \
               o-----o <- pre-rebase commits; eventually discarded

... and, finally, the corresponding reflog of all 18 items (note in reverse order!) with whitespace and comments added, and long commit hashes truncated with "[...]", purely to aid clarity:

# 4. Make a new commit on beta by mistake, then force branch back and
# create a new branch for that mistaken commit
9918a94 HEAD@{0}: checkout: moving from 9918a94[...] to gamma
9918a94 HEAD@{1}: checkout: moving from 1f29d28[...] to HEAD@{1}
1f29d28 HEAD@{2}: checkout: moving from beta to HEAD~
9918a94 HEAD@{3}: commit: Accidentally check into beta
 
# 3. Checking out a new branch; make two commits; rebase onto master as one
1f29d28 HEAD@{4}: rebase -i (finish): returning to refs/heads/beta
1f29d28 HEAD@{5}: rebase -i (squash): Committing 'one-beta' to README.txt
22b7c81 HEAD@{6}: rebase -i (start): checkout master
e6e448e HEAD@{7}: commit: Committing 'two-beta' to README.txt
22b7c81 HEAD@{8}: commit: Committing 'one-beta' to README.txt
33066d2 HEAD@{9}: checkout: moving from master to beta
 
# 2. Checking out a new branch, making a commit, then merging into master
33066d2 HEAD@{10}: merge alpha: Fast-forward
b9f989c HEAD@{11}: checkout: moving from alpha to master
33066d2 HEAD@{12}: commit: Committing 'one-alpha' to README.txt
b9f989c HEAD@{13}: checkout: moving from master to alpha
 
# 1. Creating a repository, making two commits, then moving between them
b9f989c HEAD@{14}: checkout: moving from 2beabb7[...] to master
2beabb7 HEAD@{15}: checkout: moving from master to 2beabb7
b9f989c HEAD@{16}: commit: Committing 'one' to README.txt
2beabb7 HEAD@{17}: commit (initial): First commit

Hopefully it should be clear by comparison of the three different representations above of what's happened, that the reflog is very much a comprehensive history of what you've been up to with the local HEAD reference. And hopefully you should also therefore be able to see that, by simple use of the HEAD@{...} references, you can feel comfortable that, if you relaly, really need it, you always have a breadcrumb trail leading you back to home or thereabouts.

(Now, have a look at some further references for the next level up in reflog and commit-object management!)