How to Undo Almost Anything in Git (Including an AI Agent's Mess)
An agent committed to the wrong branch, rewrote files you needed, or ran a reset it shouldn't have. Git can almost always get your work back. Which undo command fits which situation — restore, revert, reset, and the reflog that rescues 'deleted' commits — explained with the exact commands.
Working with AI coding agents means more commits, more branches, and more moments of "wait, what just happened to my files?" The good news: git almost never truly loses committed work. The bad news: git has several different undo commands, and using the wrong one can make things worse.
This is a guide to picking the right one. For each situation, the command, what it does, and how dangerous it is.
Before any of this: stop and look. Run these first. They change nothing and tell you where you are:
git status # what's changed, what's staged, which branch
git log --oneline -10 # recent commits on this branch
git diff # exactly what changed in your files
And if you're about to try something you're unsure of, make a safety copy of where you are:
git branch backup-before-undo
A branch is just a bookmark. It costs nothing and makes almost everything below recoverable.
"I changed a file and want it back the way it was"
Uncommitted changes to one file:
git restore path/to/file.ts
This throws away your uncommitted changes to that file. They're gone — git never saw them, so git can't bring them back. To discard everything uncommitted: git restore .
If you might want the changes later, stash them instead:
git stash # put all uncommitted changes aside
git stash pop # bring them back
"I staged something I didn't mean to"
git restore --staged path/to/file.ts
Unstages the file. Your changes stay in the file; they're just no longer lined up for the next commit. Completely safe.
"I want a file back from an earlier commit"
git restore --source=HEAD~3 path/to/file.ts
Puts the file back exactly as it was three commits ago (or use a commit ID instead of HEAD~3). Useful when an agent rewrote a file you wanted to keep as it was. Only that file changes.
"The last commit message is wrong, or I forgot a file"
If you haven't pushed yet:
git add forgotten-file.ts
git commit --amend
This replaces the last commit with a corrected one. Don't amend a commit you've already pushed to a shared branch — it rewrites history other people may have.
"I need to undo a commit that's already pushed"
git revert <commit-id>
revert creates a new commit that does the opposite of the old one. History is preserved; nothing is rewritten; it's safe on shared branches. This is the right way to undo something on main.
"I want to throw away my last few commits"
reset moves your branch back to an earlier commit. It comes in three strengths:
git reset --soft HEAD~2 # undo 2 commits, keep their changes staged
git reset --mixed HEAD~2 # undo 2 commits, keep their changes unstaged (the default)
git reset --hard HEAD~2 # undo 2 commits AND discard their changes
--soft and --mixed are gentle: the commits go away, the work stays in your files, and you can recommit it differently. That's a great way to squash an agent's twelve tiny commits into one.
--hard also discards the work in your files. Uncommitted changes are lost for good. The commits themselves can still be recovered — see the reflog below — but make a backup branch first anyway.
Only reset commits that haven't been pushed to a shared branch.
"I committed to the wrong branch"
Say an agent committed to main when it should have been on a feature branch, and you haven't pushed:
git branch feature/new-thing # bookmark the commits on a new branch
git reset --hard origin/main # move main back to match the remote
git switch feature/new-thing # carry on where the work is
The commits now live on the feature branch, and main is clean.
"I need one commit from another branch"
git cherry-pick <commit-id>
Copies that single commit onto your current branch. Useful when one agent's fix belongs on a different branch than the rest of its work.
"I deleted a branch" or "reset --hard took my commits"
This is the one people think is hopeless. It usually isn't. Git keeps a private log of everywhere your branch pointer has been: the reflog.
git reflog
You'll see a list like:
a1b2c3d HEAD@{0}: reset: moving to HEAD~3
e4f5g6h HEAD@{1}: commit: add team billing
i7j8k9l HEAD@{2}: commit: add team model
Find the entry just before the mistake — here, e4f5g6h, before the reset — and put a branch on it:
git branch rescued e4f5g6h
Your "lost" commits are back. The reflog is local to your machine, and by default keeps entries for commits no branch points to for 30 days — so act reasonably soon, but you have time.
"I merged and wish I hadn't"
Not pushed yet: git reset --hard ORIG_HEAD (git sets ORIG_HEAD before a merge). Already pushed: revert the merge with git revert -m 1 <merge-commit-id>, which undoes it with a new commit.
What git can't undo
- Uncommitted changes you discarded with
restoreorreset --hard. Git never had them. - Files that were never committed at all, including ones listed in
.gitignore— like.envor a local database file. - Anything outside the repository: database changes, installed packages, deployed releases, emails sent.
- A secret you pushed. Removing it from history doesn't un-leak it. Rotate the secret. (How to Keep API Keys Out of an AI-Built App.)
Which is why the best undo strategy is committing often. A commit is a save point. The more of them there are, the smaller any mistake is.
Rules for working with agents
- Commit before handing an agent a big task, so there's a clean point to come back to.
- Have agents work on branches, never directly on
main. - Don't let agents force-push or rewrite shared history. Deny
git push --forceandgit reset --hardin their permission rules, or require approval. (Running Claude Code Unattended shows how.) - Push regularly. A pushed branch survives the loss of your machine.
- Know your agent's own undo, and its limits. Claude Code's
/rewindrestores edits made with its file tools, but not changes made by shell commands — git covers those. See Claude Code Checkpoints.
Quick reference
| Situation | Command | Safe? |
|---|---|---|
| Discard changes to a file | git restore <file> |
Loses uncommitted changes |
| Unstage a file | git restore --staged <file> |
Yes |
| Get a file from an old commit | git restore --source=<commit> <file> |
Yes |
| Fix last commit (unpushed) | git commit --amend |
Yes, if unpushed |
| Undo a pushed commit | git revert <commit> |
Yes |
| Undo commits, keep the work | git reset --soft HEAD~N |
Yes, if unpushed |
| Undo commits and the work | git reset --hard HEAD~N |
Loses uncommitted changes |
| Recover "lost" commits | git reflog then git branch <name> <id> |
Yes |
EasySpawn keeps GitHub as the canonical home of your code: Claude Code works on branches in a persistent workspace, commits and pushes, and opens pull requests — so every change has a save point. See how it works or join the waitlist.
Related: Running Claude Code Agents in Parallel With Git Worktrees · How to Review a Pull Request Written by an AI Agent · Git and GitHub for Beginners · Merge Conflicts Explained
Keep reading
Getting AI to Write Tests That Actually Catch Bugs
Ask an AI for tests and you'll get plenty: tests that mock everything, assert nothing useful, and pass no matter what the code does. How to get tests that fail when behaviour breaks — what to test, how to prompt, how to check a test is real, and how tests become the agent's safety net.
How to Write Good Commit Messages (and Why It Matters With AI)
'fix', 'update', and 'wip' tell you nothing six months later. What a commit message is for, the simple format most teams use, examples of good and bad messages, how often to commit when an AI tool is making the changes, and how to get Claude Code to write useful ones.