Image of Git Reset | The Git Reset Command Explained

ADVERTISEMENT

Table of Contents

What is git reset?

Git is a distributed version control system used for tracking changes made to code files within collaborative or open-source projects. In addition to tracking code changes, Git makes it easy to branch, merge, and review the history of the codebase.

At a high level, the git reset command enables you to undo or "reset" code changes that you previously made. In this article, we'll explain how to use the git reset command along with providing some git reset examples.

What does git reset do?

The steps used for tracking changes with Git are fairly straightforward. You create or modify code files with a text editor directly in your Git working directory. You then use the git add command to move your changes to Git's staging area (or index). Finally, you can run git commit to commit these changes to the repo. These three steps correspond with the following three Git concepts:

  1. The working directory (or working tree)
  2. The staging area (or staging index)
  3. The object repository (or object database)

Executing git reset provides a way to reset changes that were made in any of these three locations. You can think of this as a way to "move backwards" in your Git flow (or Git history) if you need to undo changes in Git from any of these three locations. Essentially, Git will reset the tip of your current branch to a prior commit, and handle any uncommitted changes as you see fit.

Remember, a Git branch is basically just a label that points to the latest commit in a chain of commits.

The main result of using the git reset command is to repoint the branch label of your currently checked-out branch to a different (usually a previous) commit. You can think of this as resetting the tip of your current branch to a previous state.

Note that if you already pushed a set of changes to your remote repository such as on GitHub or BitBucket, you probably want to use the git revert command instead of reset.

git reset syntax, usage, and modes

The basic syntax for git reset is as follows:

git reset [<mode>] [<commit>]

Git reset offers three main modes (or options) that determine how it behaves. They are --mixed, --soft, and --hard. Here's a brief description of each mode:

  • git reset --mixed: The default option for git reset. Updates the current branch tip to the specified commit and unstages any changes by moving them from the staging area back to the working tree.
  • git reset --soft: Known as a soft reset, this updates the current branch tip to the specified commit and makes no other changes.
  • git reset --hard: Known as a hard reset, this updates the current branch tip to the specified commit, unstages any changes, and also deletes any changes from the working directory.

When the [commit] parameter is omitted, it defaults to the commit pointed to by Git HEAD. This is equivalent to running the command "git reset HEAD".

Since Git HEAD always points to the currently checked out commit, the current branch tip is not repointed or changed in this case. When git reset is run without specifying a commit, it is typically to undo changes in Git's staging area or working directory (or both), without resetting the branch to a different commit. Note that each branch head points to the tip of that branch, i.e. the most recent commit on that branch.

git reset example

The default option is git reset --mixed, which updates the current branch tip and moves anything in the staging area back to the working directory. We'll take a closer look at all three, but first let's create a basic Git repo with the following structure and show a simple git reset command in action:

git-reset-example/
    file1.ext

    dir1/
        dir1file1.ext

Assuming we have already made our initial commit, let's add some text to file1.ext and dir1file1.ext and stage and commit them both in separate commits.

Next, let's make one more change to file1.ext and only stage the changes (but not commit), then we'll run a git log followed by a git status to check out the state of things:

$ git log --oneline
d66f707 (HEAD -> master) Change 2
32c2d09 Change 1
38e2a6e Initial commit

Next let's run git status:

$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   file1.ext

As we can see from our output, we have a commit history reflecting three commits, along with one staged file sitting in the staging index. Note that git diff can also be useful to check the state of things. Let's run a basic git reset command and check our log and status once more:

$ git reset
Unstaged changes after reset:
M       file1.ext

$ git log --oneline
d66f707 (HEAD -> master) Change 2
32c2d09 Change 1
38e2a6e Initial commit

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   file1.ext
 no changes added to commit (use "git add" and/or "git commit -a")

A quick glance at the output tells us that a basic git reset without a specific commit parameter left the commit history unchanged, while unstaging our modified file and moving it back to the working directory.

It's important to note that our changes still exist, as the working directory was left untouched. Git reset merely moved the change out of the staging area because it defaults to the --mixed option, in contrast with --hard which will wipe out the changes in the working directory as well.

Next, we'll take a look at how to specify the parameter which will change the commit pointed to by the current branch tip.

git reset --mixed with commit parameter

As we saw in the previous section, git reset --mixed is the default mode for git reset, and is used when we are ok with moving changes out of the staging area and back into the working directory.

However, as with each git reset mode, we also have the ability to reset the current branch tip to any commit of our choice. Using the repo we created earlier, let's try resetting to a specific commit by reviewing our git log output and selecting a commit to use with our git reset command:

$ git log --oneline
d66f707 (HEAD -> master) Change 2
32c2d09 Change 1
38e2a6e Initial commit

$ git reset --mixed 32c2d09
Unstaged changes after reset:
M       dir1/dir1file1.ext
M       file1.ext

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   dir1/dir1file1.ext (red)
 no changes added to commit (use "git add" and/or "git commit -a")

$ git log --oneline
32c2d09 (HEAD -> master) Change 1
38e2a6e Initial commit

In the above example, we reset the master branch back to our 32c2d09 commit. As can be seen from the output of our git status and git log, the change staged during this commit has been unstaged and sits next to our other unstaged change from the previous reset, and 32c2d09 commit is now pointed to by master and HEAD references.

As a quick note, --mixed was added to the command for example purposes only. Since --mixed is the default option, it could have been omitted while achieving the same result.

How to undo git reset?

Before moving on to the --soft and --hard options, let's look at a brief explanation of how to undo git reset, along with the potential for unrecoverable resets. We'll use this ability to revert git reset so we can enter into subsequent examples from our original starting point.

Git tracks all ref updates, so undoing a reset is fairly trivial so long as it's done within a month or so of the git reset in question. After this time, Git's garbage collector will trash your oraphaned commits, at which point they'll be gone for good.

Assuming we're within the allotted time, we can run a git reflog to view the ref history, and reset back to our previous commit, in effect undoing our changes. Let's try it out:

$ git reflog
32c2d09 (HEAD -> master) HEAD@{0}: reset: moving to 32c2d09
d66f707 HEAD@{1}: reset: moving to HEAD
d66f707 HEAD@{2}: commit: Change 2
32c2d09 (HEAD -> master) HEAD@{3}: commit: Change 1
38e2a6e HEAD@{4}: commit (initial): Initial commit

$ git reset d66f707
Unstaged changes after reset:
M       file1.ext

$ git log --oneline
d66f707 (HEAD -> master) Change 2
32c2d09 Change 1
38e2a6e Initial commit

So, by viewing our git reflog output and resetting to the previous commit ID we found in the reflog, we're back to square one! What if we ran the same thing with git reset --soft?

What is the use of git reset --soft?

Using git reset soft simply repoints the current branch to another commit, no other changes are made. Your working directory and staging index remain completely intact. Let's take a look:

$ git reset --soft 32c2d09

$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   dir1/dir1file1.ext 

$ git log --oneline
32c2d09 (HEAD -> master) Change 1
38e2a6e Initial commit

What is the use of git reset --hard?

Using the git reset --hard option resets the current branch tip, and also deletes any changes in the working directory and staging area. Therefore, it resets index entries to the state of the specified commit.

Therefore, the git reset --hard option should be used with an abundance of caution, since it can lead to losing work in your staging area and working directory.

The --hard option is typically used when a local experiment has gone very wrong, and we simply want a fresh start from a previous committed state without any bad or questionable code muddying the waters.

Although git reset hard can be undone using the git reflog method described earlier, it's still important to use caution (even locally) because the Git garbage collector runs intermittently depending on a number of factors. Once Git runs its housekeeping tasks after a --hard reset, your changes are gone forever.

That being said, let's take a look at this command in action:

$ git reset --hard 32c2d09
HEAD is now at 32c2d09 Change 1

$ git status
On branch master
nothing to commit, working tree clean

$ git log --oneline
32c2d09 (HEAD -> master) Change 1
38e2a6e Initial commit

As expected, our --hard reset successfully moved our master branch tip to the specified commit while deleting all changes from the working directory and staging area. We have given ourselves a fresh start at commit 32c2d09, without any of the prior mishaps to cloud our minds going forward.

Does git reset hard remove stash?

A git stash is a temporary holding area where git allows you to shelve changes that you'd like to come back to at a later time.

When using git reset hard, your git stashes are totally safe. The --hard option will only affect the working directory and the staging index. This means that stashes along with untracked files will be left alone.

git reset vs git restore

After making a change to the working directory, you may have noticed the following output after running a git status:

$ git status
...
  (use "git restore <file>..." to discard changes in working directory)
...

After adding changes to the staging area:

$ git status
...
  (use "git restore --staged <file>..." to unstage)
...

The git restore command a relatively new addition to Git, hitting the scene in August 2019, in Git version 2.23. It isn't quite as robust as its older cousin git reset, but it is a useful feature which simplifies undoing changes, and allows for reversing individual files back to a previous state.

For resetting a branch to a previous commit AND specifying what to keep and what to discard in the process, git reset is the way to go. However, for a simple removal of changes from the working directory or staging area, or restoring individual files to a previous state, it's a good idea to familiarize yourself with git restore.

Summary

In this article, we discussed what git reset is, how it is used, how it impacts Git repositories, and showed some examples.

We saw that git reset is a command line tool that ships with the Git version control system. It is used for moving the branch label pointed to by HEAD to a previous commit, along with offering different modes that allow you to choose whether you'd like to remove changes from the working directory or the staging index.

The three modes for git reset are --mixed, --soft, and --hard. --mixed is the default mode, and we showed how these three options correspond with the three Git trees.

Overall, git reset is a powerful tool that can be used in countless scenarios. Although it has the potential to cause irreversible loss, we've shown here that it can be used safely by understanding the details of how it operates.

Next Steps

If you're interested in learning more about how Git works under the hood, check out our Baby Git Guidebook for Developers, which dives into Git's code in an accessible way. We wrote it for curious developers to learn how Git works at the code level. To do this we documented the first version of Git's code and discuss it in detail.

We hope you enjoyed this post! Feel free to shoot me an email at jacob@initialcommit.io with any questions or comments.

References

  1. Git SCM Docs, git reflog - https://git-scm.com/docs/git-reflog
  2. Git SCM Docs, git restore - https://git-scm.com/docs/git-restore

Final Notes