Undoing Git Changes | git reset

The git reset command

If git revert is the safe way to undo changes, git reset can be considered the dangerous way. When changes are undone with git reset, and the original commits can no longer be reached by refs or the reflog, there may be no way to restore them. In other words, this undo operation itself cannot always be undone. git reset is one of the few Git commands that can lose work, so use it carefully.

Like git checkout, git reset is a versatile command with several modes. It can remove committed snapshots, but it is used more often to undo changes in the staging area and working directory. In any case, use it only for local changes. Never use it to undo commits that have already been published for other developers.

Usage

git reset <file>

Removes the specified file from the staging area without changing the working directory. This unstages the file without discarding its modifications.

git reset

Resets the staging area to match the last commit without changing the working directory. This unstages all files, letting you rebuild the staged snapshot from the beginning.

git reset --hard

Resets both the staging area and working directory to match the last commit. The --hard flag tells Git to unstage changes and discard every change in the working directory. This makes all uncommitted changes disappear, so make sure you really want to delete the work on your local machine.

git reset <commit>

Moves the tip of the current branch to <commit> and resets the staging area to match it, while leaving the working directory unchanged. After running this command, the changes after <commit> remain in the working directory, so you can create smaller, cleaner snapshots and commit them again.

git reset --hard <commit>

Moves the tip of the current branch to <commit> and resets both the staging area and working directory to match it. This command discards all changes after <commit>, including commits that were created after that point.

Options

  • --soft: preserves both the index and the working tree.
  • --mixed: resets the index but preserves the working tree. This is the default option.
  • --hard: resets both the index and the working tree.

Additional notes

All of the commands above remove changes from the repository state in some way. Without the --hard flag, git reset is a way to clean up a repository by unstaging changes or undoing a series of snapshot commits and then rebuilding them. --hard is useful when experimental work produced unwanted results and you want to discard it completely.

While revert safely undoes published commits by preserving history, git reset is intended for undoing local changes. The two commands have different purposes: reset removes history, while revert preserves history and creates a new commit that restores the content.

Git Tutorial: Revert and reset

Never reset published commits

If you have pushed a snapshot after <commit> to a public repository even once, do not use git reset <commit>. Once a commit is published, assume that other developers are working based on it.

Undoing commits that other developers are using can seriously disrupt collaboration. When they try to synchronize with your repository, it appears that part of the project history is missing. The usual example uses origin/master as the branch in the central repository corresponding to the local master branch.

Git Tutorial: Undoing published commits

If you create new commits after the reset, Git treats the local history as having diverged from origin/master. The merge commit needed to synchronize the local repository can confuse other developers and interfere with their work.

The important point is to recognize that git reset <commit> is for discarding local experimental work that did not turn out well, not for undoing published changes. If you need to correct a published commit, use the command designed for that purpose: git revert.

Examples

Unstaging a file

git reset is often used while preparing a staged snapshot. In the following example, assume that hello.py and main.py are already tracked by the repository.

# Edit both hello.py and main.py

# Stage everything in the current directory
git add .

# Realize that the changes in hello.py and main.py
# should be committed in different snapshots

# Unstage main.py
git reset main.py

# Commit only hello.py
git commit -m "Make some changes to hello.py"

# Commit main.py in a separate snapshot
git add main.py
git commit -m "Edit main.py"

As shown above, git reset lets you unstage changes that do not belong in the next commit and make the purpose of each commit clearer.

Deleting local commits

The following example shows a more advanced use case. Suppose you have been experimenting for a while, committed several snapshots, and now want to delete all of them.

# Create a new file called `foo.py` and add some code to it

# Commit it to the project history
git add foo.py
git commit -m "Start developing a crazy feature"

# Edit `foo.py` again and change some other tracked files, too

# Commit another snapshot
git commit -a -m "Continue my crazy feature"

# Decide to scrap the feature and remove the associated commits
git reset --hard HEAD~2

git reset HEAD~2 moves the current branch back by two commits. In practice, it deletes the two most recent snapshots from the project history. As mentioned earlier, this kind of reset must be limited to unpublished commits. Never do this after pushing commits to a public repository.

More examples

Cancel an index addition

Cancels adding a file to the index. The working tree changes are preserved.

git reset [file]
git reset HEAD [file]

Cancel commits

Cancels the latest commit while preserving the working tree. This is useful when you committed but have not pushed yet.

git reset HEAD^

Cancels the last two commits while preserving the working tree.

git reset HEAD~2

Cancels the last two commits and restores both the index and working tree.

git reset --hard HEAD~2

Cancels a merge commit that has already been committed. This is useful when you already committed an incorrect merge.

git reset --hard ORIG_HEAD

Creates a new commit that undoes the changes from HEAD. This is useful when the commit has already been pushed.

git revert HEAD

Restore the entire working tree

Restores the entire working tree to the state of the last commit. All working tree and index changes after the last commit disappear. This is useful when you have not committed the changes.

git reset --hard HEAD