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!)