3 Ways to Undo in Git — And Only One Is Safe to Push

Published  ·  LithiumGit Team  ·  9 min read

Git reset, git revert, and git restore all promise the same thing — undo something you did not mean to do. That similarity is exactly why they get confused so often. In reality, each command operates on a different part of Git (commit history, the staging area, or the working directory), and picking the wrong one can either quietly rewrite shared history or permanently delete work you cannot get back. This guide breaks down exactly what each command touches, when it is safe, and which one you should reach for in every common situation.

The Core Difference in One Sentence

Git ResetDeletes commits from your current branch, optionally rewriting the staging area and working directory to match. Rewrites history.
Git RevertCreates a brand-new commit that applies the exact opposite of an existing commit. History is never rewritten — the mistake stays visible, but its effect is undone.
Git RestoreDiscards uncommitted changes in specific files, either in your working directory or in the staging area. Never touches commit history or branch pointers at all.

Think of it this way: reset rewinds the tape, revert records a correction over the top without erasing the original, and restore only cleans up what is on your desk right now — it never touches the tape at all.

Git Reset — Deleting Commits From Your Branch

git reset deletes one or more commits from your current branch, moving it back to an earlier point in history. What happens to the changes those deleted commits contained — and to your working directory — depends entirely on which flag you use: --soft, --mixed (the default), or --hard.

Before and after a git reset

Below, feature has three commits (F1, F2, F3). Running git reset HEAD~1 moves feature back one commit — F3 is deleted from the branch entirely.

Commit graph showing the feature branch with commits F1, F2, F3 before running git reset

Before — feature has three commits: F1, F2, F3

Commit graph after git reset HEAD~1 showing F3 deleted and feature now ending at F2

After git reset HEAD~1 — F3 is deleted; feature now ends at F2

The three modes of reset

Terminal
# --soft: delete the commit(s), but keep all their changes staged, ready to re-commit.
git reset --soft HEAD~1

# --mixed (default): delete the commit(s) and unstage their changes, but keep them in your files.
git reset HEAD~1

# --hard: delete the commit(s) and discard staged + working directory changes entirely.
git reset --hard HEAD~1
ModeCommit(s)Staging areaWorking directory
--softDeleted from branchUnchanged (stays staged)Unchanged
--mixed (default)Deleted from branchReset (unstaged)Unchanged
--hardDeleted from branchReset (unstaged)Reset (changes deleted)
⚠️ Danger zoneRunning git reset --hard permanently discards any uncommitted work in your working directory. There is no undo for that part. Going further back, e.g. git reset --hard HEAD~2, also permanently removes those commits from your branch history.

Key characteristics of Git Reset

  • Deletes commits — everything after the reset point is removed from your branch history
  • Local-only tool — safe on commits you have not pushed or shared
  • Three modes — soft, mixed, and hard give you increasing levels of destructiveness
  • Dangerous on shared branches — resetting a pushed branch and force-pushing can erase teammates' work

Git Revert — Undo Without Rewriting History

git revert takes an existing commit, computes the inverse of its changes, and applies that inverse as a brand-new commit. Nothing is deleted or rewritten — the original "mistake" commit is still there in the log, followed by a new commit that cancels it out.

Before and after a git revert

Starting from the same three commits, running git revert on one of them adds a brand-new commit — F4 — on top. F1, F2, and F3 are untouched; the log simply gains an additional commit that cancels out the change.

Commit graph showing the feature branch with commits F1, F2, F3 before running git revert

Before — feature has three commits: F1, F2, F3

Commit graph after git revert showing a new commit F4 added on top that undoes an earlier change

After git revert — F4 is added to undo the change; F1–F3 stay intact

Reverting a single commit

Terminal
# Undo the changes introduced by a specific commit, keeping history intact
git revert <commit-hash>

# Revert without immediately committing, so you can inspect or combine changes first
git revert --no-commit <commit-hash>

# Revert a range of commits
git revert <oldest-commit-hash>..<newest-commit-hash>

Because revert produces a new commit rather than deleting old ones, it is the only one of the three commands that is always safe on a branch other people have already pulled. Anyone who fetches the branch simply sees the correction commit — nobody's local history breaks.

Key characteristics of Git Revert

  • Non-destructive — no commit is ever deleted or rewritten
  • Safe on shared branches — the standard way to undo something already pushed
  • Fully auditable — the log shows both the mistake and the fix
  • Can conflict — reverting an old commit may conflict with later changes to the same lines

Git Restore — Discarding File Changes, Nothing Else

git restore was introduced in Git 2.23 (2019) specifically to split apart the overloaded git checkout command. Before restore existed, git checkout was used both for switching branches and for discarding file changes — two very different operations sharing one confusing command. Git 2.23 introduced git switch for branches and git restore for files, so each command now does exactly one thing.

Unlike reset and revert, git restore never moves your branch pointer and never touches commit history. It only affects uncommitted changes in your working directory or staging area, file by file.

Restoring the working directory vs the staging area

Terminal
# Discard uncommitted changes to a file in your working directory
git restore src/app.ts

# Unstage a file — keep its edits, just remove it from the staging area (--staged)
git restore --staged src/app.ts

# Discard changes to every file in the working directory
git restore .

# Restore a file to its state at a specific commit
git restore --source=<commit-hash> src/app.ts
💡 Restore vs reset --mixedgit restore --staged file does roughly what git reset file used to do — unstage a file without touching your edits. The difference is scope: reset can also delete whole commits from your branch, while restore only ever touches the uncommitted changes in the files.

Key characteristics of Git Restore

  • Never touches commit history — branch is never affected
  • File-scoped — you target specific files or paths, not the whole commit graph
  • Two targets — the working directory (default) or the staging area (--staged)
  • Destructive to uncommitted edits — discarded working directory changes cannot be recovered

Reset vs Revert vs Restore — Side-by-Side Comparison

FeatureGit ResetGit RevertGit Restore
What it operates onCommits (deletes them), staging area, working directoryCommit history (via a new commit)Working directory or staging area
Rewrites history?YesNoNo — history isn't touched at all
Creates a new commit?NoYesNo
Safe on shared/pushed branches?No — avoid after pushingYes — the standard safe undoYes — only affects your local uncommitted changes
Can permanently delete work?Yes (with --hard)NoYes (discarded working directory edits)
Typical scopeWhole commitsA single commit's changesIndividual files
Best forUndoing local, unpushed commitsUndoing a commit that is already publicThrowing away uncommitted edits or unstaging a file

When to Use Git Reset

  • Undoing your most recent local commits before anyone else has pulled them
  • Collapsing several unpushed "WIP" commits back into staged changes with --soft
  • Throwing away a local experiment entirely with --hard
  • Never on a branch you have already pushed and shared, unless you fully understand the force-push consequences

When to Use Git Revert

  • Undoing a commit that has already been pushed to a shared branch
  • Rolling back a bad release or hotfix while keeping a clear audit trail of what happened
  • Any time you want the undo itself to be reviewable, e.g. through a pull request

When to Use Git Restore

  • Discarding local edits to a file you decided not to keep
  • Unstaging a file you added with git add by mistake
  • Recovering a single file back to how it looked at an earlier commit, without affecting anything else
💡 Golden RuleIf the commit has been pushed and someone else might have it, revert. If it is still local and only yours, reset. If nothing has been committed yet, restore.

Frequently Asked Questions

What is the difference between git reset, git revert, and git restore?
Git reset deletes commits from your current branch, moving it back to an earlier point in history, and optionally changes the staging area and working directory too. Git revert creates a new commit that undoes the changes of a previous commit, without deleting anything. Git restore discards changes in the working directory or staging area for specific files, without touching commit history at all.
When should I use git revert instead of git reset?
Use git revert when the commit you want to undo has already been pushed or shared with other people. Revert adds a new commit instead of rewriting history, so it is safe on shared branches. Use git reset only on local, unpushed commits that nobody else has pulled.
What is the difference between git reset --soft, --mixed, and --hard?
git reset --soft deletes the commit(s) but keeps all their changes staged, ready to re-commit. --mixed (the default) deletes the commit(s) and unstages the changes, but keeps them in your working directory. --hard deletes the commit(s) and discards all staged and working directory changes permanently.
Why did Git introduce git restore if git checkout already existed?
Git checkout historically did too many unrelated things: switching branches, restoring files, and detaching HEAD. Git 2.23 split this into two dedicated commands — git switch for changing branches, and git restore for discarding file changes — making each command's purpose explicit.
Can I recover changes removed by a git reset --hard?
No, any uncommitted changes in your working directory that were discarded by --hard are gone forever.
Can I recover a git reset --hard HEAD~1 ?
Often yes, as long as the commit still exists in Git's reflog. Running git reflog shows recent HEAD movements, and you can recover the lost commit with git reset --hard <commit-hash>. However, uncommitted working directory changes discarded by --hard are gone for good.