How to Undo Almost Anything in Git Without Panicking

by Eric Hanson, Backend Developer at Clean Systems Consulting

The Panic Is the Problem

A developer runs git reset --hard on the wrong branch. Another does a git rebase that went sideways and now the history looks wrong. A third accidentally deleted a branch with uncommitted changes on it. In each case, the first reaction is panic — and the second is a hasty command that makes things worse.

The thing to internalize: Git almost never permanently destroys work. The object store keeps everything. Reflog tracks every HEAD movement. The question is not "is my work gone?" but "where did Git put it?" This article is a decision tree for the most common undo scenarios.

Undo an Uncommitted Change (Working Tree)

You modified a file and want to discard the changes, going back to the last committed version.

# Discard changes to a specific file
git restore src/payment/processor.py

# Discard all uncommitted changes in the working tree
git restore .

# Older syntax (still works)
git checkout -- src/payment/processor.py

Warning: git restore on the working tree is one of the few Git operations that is genuinely destructive. Uncommitted changes that aren't staged go to /dev/null. There is no reflog for working tree state. If you're not sure, stash first: git stash push -m "safety stash before restore".

Undo a Staged Change (Index)

You staged a file but haven't committed yet, and want to unstage it (keep the changes, just remove from index).

# Unstage a specific file (keeps changes in working tree)
git restore --staged src/payment/processor.py

# Unstage everything
git restore --staged .

Undo the Last Commit (Not Yet Pushed)

You committed something wrong and want to fix it before pushing.

# Undo the commit, keep changes staged
git reset --soft HEAD~1

# Undo the commit, keep changes in working tree (unstaged)
git reset HEAD~1

# Undo the commit AND discard the changes (dangerous)
git reset --hard HEAD~1

To simply fix the last commit message or add a forgotten file:

git add forgotten-file.py
git commit --amend --no-edit    # keeps same message, adds file
git commit --amend -m "Better message"  # changes message

--amend rewrites the commit. Only do this before pushing — after pushing, amending creates history divergence.

Undo a Pushed Commit (Shared History)

This is the one that trips people up. You can't amend or reset a commit that others may have pulled — that would rewrite shared history. The safe option is a revert:

# Create a new commit that undoes the changes from HEAD
git revert HEAD --no-edit

# Revert a specific commit by SHA
git revert a3f9d24 --no-edit

# Revert a range (newest first)
git revert HEAD~3..HEAD --no-edit

Revert creates a new commit. History is preserved. Anyone who already has the original commit will see the original plus the revert when they pull. Clean, transparent, and safe.

Undo a Merge

You merged a branch and it was wrong — wrong branch, wrong time, broke things.

# If not yet pushed: reset to before the merge
git reset --hard ORIG_HEAD
# Git sets ORIG_HEAD to the pre-merge state automatically during a merge

# If already pushed: revert the merge commit
# -m 1 specifies the "mainline" (the branch you merged INTO)
git revert -m 1 <merge-commit-sha>

The -m 1 flag tells Git which parent to treat as the mainline. For a typical merge commit (where main received a feature branch), parent 1 is main. Check with git log --merges to identify the merge commit SHA.

Undo a Rebase Gone Wrong

Rebase is one of the most frequently panicked-about operations. The recovery tool is reflog.

# Find the state before the rebase
git reflog

# Output looks like:
# a3f9d24 HEAD@{0}: rebase: Fix payment processor
# b7c12e1 HEAD@{1}: rebase: Add idempotency key
# f4d8a09 HEAD@{2}: rebase (start): checkout main
# 9e1b3c7 HEAD@{3}: commit: My original commit
# ...

# The entry just before "rebase (start)" is your pre-rebase state
git reset --hard HEAD@{3}

This puts your branch back to exactly where it was before the rebase. The rebase commits still exist in the object store but are no longer reachable from HEAD.

Recover a Deleted Branch

# Find the SHA of the tip of the deleted branch
git reflog | grep 'checkout: moving from deleted-branch-name'
# or
git fsck --lost-found | grep commit

# Recreate the branch at that SHA
git checkout -b recovered-branch a3f9d24

If you remember the approximate time you last worked on it, git reflog --since="2 days ago" narrows it down.

The Nuclear Option: git stash as a Safety Net

Before any operation you're unsure about, stash your working tree:

git stash push -m "safety: before risky operation $(date)"

git stash saves the working tree and index state to a stack. It's not a substitute for commits, but it gives you a checkpoint you can return to with git stash pop if something goes wrong.

The Hierarchy of Destructiveness

Not all undo operations are equally safe. From safest to most dangerous:

  1. git revert — creates new commits, never loses history
  2. git reset --soft — moves HEAD, keeps changes staged
  3. git reset (mixed, the default) — moves HEAD, keeps changes in working tree
  4. git reset --hard — moves HEAD, discards working tree changes (but commits are still in reflog)
  5. git restore on working tree — discards uncommitted working tree changes permanently

The practical rule: if you're not sure which one to use, start with the safest option that might solve your problem. You can always escalate to a more aggressive option if needed. You cannot un-run git restore on uncommitted changes.

When in doubt: git stash, then proceed.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

REST Is Not Just Using HTTP. Here Is What It Actually Means.

Most APIs labeled “REST” ignore the constraints that actually define it. Understanding what REST really requires leads to more scalable, evolvable systems—but also reveals when not to use it.

Read more

Why Your Docker Build Is Slow and How to Fix It

Slow Docker builds are almost always a caching problem — either cache misses caused by poor layer ordering, or cache being thrown away entirely in CI. Fixing them requires understanding how Docker decides what to rebuild.

Read more

The Difference Between a Mock, a Stub, and a Fake That Actually Matters

Mock, stub, and fake are used interchangeably in most codebases, but they describe different things with different tradeoffs. Knowing which to reach for — and when — determines whether your test doubles make tests clearer or more confusing.

Read more

Reactive Programming in Spring Boot — WebFlux, When to Use It, and When Not To

Spring WebFlux enables non-blocking, reactive HTTP handling. It solves a specific problem — high-concurrency I/O-bound services — and creates new problems for everything else. Here is what it actually does and the honest case for when it's worth adopting.

Read more