How to Undo Changes in Git: Reset, Revert and Restore

Git provides multiple ways to undo mistakes, including checkout, reset, and revert commands.

Undoing Changes in Git

Mistakes are an unavoidable part of software development. You might accidentally delete a block of code you meant to keep, stage the wrong file, commit too early, or push changes that should never have left your machine. The good news is that Git is designed with these scenarios in mind. Rather than punishing mistakes, Git provides a safety net that allows you to undo, correct, and recover from almost any error safely.

Understanding how to undo changes properly is essential for maintaining a clean project history and avoiding data loss. Whether you are working alone on a personal project or collaborating with a team, knowing which command to use in each situation gives you the confidence to experiment and the ability to recover when things go wrong. If you are comfortable with the basic Git workflow, you are ready to learn how to safely reverse changes.

Understanding the Three States of Git

Before undoing changes, it is important to understand the different states a file can be in within Git. Each state requires a different approach when you want to undo changes. Recognizing where your changes currently exist helps you choose the correct command.

  • Working Directory: Files you are currently editing. Changes here have not yet been told to Git. This is where you spend most of your time writing and modifying code.
  • Staging Area (Index): Changes you have selected with git add but not yet committed. This is your preparation area where you craft what goes into the next commit.
  • Repository (Commit History): Changes that have been permanently saved with git commit. Once here, they become part of your project's history.

Git provides different commands depending on where the change exists. Choosing the correct command ensures that you only undo what you intend to without affecting other work.

Undoing Changes in the Working Directory

The most common mistake is modifying a file and then realizing you want to discard those changes and go back to the last committed version. If you have not yet staged the changes, you can discard them completely and restore the file to its last committed state.

Discard uncommitted changes in a file:
# Restore a single file to its last committed state
git checkout -- file.txt

# Using modern restore command (preferred in newer Git versions)
git restore file.txt

# Discard all changes in the working directory
git restore .

This command removes all local modifications to the file. It is important to note that this action cannot be undone. Once you discard changes, they are gone unless you have them saved elsewhere. Always double-check that you do not need the changes before running this command.

Unstaging Changes

If you have already added changes to the staging area using git add but realize you staged the wrong file or want to make additional changes before committing, you can unstage them without losing your work. The changes remain in your working directory.

Remove files from staging:
# Unstage a specific file
git reset file.txt

# Unstage all staged files
git reset

# Using modern restore command
git restore --staged file.txt

# Unstage everything with modern syntax
git restore --staged .

These commands move the file back to the working directory while preserving your changes. This allows you to review, modify, or split your changes into multiple commits before staging them again.

Undoing the Last Commit (Keeping Changes)

Perhaps the most common undo scenario: you made a commit but realized you forgot to include a file, or you want to change the commit message. You can undo the last commit while keeping your changes intact, allowing you to stage additional changes and recommit.

Soft reset to undo commit but keep changes:
# Undo last commit, keep changes staged
git reset --soft HEAD~1

# Undo last commit, keep changes unstaged
git reset --mixed HEAD~1

# This is the default if you don't specify --soft or --hard
git reset HEAD~1

The difference between --soft and --mixed is subtle but important. --soft keeps your changes in the staging area, ready to be committed again immediately. --mixed (the default) keeps your changes in the working directory but unstaged, giving you a chance to review or reorganize them before staging again.

Undoing a Commit Completely (Discarding Changes)

Sometimes you want to remove a commit and discard all the changes it introduced entirely. This is useful when you committed something that was completely wrong or experimental and you want to start over.

Hard reset to completely remove a commit:
# Remove the last commit and all its changes
git reset --hard HEAD~1

# Remove the last 3 commits and all their changes
git reset --hard HEAD~3

This command permanently deletes the specified commits and all changes associated with them. Use it with extreme caution. Once you perform a hard reset, the changes are gone and cannot be easily recovered. Never use --hard on commits that have already been pushed to a shared repository.

Reverting a Commit (Safe for Shared History)

In collaborative projects where others may have based their work on your commits, rewriting history with reset is dangerous and can cause significant problems. The safer alternative is git revert. Instead of deleting commits, revert creates a brand new commit that undoes the changes introduced by a previous commit.

Revert a commit safely:
# Revert a specific commit (opens editor for commit message)
git revert commit-id

# Revert without opening editor
git revert --no-edit commit-id

# Revert the most recent commit
git revert HEAD

# Revert a range of commits
git revert HEAD~3..HEAD

Revert is the recommended approach for undoing changes in shared branches because it does not rewrite history. It simply adds a new commit that says "this change should not have happened." The original commit remains in the history for transparency, and other developers can pull the revert without conflicts.

Undoing Changes After Push

If you have already pushed changes to a remote repository, undoing them requires extra caution. Rewriting history after a push can affect other developers who have already pulled your changes. The safest approach is to use revert rather than reset.

Undo a pushed commit safely:
# Revert the problematic commit locally
git revert commit-id

# Push the revert commit to the remote
git push origin main

If you absolutely must use reset on a pushed branch and you are certain no one else has pulled it, you can force push after reset. However, this should only be done on private branches or in exceptional circumstances with full team coordination.

Force push after reset (use with extreme caution):
# Reset locally
git reset --hard HEAD~1

# Force push to remote (overwrites history)
git push --force origin main

# Safer alternative that fails if remote has new commits
git push --force-with-lease origin main

Amending the Last Commit

If you made a small mistake in your last commit, such as a typo in the commit message or forgetting to include a file, you can amend the commit instead of creating a new one. This is one of the most common undo operations in daily development.

Amend the most recent commit:
# Add forgotten file to staging
git add forgotten-file.js

# Amend the commit (adds to last commit, opens editor for message)
git commit --amend

# Amend commit message without changing content
git commit --amend -m "New commit message"

# Add files and amend without opening editor
git add forgotten-file.js
git commit --amend --no-edit

Amending is a form of rewriting history, so avoid using it on commits that have already been pushed to a shared branch. For local commits, it is a convenient way to keep your history clean by avoiding "fix typo" or "add missing file" commits.

Using git restore (Modern Approach)

Newer versions of Git introduce the git restore command as a more intuitive way to undo changes. This command separates the concerns of restoring working directory files and unstaging changes, making it easier for beginners to understand.

Modern restore commands:
# Discard changes in working directory
git restore file.txt

# Discard all changes in working directory
git restore .

# Unstage a file (remove from staging, keep changes)
git restore --staged file.txt

# Unstage all files
git restore --staged .

# Restore a file to a specific commit's version
git restore --source HEAD~2 file.txt

The git restore command is now the recommended approach for these operations, as it clearly separates the two distinct actions: restoring working directory files and un-staging files. It is available in Git version 2.23 and later.

Common Undo Scenarios and Solutions

Here is a quick reference for common real-world scenarios and the appropriate commands to handle them:

  • Accidentally edited a file and want to revert: Use git restore file.txt or git checkout -- file.txt
  • Staged the wrong file: Use git restore --staged file.txt or git reset file.txt
  • Wrong commit message: Use git commit --amend -m "New message"
  • Forgot to include a file in the last commit: Stage the file and use git commit --amend --no-edit
  • Committed too early and need to add more changes: Use git reset --soft HEAD~1, stage more changes, then commit again
  • Need to undo a commit already pushed to shared branch: Use git revert commit-id and push
  • Want to completely remove the last local commit and its changes: Use git reset --hard HEAD~1

Recovering Lost Commits with Reflog

Git's reflog is your safety net when things go wrong. It records every movement of HEAD, including commits that you may have thought were lost. Even after a hard reset, the commits still exist in the reflog for a period of time.

Recover lost commits using reflog:
# View the reflog to find lost commits
git reflog

# Output shows commit hashes and actions
# abc1234 HEAD@{0}: reset: moving to HEAD~1
# def5678 HEAD@{1}: commit: Added important feature

# Recover by creating a branch at the lost commit
git branch recovered-branch def5678

# Or reset back to that commit
git reset --hard def5678

The reflog is local to your repository and is not shared when you push. It is a powerful recovery tool that can save you from what might otherwise be permanent data loss. However, it only retains entries for about 90 days by default, after which they are cleaned up by garbage collection.

Best Practices for Undoing Changes

Undoing changes is powerful but should be done thoughtfully. Following these best practices helps you avoid common pitfalls and maintain a clean, collaborative workflow.

  • Review before committing: Use git diff to see exactly what you are about to commit. A few seconds of review can prevent many undo operations later.
  • Commit early and often: Small, frequent commits are easier to undo or revert than large, monolithic commits. They give you more granular control over your history.
  • Use revert for shared branches: Never rewrite history on branches that others are using. Revert is the safe choice for public branches.
  • Understand the scope: Know whether you are affecting only your working directory, the staging area, or the commit history before running a command.
  • Create backup branches before risky operations: Before performing a hard reset or rebase, create a backup branch to preserve the current state.
  • Test after undoing: After any undo operation, test your code to ensure it behaves as expected.

These practices are also part of Git best practices and help you maintain a stable and predictable workflow.

Common Mistakes to Avoid

Many beginners misuse undo commands, leading to unintended data loss or complicated recovery scenarios. Being aware of these common mistakes helps you avoid them.

  • Using --hard without understanding consequences: A hard reset cannot be easily undone. Always double-check what you are about to discard.
  • Rewriting history on shared branches: Using reset on a branch that others have pulled forces everyone to recover from the rewritten history, causing significant disruption.
  • Not checking the current branch: Make sure you are on the correct branch before running undo commands. It is easy to undo the wrong work.
  • Ignoring the reflog: Many developers do not know the reflog exists. If you make a mistake, the reflog is often your best path to recovery.
  • Confusing revert and reset: Revert adds a new commit; reset removes commits. Using one when you mean the other can lead to unexpected results.
  • Amending pushed commits: Amending changes the commit hash, which causes divergence for anyone who has pulled the original commit.

Frequently Asked Questions

  1. Is it safe to undo changes in Git?
    Yes, as long as you use the correct command for your situation. Git is designed to protect your data, and most undo operations are reversible themselves through the reflog. The exception is discarding uncommitted changes in the working directory, which cannot be recovered.
  2. What is the safest way to undo a commit?
    For shared repositories, git revert is the safest method because it does not rewrite history. For local commits that have not been pushed, git reset --soft or git reset --mixed are safe and preserve your changes.
  3. Can I recover deleted commits?
    Yes, in many cases. The reflog records all movements of HEAD, including commits that were removed with reset. As long as the commits are still in the reflog, you can recover them by resetting to the reflog entry or creating a branch at that commit.
  4. Should I use reset or revert?
    Use reset for local changes that have not been shared. Use revert for any commit that has already been pushed to a shared repository. Reset rewrites history; revert preserves it.
  5. What is the difference between git restore and git reset?
    git restore is a newer command introduced to separate the concerns of restoring files (working directory) and unstaging files. git reset is more powerful and can also move branch pointers. For simple undo operations, git restore is often clearer and more intuitive.
  6. What should I learn next after mastering undo operations?
    Understanding how to undo changes gives you confidence to experiment with more advanced Git features. Continue with using .gitignore to manage tracked files and prevent sensitive data from entering your repository. Then explore branching fundamentals and rebasing to manage complex workflows.

Conclusion

Undoing changes in Git is an essential skill that transforms version control from a rigid system into a flexible safety net. With commands like reset, revert, restore, and amend, you can confidently make changes knowing that you have the tools to fix mistakes at every stage of development. The key is understanding where your changes currently live and choosing the appropriate command for that state.

By mastering these undo operations, you gain the freedom to experiment, the confidence to refactor, and the ability to recover from errors without panic. Whether you are fixing a typo in a commit message, discarding experimental code that did not work out, or reverting a problematic deployment, Git provides a safe and reliable path forward.

Practice these commands in a test repository to build muscle memory and confidence. The more comfortable you become with undoing changes, the more effectively you can use Git for complex development workflows. Combine this knowledge with concepts like branching fundamentals and rebasing to build a complete, professional Git skill set.