Restoring, Reverting & Resetting Changes in Git

11 min read·Jan 1, 2025

In Git, there are essentially three ways to rollback changes made in the working tree, the staging area, or the commit history, which are:

  1. Restoring
  2. Reverting
  3. Resetting

The most common reasons for undoing commits are usually:

  • Files that were included in the commit by mistake.
  • Typos that were introduced in the commit message.
  • New code that causes unforeseen bugs or accidental code changes.

Restoring uncommitted files

In Git, restoring consists in undoing changes made in the files present in the working tree or the staging area.

Discarding changes in the working tree

It may happen that certain changes, such as the addition of temporary logs for debugging purposes, need to be removed before they are staged.

To discard the changes made to the files in the working tree and revert them to the state they were in at the last commit, you can use the git restore command:

$ git restore file ...

Alternatively, to discard all the changes made in all the files of the working tree, you can specify the current directory as an argument of the git restore command:

$ git restore .

Warning: This command should be used with caution as it permanently removes any local changes that haven't been committed.

Example

Let's consider this file named index.js:

$ cat index.js
console.log("Good morning");

When edited, Git will mark it as "modified":

$ echo 'console.log("Guten Tag");' > index.js
$ 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:   index.js

no changes added to commit (use "git add" and/or "git commit -a")

And when restored, Git will consider the working tree clean:

$ git restore index.js
$ git status
On branch master
nothing to commit, working tree clean
$ cat index.js
console.log("Good morning");

Discarding all changes

To simultaneously remove all the files present in the staging area and revert the files in the working tree to the state they were in at the last commit, you can use the git reset command with the --hard flag:

$ git reset --hard HEAD

Where HEAD is a reference to the last commit in the history.

Warning: This command will forcefully update the working directory to match the state of the last commit, which means that any untracked files and directories will be permanently deleted.

Example

Let's consider this repository that contains a tracked file named .env.development and an untracked file named .env.production:

$ ls
.env.development     .env.production
$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.env.production

nothing added to commit but untracked files present (use "git add" to track)

When adding the .env.production file to the staging area, Git will mark this file as "new":

$ git add .env.production
$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   .env.production

When resetting the repository, Git will remove the .env.production file both from the staging area and the working tree:

$ git reset --hard HEAD
HEAD is now at bdac15a add development environment
$ git status
On branch master
nothing to commit, working tree clean
$ ls
.env.development

Reverting the last commit in the history

It often happens that the changes introduced by the last commit in the history cause bugs, errors, or unexpected behaviors.

The safest way to cancel these changes is to create a new opposite commit in the history that reverts the changes introduced by the previous one.

To create a revert commit, you can use the git revert command:

$ git revert [--no-edit|--no-commit] HEAD

Where:

  • HEAD is a reference to the last commit in the history.
  • --no-edit is a flag used to keep the default commit message suggested by Git and prevent the text editor from opening.
  • --no-commit is a flag used to revert changes to the working tree and the staging area without directly committing them to the history.

By default, this command opens the default command-line text editor, allowing you edit the commit message of the reverse commit.

Once saved and closed, the commit will automatically be added to the history.

Note: In order to execute this command, your working tree needs to be in a clean state, as otherwise, Git will throw the following error:

error: Your local changes to the following files would be overwritten by merge:
Please commit your changes or stash them before you merge.
Aborting

Example

Let's consider this history, where HEAD points to the commit hash e97698a:

$ git log --oneline
e97698a (HEAD -> master) third commit
cd2bbfe second commit
9e01fd9 first commit

When reverting the repository:

$ git revert HEAD

Git will open the default text editor asking you to enter a message for the new commit:

Revert "third commit"

This reverts commit e97698ac66bec08055e23e3cc3d1885a4b63908.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
# Your branch is up to date with 'origin/master'.
#
# Changes to be committed:
# modified:   index.js
#

Once the file is saved and the text editor closed, Git will add a new commit (fr853b1) on top of the previous one (e97698a) and move the head pointer to that new commit.

$ git log --oneline
fr853b1 (HEAD -> master) Revert "third commit"
e97698a third commit
cd2bbfe second commit
9e01fd9 first commit

Removing the last commits from the history

Another riskier method for cancelling the changes introduced by the last n commits is to physically remove them from the history.

To remove the last n commits, you can use the git reset command with the --hard flag:

$ git reset --hard HEAD~n

Where HEAD~n is a reference to the commit that is n steps before the last commit.

This command will forcefully update the working directory to match the state of commit before the last n commits and remove the last n commits from the history, which means that any untracked files and directories will be permanently deleted.

Example

Let's consider this Git history:

$ git log --oneline
e97698a (HEAD -> master) third commit
cd2bbfe second commit
9e01fd9 first commit

This command will remove the last commit from the history:

$ git reset --hard HEAD~1
$ git log --oneline
cd2bbfe (HEAD -> master) second commit
9e01fd9 first commit

Summary

Here's a summary of what you've learned in this lesson:

  • The git restore command is used to discard changes made in the working tree.
  • The git reset --hard HEAD command is used to remove all the files from the staging area and discard the changes made in the working tree.
  • The git revert HEAD command is used to create a revert commit of the last commit in the history
  • The git reset --hard HEAD~1 command is used to remove the last commit from the history and update the working tree.

Enjoying the courses?

I've made these courses completely free so anyone can learn from them. If they've helped you and you'd like to actively support the work behind BackendBrewery, you can leave a tip:

Support BackendBrewery
Restoring, Reverting & Resetting Changes in Git | Backend Brewery