Reverse destructive git operations

Reverse destructive git operations

Undo your squash commit

Almost everyone I know has worked with Git, and I have to admit, when I first started working with it many years back, I was always scared of anything to do with rebase. The good news is that it's quite easy once you get a hang of it, and more importantly, it is also easy undo mistakes.

In this post, we will go through an example repository I created on my machine. We will make a mistake and then undo the mistake.

To start with, I added two files to the repository - here is the log:

aliasger@aliasgers-Mac-mini learn-git % git log
commit 71538568ba0a5b69f862eee6c5d102e990396011 (HEAD -> master)
Author: Aliasger Jaffer <hidden>
Date:   Wed Oct 13 21:32:26 2021 -0400

    Fix submit button styling issue

commit be7a4a724eba8ca99a49d27086673cc1b1c98151
Author: Aliasger Jaffer <hidden>
Date:   Wed Oct 13 21:31:44 2021 -0400

    Fix submit bug for registration form

commit 955692a9af44f4f18effebada81d99a099cf2bbf
Author: Aliasger Jaffer <hidden>
Date:   Wed Oct 13 21:30:58 2021 -0400

    Fix registration form bug

commit 36a95ae5b37dc92fbe79321ef614e5d59311626b
Author: Aliasger Jaffer <hidden>
Date:   Wed Oct 13 21:30:24 2021 -0400

    Add registration form

commit b22ecff97e717b3593d22227e6b8f2cd8ca754d2
Author: Aliasger Jaffer <hidden>
Date:   Wed Oct 13 21:29:44 2021 -0400

    Add styling

commit a0905afa46bdde4aa5c5cead97ef5ff931046a6a
Author: Aliasger Jaffer <hidden>
Date:   Wed Oct 13 21:29:02 2021 -0400

    Add index

commit 674a5aa8c267eb72909fdb4324e80f6909feeca9
Author: Aliasger Jaffer <hidden>
Date:   Wed Oct 13 21:28:50 2021 -0400

    Add README

Start the Squash (and make a mistake)

Let's say we wanted to squash 3 commits (all of them start with the word Fix in the commit log), however, we do a mistake and squash the Add registration form commit too!

Below is the command to squash these 4 commits (we are purposely making a mistake now):

aliasger@aliasgers-Mac-mini learn-git % git rebase -i HEAD~4

We should get two edit screens upon executing the command above.

The first one will asks us to select what we want to do with the commits, and the second one will asks us to add comment for our log. Remember, rebase is a destructive process - meaning, it deletes lines from our log, and lets us add a new commit message (with a new hash) to store our resulting output from the rebase.

Below are screenshots of the screens:

rebase-i-screen1.png

The first four lines in the screen above are the commits we are interacting with. The last three commits start with the letter s - which instructs git to squash them on top of each other. The second line squashes the resulting commit onto the first line (the one that has pick. In other words, we are squashing the bottom commits onto the first line.

We are squashing our Fix commits to our Add registration form commit - this is the error!

Once we save this screen (in vi, its [esc], : and then wq), we get another screen show up. This screen is asking us to enter our commit message to replace the 4 commits we squashed!

Here is the screen:

rebase-i-commit-msg.png

Note that the first time you see this screen, it will have the messages of all the previous commits. I deleted them and added a nice message that says:

Fix registration form submittion error

Lines starting with # are commented and won't appear in the log as part of the commit.

Once we save this screen (again, in vi, we use [esc], :, wq), we end up with the following on the command line:

aliasger@aliasgers-Mac-mini learn-git % git rebase -i HEAD~4
[detached HEAD 48af5a7] Fix registration form submittion error
 Date: Wed Oct 13 21:30:24 2021 -0400
 1 file changed, 1 insertion(+), 1 deletion(-)
Successfully rebased and updated refs/heads/master.

In short, our rebase was a success. Let's look at the log now.

aliasger@aliasgers-Mac-mini learn-git % git log
commit 48af5a7136578557fd7d269accec4c3cdcd08236 (HEAD -> master)
Author: Aliasger Jaffer <hidden>
Date:   Wed Oct 13 21:30:24 2021 -0400

    Fix registration form submittion error

commit b22ecff97e717b3593d22227e6b8f2cd8ca754d2
Author: Aliasger Jaffer <hidden>
Date:   Wed Oct 13 21:29:44 2021 -0400

    Add styling

commit a0905afa46bdde4aa5c5cead97ef5ff931046a6a
Author: Aliasger Jaffer <hidden>
Date:   Wed Oct 13 21:29:02 2021 -0400

    Add index

commit 674a5aa8c267eb72909fdb4324e80f6909feeca9
Author: Aliasger Jaffer <hidden>
Date:   Wed Oct 13 21:28:50 2021 -0400

    Add README

We are missing our add registration form commit!

Undo the rebase

How do we undo a rebase? The good news is that Git keeps track of things that we do. In our case, we can ask it to give us references of all the commits that were the HEAD in the past. Git keeps a pointer named HEAD, and points to the latest commit in your active branch. If you switch to a different branch, head will point to the most recent commit in that branch. If you add a commit, HEAD will move to this new commit.

To fix our problem, all we have to do is ask Git to give us the list of commits that were pointed to by the HEAD pointer. Below is the command:

aliasger@aliasgers-Mac-mini learn-git % git reflog

We should now see the following result:

48af5a7 (HEAD -> master) HEAD@{0}: rebase (finish): returning to refs/heads/master
48af5a7 (HEAD -> master) HEAD@{1}: rebase (squash): Fix registration form submittion error
d47e329 HEAD@{2}: rebase (squash): # This is a combination of 3 commits.
caf4382 HEAD@{3}: rebase (squash): # This is a combination of 2 commits.
36a95ae HEAD@{4}: rebase (start): checkout HEAD~4
7153856 HEAD@{5}: rebase (finish): returning to refs/heads/master
7153856 HEAD@{6}: rebase (start): checkout HEAD~4
7153856 HEAD@{7}: commit: Fix submit button styling issue
be7a4a7 HEAD@{8}: commit: Fix submit bug for registration form
955692a HEAD@{9}: commit: Fix registration form bug
36a95ae HEAD@{10}: commit: Add registration form
b22ecff HEAD@{11}: commit: Add styling
a0905af HEAD@{12}: commit: Add index
674a5aa HEAD@{13}: commit (initial): Add README

Notice the start of our Rebase was from HEAD@{6}. That means, the commit hash 7153856 was the point before our interactive rebase! We can tell git to take us back to that point in time!

How do we time travel backwards? Git provides us with the git reset command that can reverse us back in time. Here is the command (note that this command has various options, but for our case, we just need to use --hard).

git reset --hard 7153856

There we go - we just told git to take our HEAD pointer back to our commit 7153856 . The option --hard tells git to discard everything in our working directory (we don't have anything anyway).

Now let's do git log and see if we were able to undo our rebase:

aliasger@aliasgers-Mac-mini learn-git % git log
commit 71538568ba0a5b69f862eee6c5d102e990396011 (HEAD -> master)
Author: Aliasger Jaffer <hidden>
Date:   Wed Oct 13 21:32:26 2021 -0400

    Fix submit button styling issue

commit be7a4a724eba8ca99a49d27086673cc1b1c98151
Author: Aliasger Jaffer <hidden>
Date:   Wed Oct 13 21:31:44 2021 -0400

    Fix submit bug for registration form

commit 955692a9af44f4f18effebada81d99a099cf2bbf
Author: Aliasger Jaffer <hidden>
Date:   Wed Oct 13 21:30:58 2021 -0400

    Fix registration form bug

commit 36a95ae5b37dc92fbe79321ef614e5d59311626b
Author: Aliasger Jaffer <hidden>
Date:   Wed Oct 13 21:30:24 2021 -0400

    Add registration form

commit b22ecff97e717b3593d22227e6b8f2cd8ca754d2
Author: Aliasger Jaffer <hidden>
Date:   Wed Oct 13 21:29:44 2021 -0400

    Add styling

commit a0905afa46bdde4aa5c5cead97ef5ff931046a6a
Author: Aliasger Jaffer <hidden>
Date:   Wed Oct 13 21:29:02 2021 -0400

    Add index

commit 674a5aa8c267eb72909fdb4324e80f6909feeca9
Author: Aliasger Jaffer <hidden>
Date:   Wed Oct 13 21:28:50 2021 -0400

    Add README

Notice how the commit messages and more importantly, the hash values are the same!

Remember, git reflog has a lot more features (such as going back on stash, etc). You don't need to be afraid of git anymore.