Recovering from mistakes in Git

This is the first installment of Git related posts. It’s a result of quite successful trainings we have conducted during last two months. First one was a full day training at CH/Open workshop week in September, where we started from the basics and moved towards more advances concepts, hopefully giving people enough knowledge to not only work with Git comfortably on a daily basis, but also use some tricks when in doubt. In October we had the pleasure to run two shorter workshops at Jazoon. First part was about the essential concepts and commands in Git. In the afternoon we focused on “Kung Fu”, so things which go beyond basic porcelain commands and can cause your head to explode.

Git

One thing we have been asked about frequently is how to gracefully recover from mistakes. As Git is quite different from other, server-centric repositories, when we think about a disaster recovery we actually have several areas to cover. So let’s start locally.

Undoing not staged changes

If we made too much of a mess in our current working directory (called in this article working tree sometimes), we can discard the changes easily if the file is already tracked by Git.

git checkout -- FILE will apply changes stored in staging back to our working tree (so discard local changes). As you have seen from the demo above Git is actually very helpful and suggests this command as a hint in the git status output.

Undoing changes of a staged file

If you have already populated your local change to the staging area (index), you can invoke another helpful command for the rescue.

Invoking git reset HEAD FILE will discard staged changes and push it back to your local working tree (so that you can use the previous trick to entirely get rid of it ;))

But what if  it’s already committed (still locally, not pushed upstream)?

If our changes have been already committed we can use the same git reset command, but restoring our working tree to the previous commit (parent of HEAD), by simply invoking git reset HEAD^.

This will discard the latest commit on the current branch (so where HEAD was pointing to). If not specified otherwise, --mixed mode is used. This will push back all the changes to unstaged / working tree. If we want to preserve those changes which were part of the commit in the staging area we can use the --soft flag. If we don’t want changes to be pushed back from staging area (aka index) to our local changes we can use the --hard flag. This will result in resetting both index (staging area) and working tree. To have a better understanding of all available modes you can have a quick look at the official documentation.

To wrap this part up, here’s the simple diagram which can give you a graphical understanding of what is going on. Git

One bridge too far

What if by accident we invoked git reset --hard HEAD^ and all the great stuff we worked on till now is gone? Changes introduced by a discarded commit are sucked into the black hole. Is there something we can do about it?

Git is kind of a big brother and keeps an eye on everything what is going on; In order to see all the operations performed on our local repository we can use the git reflog command. By default it stores everything what we did from within the last 90 days. Again, actions speak better than words so let’s have a look how it works.

Restoring not yet versioned changes

There is also another interesting, yet not that common mistake from which we can recover too. It’s not as nice as easy as all the weaponry we have discussed so far but it can save some stressful moments. Let’s assume you have created a new file and staged it. But then you somehow ran git reset --hard. You can leverage git fsck command to verify your repository’s object database and find “lost blobs”.

As file names are only labels for blobs created when we commit to our repository, at this moment we will loose this information. We do have however the content and that’s the most important thing.

Here’s the full snippet which iterates over all “dangling” blobs and recover them as files named after their hashes.
for blob in $(git fsck --lost-found | awk '$2 == "blob" { print $3 }'); do git cat-file -p $blob > $blob.txt; done

Saving face

When we go remotely things are getting a bit more complicated. As we don’t want to mess up with the repository used by others, we do not really have a lot of choices, but one – git revert (besides shipping the fix as part of another commit). Obviously we can also push with force (git push -f) after removing the problematic commit through an interactive rebase, but after that we might loose some friends.

If you know the svn revert command forget about any connection, the one in Git is not really a counterpart. SVN operates on the local changes, similarly as all the tricks we have mentioned so far in the Git world. What git revert does is taking an arbitrary commit (or a set of them) and replays them on top of the HEAD with reverted changes. This results in new commits created on the selected branch and might require resolving the merge conflicts sometimes.

That’s it for now. We are going to write few more articles concerning Git, so watch our blog for the updates!

Never miss an article by subscribing to our monthly digest!

Summary
Recovering from mistakes in Git
Article Name
Recovering from mistakes in Git
Description
This is the first installment of Git related posts. It's a result of quite successful trainings we have conducted during last two months.
Author
Publisher Name
Atos Consulting CH
Publisher Logo

2 thoughts on “Recovering from mistakes in Git

  1. Nik Reply

    Thank Git for dangling blobs! You saved 2 days of my life.

    I’ve been weaning myself off the Git Extensions gui for windows but late in the day feeling tired with sore eyes I started a commit, staged the files (that saved me), wrote a stupid commit message, then clicked “reset all changes”. What I meant to click was “forget my stupid commit message, unstage those files and return me to the main history window”.

    Do NOT click “reset all changes”. They should kill that button, or rename it.

    It’s command line for me from now on, though a picture is worth a thousand ascii chars.

  2. Vinicius Reply

    You just saved my life.
    I’m sort of new to git, so i did something stupid and just lost all my files.
    With that git fsck –lost-found tip i could recover literally thousands of important files.

    There really isn’t any way to recover the files names and folder structure?

Leave a Reply

Your email address will not be published. Required fields are marked *