I think we all know, and hopefully love Git. Git is very powerful and even so, a lot of people (including myself) haven’t experienced the full power of Git. I for myself haven’t used reflog or even heard about it for a long time. However, reflogs can probably become really important.
What is this reflog?
Git describes reflogs as:
Reference logs, or “reflogs”, record when the tips of branches and other references were updated in the local repository. Reflogs are useful in various Git commands, to specify the old value of a reference.
Sure, this sounds a bit complicated, but in the end it’s fairly simple:
For every “update” we do in our local repository, Git will create a reference log entry.
An example
Let’s say you’ve a git project with some files in it:
-rw-r--r-- 1 dbarton staff 6 9 Dec 14:02 file1 -rw-r--r-- 1 dbarton staff 6 9 Dec 14:03 file2 -rw-r--r-- 1 dbarton staff 6 9 Dec 14:03 file3
We check the status of our local repository and all looks fine:
$ git status On branch develop nothing to commit, working tree clean
Now we want to add a new feature and therefor we create a new branch:
$ git checkout -b awesome-feature Switched to a new branch 'awesome-feature'
Let’s say our feature needs a new file and a change on an existing one:
$ git status On branch awesome-feature Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: file3 Untracked files: (use "git add <file>..." to include in what will be committed) file4 no changes added to commit (use "git add" and/or "git commit -a")
Looks fine, so let’s stage the files and commit them into our awesome-feature branch:
$ git add file3 file4 $ git status On branch awesome-feature Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: file3 new file: file4 $ git commit -m"awesome feature" [awesome-feature a760e09] awesome feature 2 files changed, 3 insertions(+) create mode 100644 file4
Time flies and we’ve to work on another topic now, however our feature isn’t finished yet. So we’ll switch back to the develop branch:
git checkout develop
After some changes our develop branch diverged from the awesome-feature branch and we have all those new shiny commits:
$ git log --oneline --decorate --graph --all * 7befa5b (HEAD -> develop) Refactoring some code bits * 23666bd Another great feature * 4c0680f Another important bugfix | * a760e09 (awesome-feature) awesome feature |/ * de5ed6e Initial commit
Now we could easily jump between branches or commits we’ve done in the past, just by using our lovely git checkout command.
However, we could also use our reference log to see what we’ve done so far!
Reflog
The reference log is a list of local actions, if you want so:
$ git reflog 7befa5b HEAD@{0}: commit: Refactoring some code bits 23666bd HEAD@{1}: commit: Another great feature 4c0680f HEAD@{2}: commit: Another important bugfix de5ed6e HEAD@{3}: checkout: moving from awesome-feature to develop a760e09 HEAD@{4}: commit: awesome feature de5ed6e HEAD@{5}: checkout: moving from develop to awesome-feature de5ed6e HEAD@{6}: commit (initial): Initial commit
As you can see, these are all the actions you’ve done with git – not only commits! While git log shows only the logs of commits, git reflog shows you all your local actions. This might be comparable with the bash history.
For example I see that “4 changes ago” I’ve commited the commit awesome feature. I can now reference to this commit with its SHA string de5ed6e or by using the HEAD@{4} syntax.
Now you might say: All fine comrade, but I can also use the normal git log as I can see the same commit in there too. So why/when the heck should I use the reflog over log?
First of all, the reflog is a “changelog” of all the steps you’ve done so far. This is quite nice, but trust me it gets better!
Let’s say you’ve pushed your awesome-feature branch to the remote origin, or already merged it into your develop. So you might no longer need the branch in your local repository and therefor you delete it:
$ git branch -D awesome-feature Deleted branch awesome-feature (was a760e09).
Now you’ll have a look at the log again:
$ git log --oneline --decorate --graph --all * 7befa5b (HEAD -> develop) Refactoring some code bits * 23666bd Another great feature * 4c0680f Another important bugfix * de5ed6e Initial commit
Oh, farts… Did I just delete the awesome-feature branch before merging it locally or pushing it to the origin? Did I just lost all my changes? Exactly this happened to me twice so far. It’s late night, you’re jumping between branches and accidentally deleting the wrong branch (it was really late and I was tired as f*).
Now you’re lost, aren’t you? No, you’re not, because there’s our saviour, our magic reference log:
$ git reflog 7befa5b HEAD@{0}: commit: Refactoring some code bits 23666bd HEAD@{1}: commit: Another great feature 4c0680f HEAD@{2}: commit: Another important bugfix de5ed6e HEAD@{3}: checkout: moving from awesome-feature to develop <strong>a760e09 HEAD@{4}: commit: awesome feature</strong> de5ed6e HEAD@{5}: checkout: moving from develop to awesome-feature de5ed6e HEAD@{6}: commit (initial): Initial commit
And there it is, our missing commit. Let’s get it and restore our branch:
$ git checkout HEAD@{4} Note: checking out 'HEAD@{4}'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b <new-branch-name> HEAD is now at a760e09... awesome feature $ git checkout -b awesome-feature Switched to a new branch 'awesome-feature'
A quick look on our log tells us we’re safe again:
$ git log --oneline --decorate --graph --all * 7befa5b (develop) Refactoring some code bits * 23666bd Another great feature * 4c0680f Another important bugfix | * a760e09 (HEAD -> awesome-feature) awesome feature |/ * de5ed6e Initial commit
The Git reference log finally saved our asses – YAY! This is awesome, innit? And the best thing is, it also works when you messed up your (local) rebase 😉
Reflog and expire
But be warned. Git reference logs can be expire, which can be controlled with the expire argument. The expire time can also be configured via gitconfig! However this must be explicitly configured.
If you didn’t knew reference logs so far then you might ask yourself: Wait, you say all those sensitive commits (e.g. accidentally commited passwords) I removed in the past by rewriting the history are still there in the reflog? Yes mate, all those sensitive commits might still be existing if they didn’t expire – which they don’t do by default 😉
16 Comments