How to Use git reset

(Last Updated: 2019-10-23)

git reset is a powerful command Rietta staff use on a daily basis. However powerful, git reset has 2 distinct features:

Often you’ll hear that the reset command is basically the opposite of the add command. While this is true for the default reset, there are other options to reset the index without unstaging the files as well. This blog article summarizes the subtle difference between the two different soft resets as well as provides a nice trick to view.

reset (aka reset --mixed)

The most common way to use git reset is without any arguements. This results in the same operation as passing the flag --mixed and will change our index as well as remove the files from the staging tree.

Let’s begin with checking what changes we’ve made and then adding our changes into the staging area.

$ git status
Changes not staged for commit:
	modified:   .circleci/config.yml
$ git add .circleci/config.yml
$ git status
Changes to be committed:
        modified:   .circleci/config.yml

We can utilize the reset command to move changes out of the staging area back into our working directory, so if we supply the file path to said command, which in our case is .circleci/config.yml,

$ git reset .circleci/config.yml
Unstaged changes after reset:
M        .circleci/config.yml
$ git status
Changes not staged for commit:
        modified:   .circleci/config.yml

The reset command will undo and move our changes out of our staging area back into our working directory. We will be back at the exact point before we ran git add .circleci/config.yml. When checking our git status, we can see that our changes are now only present in our working directory and not in our staging area.

reset --soft

We can take this a step further and use the reset command to not only undo and move changes from our staging area back to our working directory, but we can also do the same with commits in our local git repository. For example, lets say we had already commited some changes to our local git repository but want to make some final modifications before pushing our commit to our remote repository.

$ git status
Changes not staged for commit:
	modified:   .circleci/config.yml
$ git add .circleci/config.yml
$ git commit -m 'We will reset these changes'
[ea7edf0] We will reset these changes
1 file changed, 1 insertion(+), 1 deletion(-)

So now that our changes are in our local git repository prior to being pushed up to our remote repository, we can use reset with the --soft flag along with where we’d like the HEAD pointer to be in order to undo the commit and have the changes back in our local directory to make our modifications. The command we want to execute will look like this: git reset --soft HEAD~1, we are moving our HEAD pointer to the commit we had just made.

$ git log -1
ea7edf0 2 minutes ago Christopher Choi We will reset these changes
$ git reset --soft HEAD~1
$ git status
Changes to be committed:
      modified:   .circleci/config.yml

reset --patch

git add --patch is very useful when wanting to be more specific with what changes we want to add to the staging area before committing. We can apply the same rule to reset by applying the same --patch flag when wanting to undo and put specific changes from a commit in a our local git repository back into our working directory.

So for example, if we had added a large changeset and committed it to our local git repository but want to pull out some changes from the commit, we can run git reset --patch HEAD~1. We are also specifying which commit we want to modify with our HEAD pointer, HEAD~1 meaning one commit prior to what our HEAD currently is.

$ git reset --patch HEAD~1
-      # run test suite!
+      # run tests!
       - run:
           name: Run Tests
           command: |
Apply this hunk to index [y,n,q,a,d,e,?]?

The options in the array are exactly the same as git add --patch so we can piece by piece undo our changes and put them back into our working directory. As a caveat, you cannot use --patch with --soft, --hard, or --mixed. The --patch reset will always operate like a --mixed reset.

Conclusion

Before I understood reset better, I would push up changes to the remote repository and any subsequent modifications to said changes were added as separate commits. I would then do an interactive rebase to squash/fixup commits. While this process worked, it often lead to messy looking pull requests littered with force-pushed logs. I now use git resets to cleanup changes on my local git repository prior to pushing up commits to the remote repository.