The background
I was trying to rebase my branch against the latest master main branch one day, which had some new changes from my colleague. Unfortunately, those changes actually had conflicts with the changes on my branch. Obviously, git
then asked me to resolve the conflicts when I tried to do a rebase, and so I did. But after I was done, I realized that the changes on my branch no longer worked, and upon further digging, I seemed to have not resolved the conflicts correctly, hence introducing a bug in my branch.
So I tried git diff
-ing against the latest main branch again to see what I did wrong. But because the conflicts were quite complex and with all the lines of additions and deletions jumbled in-between each other, detecting the problem couldn't just be done in a glance. I had to resort to a more extreme approach, starting over on my rebase.
That means I had to reset my current branch to the state before I did the rebase, so I had to get that commit hash from before. I cannot get that from my branch now, since if I run git log
on my branch, it will show the commit hashes after the rebase. But not to worry, right? Because I could obtain the old commit hash by referring to my branch on remote (in this case BitBucket). And here was when I realized that I did another crucial mistake: I did not push my branch to remote, at all. All my commits were just in my local repository.
So, now what?
git reflog
to the rescue
And that was when I stumbled upon git reflog
, a handy, little-less-popular feature of git
that I love. According to the documentation, git-reflog
:
record when the tips of branches and other references were updated in the local repository.
That means, whenever you do any operation in your local git
repository, it gets logged into reflog. That's right; when you create a new commit, that gets logged. When you amend a commit, that gets logged. When you checkout to a different branch, that gets logged. When you cherry-pick a commit, that gets logged. When you rebase, each steps git
takes to rebase your branch gets logged. You can try git reflog
now on any of your git
repos and you will see all the operations you did in your local repo.
Bringing back the lost commit from reflog the grave
So, with this new knowledge equipped, I could solve the problem I described earlier. I ran git reflog
, traced the last commit I made before the rebase and copied its commit hash, and simply ran git reset --hard <the old commit hash>
. And voilà, I was back at square 1 (sort of, see Footnote 1). I then re-ran git rebase master
to repeat the merge conflict resolution process, and resolved it properly this time.
Conclusion
And that's how you bring back commits from the grave. Yes, you might not need this everyday. But when you do need it, you'd be glad it exists. I know I did.
Footnote 1
git
will actually record your merge conflict resolution the first time you do it, so even if you rerun git rebase master
, it will apply those recorded resolutions and you might see your buggy merge conflict resolution again. To resolve this, run:
# rerere stands for reuse recorded resolution
git rerere forget path/to/file/with/buggy/resolution
or if there's a ton of files, just run rm -rf .git/rr-cache
.
I actually wish I could disable git rerere
. I don't find it useful tbh.
Footnote 2
You can run git reflog expire
to delete these dangling commits. By default, it'll keep the commits created within the last 90 days. I believe they'll never get deleted ever if you don't run this command or git gc
command, but I could be wrong. In any case, I don't think you'll want to dig up dangling commits from beyond 90 days ago. There's gonna be so much noise.
Top comments (4)
Quite helpful! After resolving merge conflict git log command just shows a single thread of history.
Super useful..Glad you fixed your rebase. Why didn't you go with a git merge instead? Also, first time I learn about git rerere (I thought Rihanaa had her own git command 😂)
I believe merge would still produce the same result since the merge conflicts would still happen. Plus, I don't like what
git merge
does to the tree. 😅 I cringe every time I see a merge from master into one branch and later a merge from the branch back into master. 😅You saved my day!