Okay, so it didn’t save my life. But it saved me hours of time and prevented me from experiencing incredible amounts of frustration.
I had been working on a big, new feature, a tricky problem. After a few hours of solidly working away, I was done. I ran git status and sat back and looked proudly at the list of red that detailed the ten or so files that I had either modified or created.
I meticulously reviewed and added all of my changes. Then I committed my changes with a simple, descriptive message. I was ready to git push:
And then I realised I’d made a mistake. I was working on master.
You see, I hadn’t been working on one of my own codebases. I had been working on something for a friend and I wanted them to review all of the changes before I made them part of the official website. I decided that I should instead commit all of my work onto a branch and open a pull request.
Now, the safest thing to do — and believe me, I had this idea, so it’s beyond me why I decided not to do it — would have been to cherry pick the commit onto a new branch and then reset master to its origin. However, for some strange reason, I backed my Git prowess and decided to, from memory, do something different in order to revert the codebase to its pre-commit stage.
Let’s relive the disaster together
Here is a re-enactment. We’ll pick up from the moment I realised I’d made a mistake:
The commit at the top is the one I needed to move.
The first thing I did was type in git reset — soft HEAD^ so that the last commit would disappear but I’d still have all of my work staged. git status revealed this:
And git log showed me this:
Great! I had my work, but the “New, big feature…” commit was gone from master.
Then, I checked out a new branch called new-big-feature and ran git status:
At this point, I thought to myself: “The new-big-feature branch is looking good. Before committing these changes, let me tidy up the master branch.”
(Why, Nadia, why?!)
So, I checked out the master branch again and ran git reset --hard origin/master.
git status showed me:
Back to the new-big-feature branch.
Now, I expected to see this when I ran git status:
Instead, I saw this:
What the fuck?!
No, this couldn’t be!
I started to shake. My heart was pounding. I didn’t have time to do all of this work again. How could I have been so careless?
I typed in git status a million more times, each time saying that this must all be some mistake, git status was only erroring and all of my work would be there and everything would be just fine.
After five or so minutes of frantically typing on my keyboard and freaking out, I stopped and put my head in my hands, wondering what to do, and starting to psyche myself up for starting the work all over again.
Git is so powerful, most people only skim the surface
Suddenly, I remembered somebody saying that to me. I also had a vague recollection of someone saying:
“It keeps a log of every single Git command that you type in and you can check out to different stages of your local repository.”
“Well, Git,” I thought to myself, “let’s see just how powerful you are.”
I went to Google and typed in: “recover unpushed lost commit” and that’s when I found out about the wonderful git reflog:
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. For example, HEAD@{2} means “where HEAD used to be two moves ago”, master@{one.week.ago}means “where master used to point to one week ago in this local repository”, and so on.
Hopeful, I went back to my terminal, and typed in git reflog:
The last line looked interesting. It appeared to reflect the point in my local repository after I’d made the commit and before I did the deadly reset.
I took a big, deep breath, crossed my fingers, and then typed in git reset --hard HEAD@{6}.
Hallelujah!
git status revealed this:
And git log gave me this:
The commit had reappeared. My work was restored. I was back in business.
I switched back to my new-big-feature branch, cherry-picked the 2d6e22b commit, and then reset master.
Phew.
Git has your back
I hope you’re never in that situation — accidentally losing commits and panicking about hours of lost work. Committing little and often and pushing regularly will save you from scenarios like the one described above. However, I hope that my tale has given you confidence to press forward and try out things that you’re not sure will work, like complicated rebases and merges, when you’re deep in the weeds of Git and unsure how to get yourself out. After all, you now know it’s nigh on impossible for you to lose any work that’s been committed, even if it has never been pushed.
I also hope that this tale has made you curious to discover more of Git’s surface area. This experience, combined with my discovery of the --intent-to-addoption the other week, has made my keen to find out just what else Git can do for me.
Here’s something I’ve already learnt — with Git, you can even recover work that you have never committed!
But that’s a blog post for another time.
Top comments (2)
Thanks for sharing your experience!
I was in such situation before and yeah
reflogs
is savage saved my life a lot :Dlooking forward to read your next blog post.
The commands I would have chosen
$ git checkout -b new-big-feature
$ git branch -f master origin/master
I've started to delete local master development should be on a branch. And origin/master already tracks upstream.
The other option I choose.
$ git push origin master:new-big-feature