Pick your situation
| Your situation | Command |
|---|---|
| Fix the message of the last commit | git commit --amend |
| Undo last commit, keep changes staged | git reset --soft HEAD~1 |
| Undo last commit, keep changes unstaged | git reset HEAD~1 |
| Undo last commit, discard changes | git reset --hard HEAD~1 |
| Undo a commit that is already pushed | git revert <sha> |
| Undo an old commit, keep the ones after it | git revert <sha> |
| Remove an old commit from history entirely | git rebase -i <sha>~1 |
| Undo a merge | git revert -m 1 <merge-sha> |
| Recover something you destroyed | git reflog then git reset --hard <sha> |
Decide whether the history is shared
Has the commit been pushed to a shared branch?
- No:
reset,amendandrebasecan rewrite the local branch without disrupting another clone. - Yes: prefer
revert. Rewriting published history makes collaborators reconcile their local branches and can discard work during a careless force-push.
After that, choose whether the original file changes should stay staged, remain unstaged or be discarded.
Fixing the last commit
Wrong message, or you forgot a file
# Edit the message
git commit --amend -m "fix: correct tax rounding on invoices"
# Add a missing file to the previous commit
git add src/tax.ts
git commit --amend --no-edit
--amend creates a replacement for the previous commit, so its SHA changes. Use it freely before pushing. After publishing the original commit, updating the remote requires a force-push and coordination with anyone using that branch.
Undo the commit but keep the work
# Keep the changes staged
git reset --soft HEAD~1
# Keep the changes unstaged
git reset HEAD~1 # Mixed mode is the default
# Destroy every change from the commit
git reset --hard HEAD~1
The three modes differ only in what happens to your files:
| Mode | Moves branch pointer | Staging area | Working directory |
|---|---|---|---|
--soft | Yes | Keeps your changes | Untouched |
--mixed (default) | Yes | Cleared | Keeps your changes |
--hard | Yes | Cleared | Overwritten; work lost |
Undoing a commit that is already pushed
git revert creates a new commit containing the inverse of an earlier commit. Existing history remains in place, so collaborators can receive the change with an ordinary pull.
# Revert one commit
git revert a1b2c3d
# Revert several commits, newest first
git revert a1b2c3d b4e5f6a
# Stage the inverse for review
git revert --no-commit a1b2c3d
The new commit appears in the log as "Revert ...". That extra entry records both what happened and how it was corrected. On a shared branch, this trace is preferable to rewriting commits that others may already have based work on.
Reverting a merge
A merge commit has two parents, so Git needs to know which one to treat as the mainline:
# Mainline 1 keeps the first parent, usually the target branch
git revert -m 1 <merge-sha>
Removing a commit from the middle of history
Interactive rebase rewrites every affected commit from the selected point forward. Keep it to unpushed work or a personal branch that no one else is using.
# Rewrite the five most recent commits
git rebase -i HEAD~5
An editor opens with one line per commit. Change the verb at the start of a line to change what happens:
pick a1b2c3d feat: add invoice export
drop b4e5f6a wip: debugging noise ← delete this commit
squash c7d8e9f fix typo ← fold into the one above
reword d0e1f2a feat: tax rounding ← change the message only
edit e3f4a5b refactor: extract helper ← stop here to amend it
Save and close the editor to begin. If a conflict appears, resolve it, stage the resolved files with git add, then run git rebase --continue. Use git rebase --abort to return to the pre-rebase state.
Undoing things that are not commits
# Unstage a file while keeping its edits
git restore --staged src/app.ts
# Permanently discard edits to one file
git restore src/app.ts
# Permanently discard every uncommitted edit
git restore .
# Preview, then remove untracked files and directories
git clean -nd
git clean -fd
# Stash work for later
git stash push -m "half-finished refactor"
git stash pop
Git 2.23 introduced git restore and git switch to separate file restoration from branch changes. git checkout -- file still works, but restore states the file operation more clearly.
Recovering after the wrong command
The reflog is a local record of recent HEAD positions, including commits that no branch currently names. Use it to locate the state before a mistaken reset, rebase or branch deletion.
git reflog
# a1b2c3d HEAD@{0}: mistaken reset to HEAD~3
# f9e8d7c HEAD@{1}: desired reporting commit
# 8b7a6c5 HEAD@{2}: earlier rounding fix
# Return to the state before the reset
git reset --hard HEAD@{1}
# Recover a deleted branch
git reflog
git branch recovered-feature f9e8d7c
# Find commits without branch references
git fsck --lost-found
Reflog entries survive for 90 days by default (30 for unreachable commits) before garbage collection. The practical limit on recovery is therefore time, not permanence.
Force-pushing without hurting anyone
If you intentionally rewrote a branch that already exists on the remote, update it with the guarded force option:
# Stop if the remote changed since your last fetch
git push --force-with-lease
# Unsafe on a shared branch
git push --force
--force-with-lease verifies that the remote branch still points where your clone expects. If another push changed it, Git rejects your update instead of overwriting those commits. Plain --force omits that protection.
Frequently asked questions
What is the difference between git reset and git revert?
reset moves a branch pointer and can remove commits from that branch’s visible history. Use it for local, unshared work. revert adds a new commit that reverses an earlier one, so it is the usual choice after the original commit has been shared.
How do I undo the last commit but keep my changes?
git reset --soft HEAD~1 keeps everything staged and ready to re-commit. git reset HEAD~1 keeps the changes but unstages them. Neither touches your files on disk.
Can I undo a git reset --hard?
Yes, if the work was committed. Run git reflog, find the SHA from before the reset, and git reset --hard back to it. Uncommitted changes destroyed by --hard are not recoverable, because they were never stored in Git.
How do I remove a secret I accidentally committed?
Rewriting history with git filter-repo or BFG Repo-Cleaner removes it from the repository, but treat the credential as compromised regardless and rotate it immediately. It has been on a remote server, in CI logs and in every clone. Rotation is the fix; history rewriting is cleanup.
Why did my reverted changes not come back after re-merging?
Because reverting a merge commit tells Git those changes are already accounted for. Re-merging the same branch brings nothing new. Revert the revert commit first, then merge again.
Is git rebase -i safe?
It is appropriate for commits you have not pushed, and git rebase --abort can stop an in-progress rebase. Rebasing shared commits requires everyone who pulled them to reconcile a rewritten branch. Interactive rebase also needs an editor, so it is not suitable for many automated environments.