Table of Contents
Understanding git reset vs revert is one of the most important skills for any PHP developer. Every developer commits something they shouldn’t have – a debug statement left in production code, a commit to the wrong branch, a database password accidentally tracked. Git has multiple ways to undo things and picking the wrong one makes the situation worse.
Git has multiple ways to undo things – and picking the wrong one makes the situation worse. This guide covers every undo scenario you’ll actually hit on a PHP project, with the right command for each situation and clear explanations of what each one actually does.
For the official Git documentation on undoing changes see the Git undoing things documentation.
Understanding What You’re Undoing
Before reaching for an undo command, identify where the problem is:
Working directory - file changed but not staged yet
Staging area - file staged with git add but not committed
Local commit - committed but not pushed to remote
Remote commit - committed AND pushed to GitHub/GitLab
The right undo command depends on which stage the problem is at. Getting this wrong – especially on remote commits – can cause serious problems for other developers on the same repository.
Undoing Unstaged Changes
You edited a file, realized it’s wrong, and haven’t staged it yet. The simplest undo:
# Discard changes to a specific file
git restore src/Auth/LoginController.php
# Discard all unstaged changes in the project
git restore .
Output:
# No output - the file is restored silently
This is permanent. Restored files are gone – Git has no record of what you changed because it was never committed. Before running git restore . on a whole directory, run git diff to review what you’re about to discard.
The older equivalent you’ll see in tutorials:
# Older syntax - same result
git checkout -- src/Auth/LoginController.php
Both work. git restore is cleaner and less confusing because checkout does too many things.
Undoing Staged Changes
You ran git add but haven’t committed yet and want to unstage:
# Unstage a specific file (keep changes in working directory)
git restore --staged src/config/database.php
# Unstage everything
git restore --staged .
Output:
Unstaged changes after reset:
M src/config/database.php
This does not delete your changes. It just moves the file back to the working directory state – modified but no longer staged. Your edits are still there.
Undoing the Last Commit (Not Pushed)
You committed something and immediately realized the mistake. Three options depending on what you want to keep:
# Undo commit, keep changes STAGED (safest)
git reset --soft HEAD~1
# Undo commit, keep changes in working directory (most common)
git reset HEAD~1
# Undo commit, DELETE all changes (destructive - careful)
git reset --hard HEAD~1
The HEAD~1 means “one commit before where HEAD currently points.” For two commits back use HEAD~2 and so on.
When to use each:
--soft– committed too early, want to add more files before recommitting- default (–mixed) – committed wrong files, want to re-stage selectively
--hard– committed something that should not exist at all and you want it completely gone
# Example: committed debug code accidentally
git log --oneline -3
# Output:
# a1b2c3d Add var_dump debugging to payment controller ← this one
# 8c4d1e5 Add Stripe payment integration
# 2a9f3b1 Release v1.1.0
# Remove the commit, keep the changes unstaged
git reset HEAD~1
# Now fix the file properly
# Remove the var_dump statements
git add src/Payment/PaymentController.php
git commit -m "Add Stripe payment integration with proper error handling"
Amending the Last Commit
Minor mistake in the last commit – wrong message, forgot a file, typo in code. As long as you haven’t pushed, amend instead of reset:
# Fix the commit message only
git commit --amend -m "Add Stripe payment integration with webhook support"
# Add a forgotten file to the last commit
git add src/Payment/WebhookHandler.php
git commit --amend --no-edit # keeps the existing message
# Fix both file and message
git add src/Payment/WebhookHandler.php
git commit --amend -m "Add Stripe payment integration with webhook support"
Output:
[main 9d4e2f1] Add Stripe payment integration with webhook support
Date: Sat May 3 09:00:00 2026 +0530
2 files changed, 84 insertions(+)
Amend rewrites the last commit. Never amend a commit that’s already been pushed – it rewrites history and will conflict with everyone else’s copies.
Undoing a Pushed Commit Safely
The commit is already on the remote. Other developers may have pulled it. This is where git revert comes in – it creates a new commit that undoes the changes rather than rewriting history:
git log --oneline -3
# Output:
# a1b2c3d Deploy broken payment form validation ← this broke production
# 8c4d1e5 Add Stripe payment integration
# 2a9f3b1 Release v1.1.0
# Revert the bad commit
git revert a1b2c3d
Git opens your editor for a commit message. The default message is fine:
Revert "Deploy broken payment form validation"
This reverts commit a1b2c3d.
Save and close. Git creates a new commit:
[main b5c6d7e] Revert "Deploy broken payment form validation"
1 file changed, 12 deletions(-)
Now push the revert:
git push
The history now shows what happened clearly:
b5c6d7e Revert "Deploy broken payment form validation"
a1b2c3d Deploy broken payment form validation
8c4d1e5 Add Stripe payment integration
Anyone who pulls sees the problem and the fix. History is intact. No force push required.
Git Reset vs Revert: Which to Use and When
| Situation | Use | Why |
|---|---|---|
| Commit not pushed yet | git reset | Rewrites local history safely |
| Commit already pushed | git revert | Creates new commit, no force push |
| Solo project, need to clean history | git reset –hard | Only if you’re certain about the loss |
| Team project, bad commit pushed | git revert | Never reset what others have pulled |
Stashing – Temporarily Saving Uncommitted Work
You’re halfway through a feature when an urgent bug fix comes in. You can’t commit half-finished code and you can’t lose the work. Stash it:
# Stash current work
git stash
# Check what's stashed
git stash list
# Switch to fix the bug
git checkout main
git checkout -b hotfix/broken-login-redirect
# Fix the bug, commit, merge...
# Then come back to your feature
git checkout feature/email-notifications
# Apply the stash
git stash pop
Output of git stash:
Saved working directory and index state WIP on feature/email-notifications:
3f7a9b2 Add EmailNotifier base class
Output of git stash list:
stash@{0}: WIP on feature/email-notifications: 3f7a9b2 Add EmailNotifier base class
Output of git stash pop:
On branch feature/email-notifications
Changes not staged for commit:
modified: src/Notifications/EmailNotifier.php
Dropped stash@{0}
Your work is back exactly where you left it. stash pop applies the most recent stash and removes it from the stash list. stash apply applies it but keeps it in the list – useful when you want to apply the same stash to multiple branches.
Named Stashes
# Save with a descriptive name
git stash save "half-finished email template logic"
# List all stashes
git stash list
# Output:
# stash@{0}: On feature/email-notifications: half-finished email template logic
# stash@{1}: WIP on feature/payment: add webhook handler
# Apply a specific stash by index
git stash apply stash@{1}
# Drop a specific stash
git stash drop stash@{1}
# Clear all stashes
git stash clear
Recovering a Deleted Branch
You deleted a branch and realized it wasn’t fully merged. Git keeps a log of where HEAD has been – called the reflog – and you can recover from it:
# Find the commit hash of the deleted branch
git reflog
# Output:
# a1b2c3d HEAD@{0}: checkout: moving from feature/payment to main
# 8c4d1e5 HEAD@{1}: commit: Add Stripe webhook handler ← last commit on deleted branch
# ...
# Recreate the branch from that commit
git checkout -b feature/payment 8c4d1e5
The reflog keeps entries for 30 days by default. If the branch was deleted recently the commits are still there – just not attached to any branch.
Fixing a Commit on the Wrong Branch
You committed to main when you meant to commit to your feature branch. Common mistake, easy to fix:
git log --oneline -3
# Output:
# a1b2c3d Add experimental caching layer ← should be on feature/caching, not main
# 8c4d1e5 Release v1.1.0
# 2a9f3b1 Update README
# Step 1: Note the commit hash - a1b2c3d
# Step 2: Create the correct branch from current state
git checkout -b feature/caching
# Step 3: Go back to main
git checkout main
# Step 4: Remove the commit from main (it's safe, not pushed)
git reset --hard HEAD~1
# Now main is back at Release v1.1.0
# And feature/caching has the correct commit
Removing a Committed File From Tracking
You accidentally committed a file that should be ignored – a config/local.php, a .env file, a log file:
# Remove from Git tracking without deleting the file
git rm --cached config/local.php
# Add to .gitignore so it doesn't get committed again
echo "config/local.php" >> .gitignore
# Commit both changes
git add .gitignore
git commit -m "Remove config/local.php from tracking - add to gitignore"
git push
If the file contained credentials – database passwords, API keys, anything sensitive – rotating those credentials is mandatory even after removing the file. The file still exists in the commit history. Anyone with access to the repository history can see it.
Frequently Asked Questions
I ran git reset –hard and lost my work. Can I recover it?
Possibly. Git keeps loose objects for at least 30 days before garbage collecting them. Run git reflog to find the commit hash of the work you lost. If it appears in the reflog you can recover it:
git reflog
# Find the hash of the commit with your lost work
git checkout -b recovery-branch <hash>
This only works if you had committed the work before running reset. If the work was never committed – only in the working directory or staging area – it cannot be recovered after git reset --hard.
What is the difference between git reset –soft, –mixed and –hard?
All three move the HEAD pointer backward. The difference is what happens to your changes: --soft keeps changes staged, --mixed (the default) keeps changes in the working directory but unstaged, --hard deletes all changes completely. Think of it as how far back you want to roll the clock – just the commit, the commit and staging, or everything.
Can I undo a git revert?
Yes – revert the revert. Since git revert creates a new commit, you can revert that commit the same way:
git revert b5c6d7e # reverts the revert commit
This re-applies the original changes. More straightforwardly you can just reapply the work manually and commit it.
I pushed sensitive data to GitHub. What do I do?
Three things immediately. First, rotate every credential that was exposed – change the database password, regenerate the API key, invalidate the session secret. Treat them as compromised regardless of how quickly you act. Second, use git revert to remove the sensitive data from the latest commit and push. Third, if the repository is public, contact GitHub support to purge the cached version. Removing the file from history with git filter-branch or the BFG Repo Cleaner is possible but complex and disruptive to anyone else on the repository.
Summary
The right undo command depends on where the problem is and whether the commit has been pushed:
- Unstaged changes –
git restore file.php - Staged changes –
git restore --staged file.php - Last commit not pushed –
git reset HEAD~1orgit commit --amend - Commit already pushed –
git revert <hash>– never reset what others have pulled - Work in progress interrupted –
git stashandgit stash pop - Deleted branch –
git reflogto find and recover it
For branching strategies that prevent most of these mistakes in the first place, the Git branching for PHP projects guide covers feature branches, hotfixes, and merge patterns in detail.
