I’ve made my share of mistakes using Git. This is an easy trick I learned that saved my butt when I would otherwise have lost a bunch of progress after an incorrect Git action.

git reflog is the command I’m talking about. If you are used to working with a Git interface, such as the Git tool window in IntelliJ, you might not know this view of your local history even exists. The tool window only shows you a log of commits that are currently applied to your local and remote repositories:

Git tool window

This is enough for cases like navigation, cherry-picking, squashing commits etcetera. Here are some examples of (local) mistakes where you need to reapply a specific commit that might not appear in the regular log:

  1. when you squash commits, and accidentally omit a commit.

  2. when you complete a merge, but find out a change you applied wasn’t correct.

This is where git reflog comes in handy! Before I give you an example, don’t forget Tim Toady, there is more than one way to do it! But I’ve found reflog to be easy and effective in most (local) "oops" scenario’s.

Say that you have two commits that you want to squash:

git oopsies1

You use the git rebase --interactive command, and it results in one commit on the git log:

git oopsies2

Now, the original state of your branch isn’t visible with the git log command, but what if we just want to undo what happened? git reflog:

$ git reflog
c4c1685c (HEAD -> test) HEAD@{0}: rebase (finish): returning to refs/heads/test
c4c1685c (HEAD -> test) HEAD@{1}: rebase (fixup): test2 & test
6ec1dca2 HEAD@{2}: rebase (reword): test2 & test
92a02eb4 HEAD@{3}: rebase: fast-forward
98c50d8c (origin/master, origin/HEAD, master) HEAD@{4}: rebase (start): checkout 98c50d8cb463e352931688ff35948435c3abefef
ec2fb00a HEAD@{5}: commit: test2

Here, we can see the history of all the changes to the local repository. With this, you can easily go back to an earlier state of the repository, without manually having to "rebuild" it:

$ git reset --hard HEAD@{5}
HEAD is now at ec2fb00a test2

the log is now back to its original state before the rebase:

$ git log
commit ec2fb00ac3f95d03d4f2717b04fca030b586ea50 (HEAD -> test)
Author: Casper <casper.rooker@jdriven.com>
Date:   Thu Oct 20 17:28:54 2022 +0200


commit 92a02eb4e0d71d0881d50859e2848cdc04c0d240
Author: Casper <casper.rooker@jdriven.com>
Date:   Thu Oct 20 17:22:36 2022 +0200


And in the reflog, the changes simply add onto the list:

$ git reflog
ec2fb00a (HEAD -> test) HEAD@{0}: reset: moving to HEAD@{5}
c4c1685c HEAD@{1}: rebase (finish): returning to refs/heads/test
c4c1685c HEAD@{2}: rebase (fixup): test2 & test
6ec1dca2 HEAD@{3}: rebase (reword): test2 & test
92a02eb4 HEAD@{4}: rebase: fast-forward
98c50d8c (origin/master, origin/HEAD, master) HEAD@{5}: rebase (start): checkout 98c50d8cb463e352931688ff35948435c3abefef
ec2fb00a (HEAD -> test) HEAD@{6}: commit: test2
92a02eb4 HEAD@{7}: reset: moving to HEAD@{1}

Neat, right?