DevToolBoxFREE
Blog

Git Undo Last Commit: 5 Ways to Fix Mistakes (Keep Your Changes)

7 min readby DevToolBox

Made a wrong commit? Don't panic. Git gives you multiple ways to undo commits while keeping your code changes intact. Whether you need to fix a commit message, unstage files, or completely reverse a pushed commit, this guide covers all 5 methods with clear explanations and practical examples.

Quick Answer: Undo Last Commit (Keep Changes)

If you just want to undo your last commit and keep all changes staged (ready to commit again), run this single command:

$ git reset --soft HEAD~1

This moves HEAD back one commit but leaves all your changes in the staging area. Your code is untouched — you can modify files, fix the commit message, or add more changes before committing again.

What each part means:

  • git resetgit reset — the command to move HEAD to a different commit
  • --soft--soft — keep changes staged (in the index)
  • HEAD~1HEAD~1 — one commit before the current HEAD
# Before: You committed something wrong
$ git log --oneline
a1b2c3d (HEAD -> main) wrong commit message
f4e5d6c previous good commit

# Undo it:
$ git reset --soft HEAD~1

# After: Commit is gone, changes are staged
$ git log --oneline
f4e5d6c (HEAD -> main) previous good commit

$ git status
Changes to be committed:
  modified:   src/app.tsx
  modified:   src/utils.ts

Method 1: git reset --soft (Undo Commit, Keep Staged)

git reset --soft HEAD~1 is the gentlest way to undo a commit. It moves HEAD back by one commit, but your changes remain staged. This is perfect when you want to re-do the commit with a different message or add more files.

When to use:

  • You want to change the commit message
  • You forgot to add a file to the commit
  • You want to split one commit into two
  • You want to combine this with other changes before committing
# Undo last commit, keep changes staged
$ git reset --soft HEAD~1

# Fix the commit message and recommit
$ git commit -m "correct commit message"

# Or add a forgotten file and recommit
$ git add forgotten-file.ts
$ git commit -m "feature: add login with all files"

# Or split into two commits
$ git reset --soft HEAD~1
$ git reset HEAD src/unrelated-file.ts   # unstage one file
$ git commit -m "feature: add login"
$ git add src/unrelated-file.ts
$ git commit -m "fix: update config"

After running this command, git status shows your changes as "Changes to be committed" (green). You can immediately commit again.

Method 2: git reset --mixed (Undo Commit, Keep Unstaged)

git reset --mixed HEAD~1 (or simply git reset HEAD~1, since --mixed is the default) undoes the commit AND unstages the changes. Your files are preserved, but they appear as unstaged modifications.

When to use:

  • You want to selectively re-stage only some files
  • You want to review changes before re-committing
  • You committed the wrong files and need to reorganize
  • You want to split a large commit into several smaller ones
# Undo last commit, unstage all changes
$ git reset HEAD~1          # --mixed is the default
# or explicitly:
$ git reset --mixed HEAD~1

# Now selectively stage what you need
$ git status
Changes not staged for commit:
  modified:   src/auth.ts
  modified:   src/config.ts
  modified:   src/test.ts

# Stage only what belongs in this commit
$ git add src/auth.ts
$ git commit -m "feature: add authentication"

# Stage the rest separately
$ git add src/config.ts src/test.ts
$ git commit -m "chore: update config and tests"

After running this command, git status shows your changes as "Changes not staged for commit" (red). You need to git add files before committing again.

Difference from --soft: With --soft, changes stay staged. With --mixed, changes are unstaged. Both keep your files intact.

Method 3: git commit --amend (Modify Last Commit)

git commit --amend doesn't technically "undo" the commit — it replaces the last commit with a new one. This is the fastest way to fix a commit message or add forgotten files without creating extra commits in your history.

When to use:

  • Fix a typo in the commit message
  • Add a file you forgot to include
  • Remove a file that shouldn't have been committed
  • Update the commit with small additional changes
# Fix commit message only
$ git commit --amend -m "fix: correct the typo in error handler"

# Add a forgotten file to the last commit
$ git add forgotten-file.ts
$ git commit --amend --no-edit    # keeps the original message

# Remove a file from the last commit
$ git reset HEAD^ -- secrets.env  # unstage the file
$ git commit --amend --no-edit

# Change both message and content
$ git add extra-changes.ts
$ git commit --amend -m "feature: complete login with validation"

# View the amended result
$ git log --oneline -1
b2c3d4e (HEAD -> main) feature: complete login with validation

Warning: --amend rewrites the commit hash. Do NOT amend commits that have been pushed to a shared branch unless you coordinate with your team.

Method 4: git revert (Safe Undo for Pushed Commits)

git revert creates a NEW commit that undoes the changes from a previous commit. Unlike reset, it does not rewrite history — it adds to it. This is the only safe way to undo commits on shared/public branches.

When to use:

  • The commit has already been pushed to a shared branch
  • Other developers have pulled the commit
  • You need an auditable undo (the revert itself is recorded)
  • Working on main/master or any protected branch
# Revert the last commit (creates a new commit)
$ git revert HEAD

# Git opens editor with default message:
# Revert "add broken feature"
# This reverts commit a1b2c3d.

# Skip the editor, use default message
$ git revert HEAD --no-edit

# Revert a specific commit by hash
$ git revert a1b2c3d

# Revert without committing (stage changes only)
$ git revert HEAD --no-commit
$ git status
Changes to be committed:
  modified:   src/feature.ts    # changes are reversed
$ git commit -m "revert: remove broken feature"

# Result: history shows both the original and the revert
$ git log --oneline
e5f6g7h (HEAD -> main) Revert "add broken feature"
a1b2c3d add broken feature
f4e5d6c previous good commit

How it works: If your commit added a line, the revert commit removes that line. If your commit deleted a file, the revert commit restores that file.

Key difference from reset: Reset removes commits from history. Revert adds a new commit that undoes changes. Reset is for local/unpushed commits. Revert is for pushed/shared commits.

Method 5: git reflog (Recover Lost Commits)

git reflog is your safety net. It records every move of HEAD in your local repository — even after destructive operations like reset --hard. If you accidentally lost commits, reflog can help you recover them.

When to use:

  • You ran git reset --hard and lost changes
  • You need to recover a commit after a bad rebase
  • You accidentally deleted a branch with unmerged commits
  • You need to find a previous state of your repository
# View the reflog (recent HEAD movements)
$ git reflog
a1b2c3d (HEAD -> main) HEAD@{0}: reset: moving to HEAD~1
f4e5d6c HEAD@{1}: commit: add important feature
b7c8d9e HEAD@{2}: commit: previous work
...

# Oh no! I accidentally reset --hard and lost my commit!
# Find it in reflog:
$ git reflog
b7c8d9e (HEAD -> main) HEAD@{0}: reset: moving to HEAD~1
f4e5d6c HEAD@{1}: commit: add important feature   # <-- there it is!

# Recover it:
$ git reset --hard f4e5d6c
# or create a new branch from it:
$ git branch recover-branch f4e5d6c

# Recover after a bad rebase:
$ git reflog
c1d2e3f (HEAD -> feature) HEAD@{0}: rebase (finish)
a1b2c3d HEAD@{1}: rebase (start)
x9y8z7w HEAD@{2}: commit: my work before rebase  # <-- before rebase

$ git reset --hard x9y8z7w   # back to pre-rebase state

# Recover a deleted branch:
$ git branch -D feature-branch    # oops!
$ git reflog | grep feature
h1i2j3k HEAD@{5}: commit: work on feature-branch
$ git checkout -b feature-branch h1i2j3k   # recovered!

Important: Reflog is local only. It tracks HEAD movements on YOUR machine. It does not track other people's actions, and entries expire after 90 days by default.

Comparison: All 5 Methods Side by Side

MethodCommandChanges Kept?History Rewritten?Safe for Pushed?Best For
reset --softgit reset --soft HEAD~1Yes (staged)YesNoRecommit / fix message
reset --mixedgit reset HEAD~1Yes (unstaged)YesNoSelective re-staging
commit --amendgit commit --amendYes (replaced)YesNoQuick fix last commit
revertgit revert HEADNo (new commit)NoYesPushed / shared commits
refloggit reflog + resetRecovers lost--Recovering lost commits
# Visual: How the 3 reset modes affect your changes

                    Working Dir    Staging Area    Repository
                    ───────────    ────────────    ──────────
reset --soft          ✓ kept        ✓ kept        ✗ commit removed
reset --mixed         ✓ kept        ✗ unstaged    ✗ commit removed
reset --hard          ✗ DELETED     ✗ DELETED     ✗ commit removed

commit --amend        ✓ kept        ✓ merged      ✗ commit replaced
revert                ✓ kept        ✓ new commit  ✓ preserved (adds new)

Already Pushed? How to Handle Shared Branches

What you do after an undo depends on whether the commit has been pushed and whether others have pulled it.

Scenario 1: Pushed, but nobody pulled yet

You can use reset + force push, but use --force-with-lease to avoid overwriting others' work:

# Undo the commit locally
$ git reset --soft HEAD~1

# Make your fix
$ git add .
$ git commit -m "correct commit"

# Force push (safe version)
$ git push --force-with-lease origin my-branch

Warning: Force pushing rewrites remote history. Only do this on branches you own.

Scenario 2: Pushed, and others have pulled

Use git revert. This is the only safe option:

# Create a revert commit
$ git revert HEAD --no-edit

# Push normally (no force needed)
$ git push origin main

Scenario 3: Pushed to a protected branch (main/master)

Most teams protect main from force pushes. You must use revert:

# On a protected branch, revert is your only option
$ git revert HEAD --no-edit
$ git push origin main

# Or revert via a pull request:
$ git checkout -b revert-broken-feature
$ git revert HEAD --no-edit
$ git push origin revert-broken-feature
# Then create a PR to merge the revert

Rule of thumb: If in doubt, use git revert. It is always safe.

Undo Multiple Commits: HEAD~2, HEAD~3

You can undo more than one commit by changing the number after HEAD~. The number specifies how many commits to go back.

# Undo last 2 commits (keep changes staged)
$ git reset --soft HEAD~2

# Undo last 3 commits (keep changes unstaged)
$ git reset HEAD~3

# Undo last 5 commits (DISCARD all changes - dangerous!)
$ git reset --hard HEAD~5

HEAD~N means "N commits before HEAD". You can also use commit hashes instead:

# Reset to a specific commit hash
$ git log --oneline
a1b2c3d (HEAD) fourth commit
e4f5g6h third commit
i7j8k9l second commit
m0n1o2p first commit

# Go back to "second commit"
$ git reset --soft i7j8k9l
# Now "third commit" and "fourth commit" changes are staged

Warning: Undoing multiple commits with reset on a shared branch is dangerous. Use revert for each commit instead:

# Safe way to undo multiple pushed commits: revert each one
# Revert in reverse order (newest first)
$ git revert HEAD --no-edit        # undo commit 3
$ git revert HEAD~1 --no-edit      # undo commit 2

# Or revert a range (Git 1.7.2+)
$ git revert HEAD~2..HEAD --no-edit

# Push all reverts at once
$ git push origin main

Note: When reverting multiple commits, revert them in reverse order (newest first) to avoid conflicts.

Frequently Asked Questions

What is the difference between git reset --soft, --mixed, and --hard?

--soft keeps changes staged (ready to commit). --mixed (default) keeps changes but unstages them. --hard discards all changes permanently. Both --soft and --mixed are safe — your code is preserved. Only --hard is destructive.

Can I undo a git reset --hard?

Yes, if you act quickly. Use git reflog to find the commit hash before the reset, then run git reset --hard <hash> to restore it. However, uncommitted changes that were discarded by --hard cannot be recovered by Git (they were never recorded).

Should I use reset or revert?

Use reset for local/unpushed commits — it gives a cleaner history. Use revert for pushed/shared commits — it is safe and doesn't break other developers' history. When in doubt, use revert.

How do I undo a merge commit?

For an unpushed merge: git reset --hard HEAD~1 (this discards the merge). For a pushed merge: git revert -m 1 HEAD (the -m 1 flag tells Git which parent to revert to, usually the main branch). Be careful: reverting a merge makes it tricky to re-merge the same branch later.

What does HEAD~1 mean vs HEAD^?

For most cases, HEAD~1 and HEAD^ are identical — both refer to the parent of the current commit. The difference matters for merge commits: HEAD^ is the first parent (usually main), HEAD^2 is the second parent (the merged branch). HEAD~2 means "grandparent" (two commits back in the first-parent chain).

How do I undo a commit but keep the files on disk?

Use git reset --soft HEAD~1 (keeps changes staged) or git reset HEAD~1 (keeps changes unstaged). Both preserve your files. Only git reset --hard HEAD~1 deletes your changes. If you already used --hard, check git reflog immediately.

𝕏 Twitterin LinkedIn
Was this helpful?

Stay Updated

Get weekly dev tips and new tool announcements.

No spam. Unsubscribe anytime.

Try These Related Tools

🔀Git Command Generator.gi.gitignore Generator±Text Diff Checker

Related Articles

Git Commands Cheat Sheet: Essential Commands Every Developer Needs

Complete Git commands cheat sheet covering setup, branching, merging, rebasing, stashing, and advanced workflows with practical examples.

Git Rebase vs Merge: When to Use Each (with Visual Examples)

Understand the difference between git rebase and merge. Learn when to use each, avoid common pitfalls, and master your Git workflow.