Undo the last Git commit: a practical Git & GitHub guide. Different situations, different commands.
We've all been there: you commit something you didn't mean to, and now it's sitting in your Git history. The good news is that undoing the last Git commit doesn't require heroic efforts or wiping your repository clean. Whether you need to keep your changes, discard them entirely, or handle a commit that's already pushed, Git gives you several tools to fix the situation with minimal friction.
The key is knowing which command fits your situation. Undo the last Git commit with confidence by choosing the right approach for your workflow.
Understanding the three reset modes
Before diving into specific scenarios, let's clarify what happens during a Git reset. The git reset command moves the HEAD pointer and can optionally modify both the index (staging area) and the working directory. Git provides three main modes that differ in what gets preserved and what gets discarded:
- Soft reset — undoes the commit but keeps changes staged
- Mixed reset — undoes the commit and unstages changes (default behavior)
- Hard reset — undoes the commit and discards changes entirely
Understanding this distinction is crucial for using Git effectively in a team environment and protecting your work.
Keep changes staged — git reset --soft HEAD~1
Use soft reset when you want to undo your last commit but keep the changes ready to recommit. This is perfect when you've committed too early, forgot to add a file, or made a mistake in the commit message.
Real-world scenario: You've just committed a feature branch with a typo in the commit message or realized you should have included another file. You want to fix it without losing your work.
git reset --soft HEAD~1After running this command, your changes remain in the staging area. You can verify the state with:
git statusYou'll see all the files from your undone commit listed under "Changes to be committed." Now you can either:
- Amend the commit message and recommit everything
- Unstage specific files to keep them separate
- Add additional files before recommitting
Example workflow:
git reset --soft HEAD~1
git status
# Now modify your index as needed
git add more-files.js
git commit -m "Corrected commit message with all files"This approach is especially useful in team settings because it preserves your intent and work while giving you a chance to get it right before pushing.
Keep changes unstaged — git reset HEAD~1
Mixed reset (the default) undoes your commit and unstages the changes, leaving them in your working directory. This gives you full control to selectively stage and commit parts of your work separately.
Real-world scenario: You committed multiple unrelated changes together—a bug fix and a refactoring—when they should have been separate commits. You want to split them into logical commits.
git reset HEAD~1Now all files from your undone commit appear as untracked or modified in your working directory:
git status
# Output shows files as "Changes not staged for commit"You can now stage and commit files selectively:
git add src/bugfix.js
git commit -m "Fix: resolve null pointer exception in user service"
git add src/refactor.js
git commit -m "Refactor: simplify authentication logic"This is the most flexible reset option and often the right choice when you need to restructure your commits. Mixed reset is also the default behavior if you use git reset HEAD~1 without specifying --soft or --hard.
Discard everything — git reset --hard HEAD~1
Hard reset completely removes your last commit and discards all changes. Use this only when you're absolutely certain you don't need the work from that commit.
Real-world scenario: You accidentally committed debugging code or sensitive credentials and want to wipe it from your local history immediately. This is your nuclear option.
git reset --hard HEAD~1After this command runs, your working directory and index match the commit before HEAD. The changes are gone from your local repository.
Warning: Hard reset is destructive. If you haven't pushed yet, you can potentially recover the commit using git reflog, but recovery isn't guaranteed. Once you push or garbage collection runs, the data becomes difficult or impossible to retrieve.
Example of checking the reflog if you realize you made a mistake:
git reflog
# Find the commit hash you want to restore
git reset --hard abc1234Use hard reset sparingly and only when you're certain. In production workflows, the team often prefers git revert for commits that have already been shared.
Already pushed? — Use git revert to create an undo commit
If you've already pushed your commit to the remote repository, reset commands become problematic. They rewrite history, and force-pushing can overwrite your teammates' work. Instead, use git revert to create a new commit that undoes the changes from your previous commit.
Real-world scenario: Your feature branch is merged to main, deployed to staging, and your colleagues have pulled the latest changes. Resetting would require force-pushing and cause chaos. You need a clean, linear history.
git revert HEADGit opens an editor with a pre-filled commit message like "Revert 'your commit message here'". You can accept it or customize it:
git revert --no-edit HEADThis creates a new commit that inverts the changes from your previous commit. Your repository history now shows both the original commit and the revert commit, preserving a complete audit trail.
Multi-commit example: If you need to revert multiple commits, you can revert a range:
git revert HEAD~2..HEADThis reverts the last three commits in order. The commits appear in reverse order in your history so that the net effect of applying them is correct.
git revert is the professional standard when dealing with pushed commits because it maintains a clear history and doesn't disrupt other developers' work. It's slightly more verbose than resetting, but the trade-off is safety and transparency.
Common mistakes
Force-pushing after a reset: Many developers reset a pushed commit and then force-push without considering their team. Force-push rewrites history and can destroy work that colleagues have based on the original commit. Reserve force-push for personal branches or when you've coordinated with the team.
Confusing reset modes: Using git reset --hard when you meant --soft has destroyed plenty of work. Double-check which mode you need, and consider using git diff or git status first to understand what you're about to change.
Resetting instead of reverting on shared branches: If your commit is already on a shared branch like main or develop, reset almost always causes problems. Use git revert instead. The only exception is if you catch the mistake immediately before anyone else pulls.
Not understanding HEAD~1 vs HEAD^: Both refer to the parent of the current commit, but they work slightly differently in some edge cases. Stick with HEAD~1 for clarity—it means "one commit back" unambiguously.
Losing work without checking reflog: If you've hard-reset and lost changes, check git reflog immediately. The reflog keeps a history of HEAD movements and can save you from data loss, but only if you act quickly.
Key takeaways
- Use
git reset --soft HEAD~1to undo a commit while keeping changes staged for recommitting or amending the message. - Use
git reset HEAD~1(mixed reset) to undo a commit and unstage changes, allowing you to split them into separate commits. - Use
git reset --hard HEAD~1only when you're certain you want to discard the commit and its changes entirely—it's destructive and irreversible without reflog recovery. - Use
git revertfor any commit that's already been pushed to a shared branch; it creates a new undo commit instead of rewriting history. - Always verify your intention with
git statusandgit diffbefore running reset commands on local commits. - Coordinate with your team before force-pushing after a reset; revert is almost always safer on shared branches.
- Check
git reflogimmediately if you've accidentally hard-reset and lost work—recovery is possible but not guaranteed.






Comments
Loading comments…