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?
And that was when I stumbled upon
git reflog, a handy, little-less-popular feature of
git that I love. According to the documentation,
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.
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.
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.
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.
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.