Use of Git Reset, Git Revert, Git Checkout & Squash Commit

Amit Prajapati
MindOrks
Published in
13 min readAug 19, 2019

From the above title, you may know that what we will learn today. Let’s understand this concept well. Get started… 👩‍💻

1) Undo uncommitted changes using git checkout --<filename>

  • It rollbacks any content changes to those of the specific commit.
  • This will not make changes to the commit history.
  • Use this to move the HEAD pointer to a specific commit or switch between branches.

for e.g: Firstly, we will create a new repository named “git-tut”. In this repo, we create a file named ‘form.html’ file in which contains a login functionality layout.

In the above image, you see that the repository “git-tut” is initialized. By using the git status command it will track the file ‘form.html’ which is in the git-tut repo. Now you add this file ‘form.html’ to the staging area using the command git add filename . This is what ‘form.html’ looks like.

Now, again you modified the ‘form.html’ file with added “reset-password” field.

After some time, your teammates say to remove that ‘reset-password’ field. Now, you can easily undo that change by removing that entire ‘reset-password’ field code but when you are doing programming in real life you might have hundreds of lines of codes changed for that you are not going to manually undo all of those lines right it’s very cumbersome so that’s why you will use the command git checkout -- <filename> .

As you see, the file ‘form.html’ is modified. Now, let’s run the command git checkout -- form.html .

This command will undo our changes. Let’s have a look.

Now, what if you have multiple files changed so again you have to perform the same command git checkout -- <filename> multiple times with different file names but there is a convenient option of using the dot (.). When you do a dot it’s going to undo all the files changes which you want to undo. e.g. git checkout . .

2) Undo committed changes using — -> git revert

  • Rollback changes you have committed.
  • Creates a new commit from a specified commit by inverting it. Hence, adds a new commit history to the project, but it doesn’t modify the existing one.

for e.g: Now, you again modified ‘form.html’ file with some extra fields like this 👇

Let’s view, the commit history.

After working some time on the “google” and “facebook” login feature you committed the code. When executing the code you got an error and tried to solve but it’s not working fine. After some time, your teammates say that we have to show the project to the client. Now, what will you do, you have to undo the changes. So, here comes the use of the command git revert.

The commit ID is required to perform a git revert command. In our case, the commit ID is f267ef7 which you can see in the image above. The git revert commit-id command opens the vi editor because if you want to change the commit message you can do it with that by pressing the key i on the keyboard. If you don’t want to change anything just press esc and type :wq where “w” indicates that the file should be written, or saved which will overwrite the existing file and the “q” indicates that vim should quit, or exit then press Enter .

The git revert command explicitly commits the changes. When you see your, editor(Notepad, VS Code, etc), the ‘google’ and ‘facebook’ login feature are gone.

Suppose, you don’t want explicitly commit by git revert command. In that case, you can use -n option which means don’t commit automatically. There are multiple options available in git revert command. See in the below image.

for e.g: Let’s say, you also want to remove the “Remember Me” code. I will undo the changes using git revert -n commit-id command. In our case, the commit-id is 3902cba

As you see, it doesn’t commit explicitly means we have to commit the changes so first add the file to the staging area and commit the file. Let’s view our commit history.

I hope you will understand this command.

Resetting changes using — -> git reset

  • Use this to return the entire working tree to the last committed state. This will discard commits in a private branch or throw away uncommitted changes!
  • It can be used to unstage a file.

Before we dive into the rest command we need to take a look at what is called the trees of git: Working directory, Staging Area and the Repository.

You can think of them as three areas where changes can reside from git’s point of view:

  • Working Directory: Your project file on your file system.
  • Staging Area: A preview of the next commit.
  • Repository: Datastore where git keeps all (past) commits.

The reset command operates in these three areas. To add the file to the staging area by using git addfilename and to commit the file using git commit -mfilename .

The git reset command has three options such as --soft, --hard and --mixed .

Let’s say we do some more refactoring on the “form.html” file and do the whole add/commit cycle again.

Now our Working Directory, Staging Area and the Repository will all contain the new second version of our “form.html” file.

But what about the first version? We know that the repository keeps all previous commits, so the first version of “form.html” is still there:

To keep track which version of the “form.html” is the current one, The Repository has a special pointer called HEAD that points to the current version (and the status command only looks at the current version that HEAD is pointing to when comparing it to the Staging Area version).

Now that we have that covered we can finally go to our reset command and see how it works by manipulating the content of these git areas (trees).

1) reset — soft

This first option of reset command will only do one thing:

  • move the HEAD pointer

In our case, we will move it to the previous commit (the first version of “form.html”) by running: git reset --soft <commit-id> or HEAD~1

The tree of git now look like this:

for e.g: Let’s view, our commit history in the image below:

Suppose, If you want to remove the “added remember me checkbox” commit from the commit history and don’t want the changes in that commit is lost. So, here comes the use of git reset --soft command.

As you see in the image above, the HEAD is pointing to the previous (first) commit.

And when we run git status we see a familiar message:

So, running git reset --soft HEAD~1 was basically undone our last commit, but the changes contained in that commit are not lost — they are in our Staging Area and Working directory. See in the image below.

This is our working directory.
Staging Area (Index)

See, Nothing changed in our code.

2) reset — mixed

The second option of reset command will do two things:

  • move the HEAD pointer.
  • update the Staging Area (with the content that the HEAD is pointing to)

So, the first step is the same as with --soft option. The second step takes whatever the HEAD points to (in this case, it is version one of the “form.html” file) and puts it into the Staging Area.

So, after running git reset --mixed HEAD~1 our areas look like this:

for e.g: After some changes and commits in our “form.html”. Let’s view the commit history.

Now, you want to remove the latest commit which is “added google and facebook login”. After removing this commit it will not change our Working Directory content but only change our Staging Area content.

As you see, the HEAD is pointing to the previous commit.

And running git status now again gives us a familiar message:

The --mixed parameter (which is a default if you don’t specify anything) will reset the HEAD to another commit and will reset the index (Staging Area) to match it, and it will stop there. The working copy will not be touched. So, all of the changes between the original HEAD and the commit you reset are still in the working copy and appear as modified, but not staged.

As you see, there is no change in our Working Directory.

But, in this image, our file contents are changed.

3) reset — hard

And now for the notorious hard option. Running reset --hard will do three things:

  • move the HEAD pointer
  • update the Staging Area (with the content that the HEAD is pointing to)
  • updates the Working Directory to match the Staging Area.

So, the first two steps are the same as with --mixed . The third makes the Working Directory look like the Staging Area.

So, after running git reset --hard HEAD~1 our areas look like this:

for e.g: Let’s again view our commit history.

Now, you want to again reset the latest commit which is “added new user section”. Let’s try to reset this commit by using --hard option.

As you see in the image, the HEAD pointer is pointing to the previous commit and the content will also change in the Staging Area and Working Directory. Let’s have a look.

This is our Working Directory.

This is our Staging Area (Index).

And running git status gives us:

So, running git reset --hard HEAD~1 has undone our last commit and the changes contained in that commit are neither in our Working Directory or the Staging Area. But they are not completely lost. Git doesn’t delete commits from Repository (actually, it does sometimes, but very rarely), so this means our commit with the second version is still in the Repository, it is just a bit to find (you can track it by looking at something called reflog).

This is the more dangerous of the commands and is where you can cause damage. Data might get lost here*!

Combine commits using squash

A way to group some changes together, especially before sharing them to others.

In Git you can merge several commits into one with the powerful interactive rebase. It’s a handy tool I use quite often.

Let’s understand this by an example:

for e.g: Let’s view our commit history in the master branch.

Now, we create a new branch named “api-login” in this branch we implement API logins such as Google, Facebook, and Linkedin and commit the changes in the “api-login” branch.

Let’s do some commits in the “api-login” branch.

In the above image, it shows the commits in our ‘api-login’ branch. There are only four commits. If you want to combine all the four commits into one commit, it is possible because of the “squash” option provided by git rebase command. To do so, we have to switch to the “api-login” branch by using the git checkout api-login command.

The first thing to do is to invoke git to start an interactive rebase session:

git rebase --interactive HEAD~[n]

Or, shorter:

git rebase -i HEAD~[n]

And this is what I would like to do: where [n] is the number of commits you want to join, starting from the most recent one as you see in the image below.

So, In our case, we will write the value of [n] is 4, because we want to join these four commits into one commit.

So, in this case, the command would be :

git rebase -i HEAD~4

because I want to combine the last four commits into one, and the commit-hash 7ccb146 added Google API Login is the fourth commit.

I have tons of commits to squash, do I have to count them one by one?

A downside of the command git rebase -i HEAD~[n] is that you have to guess to an exact number of commits, by counting them one by one. Luckily, there is another way:

git rebase --interactive [commit-hash]

Where [commit-hash] is the hash of the commit just before the first one you want to rewrite from. So in our example, the command would be:

git rebase --interactive 4b47ceb 

Where commit-hash 4b47ceb is added remember me checkbox . You can read the whole thing as- Merge all my ‘api-login’ branch commits on top of the commit [commit-hash] which is 4b47ceb .

When you perform git rebase -i HEAD~4 will pop up an editor, showing the list of commits you want to merge. Note that it might be confusing at first since they are displayed in reverse order, where the older commit is on top. I've added --- older commit and --- newer commit to make it clear, you won’t find those notes in the editor.

Below the commit list, there is a comment which outlines all the operations available. You can do many smart tricks during an interactive rebase, let’s stick with the basics for now though. Our task here is to mark all the commits as squashable.

You mark a commit as squashable by changing the work pick into squash next to it (or s for brevity, as stated in the comments). To go in insert mode press i on the keyboard. The result would be:

Save the file and close the editor by pressing Esc followed by :wq .

Now, you have just told Git to combine all four commits into the first commit in the list. It’s now time to give it a name: your editor pops up again with a default message, made of the names of all the commits you have squashed.

You can leave it as it is and the commit message will result in a list of all intermediate commits, as follows:

Usually, I don’t care to keep such information, so I swipe out the default message and use something more self-explanatory like added API Login .

Save and close the editor.

The squashing of commits is successfully completed.

Let’s view our ‘api-login’ commit history.

Let’s have a graphical look of our commit in the “api-login” branch.

I hope this blog helps in understanding the concepts.

If you really liked it, then do share it with your friends :-)

Thank you.

--

--