GitVibes / Undo Toolkit
Part 4

The "Undo" Toolkit: Reversing AI Mistakes

"The AI will break things. Your job isn't to prevent that — it's to recover instantly."

No matter how good your AI assistant is, it will occasionally hallucinate, delete the wrong file, or introduce a subtle bug. Git gives you a full spectrum of undo tools — from gentle nudges to nuclear resets. Knowing which tool to reach for in each situation is what separates a confident developer from a panicked one.

Choose the least destructive tool that solves the problem
Important
This is the most critical section. The AI will misunderstand a prompt, generate buggy code, or delete something important. Your value as an engineer is your ability to recover instantly and safely.

4.1 "Discard This Mess" (Local, Not Committed)

The AI just rewrote half your file and it's completely wrong. You haven't committed anything yet. This is the simplest undo — just throw it all away and go back to your last save point.

Discard local edits and return to the last committed state
Note
The Problem: The AI modified files and the result is completely wrong. You haven't staged or committed. You want to revert to your last save point.

The fix is simple -- one command wipes the slate clean and takes you back to your last commit:

Discard changes
git restore .              # Discard edits to tracked files
git restore src/bad_file.py  # Discard a single file

One blind spot: git restore only rewinds files Git already tracks. Brand-new files the AI scaffolded are untracked, so they survive it. Preview the leftovers with git clean -n (a dry run), then delete them with git clean -fd.

And one more trick for the road — restore doesn't have to stop at "the last commit." The most common real-world rescue is one file, from an older commit: the AI broke src/parser.py three commits ago and you only just noticed. No reset, no history surgery:

Pull one file back from the past
git restore --source=HEAD~3 src/parser.py
# The file's content is now as it was 3 commits ago;
# everything else stays untouched. Review it, then commit.

In VS Code, you don't need the terminal for this. In the Source Control panel, hover over the file you want to discard under "Changes" and click the curved arrow icon. To discard ALL changes at once, click the curved arrow next to the "Changes" header. You can also right-click any file and choose "Discard Changes".

Caution
This is a "dangerous" command: your local changes are gone forever. But in this case, that's exactly what you want.
Vibe it

"That last change broke everything — throw it all away and go back to my last commit"

"Discard all the changes you just made, they're not working"

4.2 "I Staged This by Accident" (Staged, Not Committed)

You hit git add . a bit too quickly and staged files you didn't mean to include. No worries — unstaging is completely harmless and doesn't touch your code.

Unstage files without losing your edits — a gentle undo
Note
The Problem: You used git add . and accidentally staged a file with a bad AI change. You need to "unstage" it.

No worries -- unstaging is a safe operation that simply moves a file back out of the staging area:

Unstage a file
git restore --staged src/bad_file.py
# Older equivalent: git reset HEAD src/bad_file.py

In VS Code, this is a one-click fix. In the Source Control panel, look under "Staged Changes", hover over the file you want to unstage, and click the (minus) button. The file moves right back to "Changes":

VS Code
Click the − button next to any staged file to unstage it. It moves back to the 'Changes' section.

Try It: Unstage Dangerous Files

You ran git add . too quickly and staged .env with credentials and a debug file. Unstage them with git restore --staged before committing — then remember the permanent fix from Part 2: add them to .gitignore so the next git add . can't stage them again.
Unstage Secrets & Debug Files

Loading playground...

Vibe it

"Unstage config.py, I don't want that in this commit"

"I accidentally staged everything — unstage all files except auth.py"

4.3 "I Forgot a File in My Last Commit"

You just committed — and immediately realized you forgot a file, or there's a typo in the message. Instead of creating a messy "oops" commit, you can quietly fix the last one.

Amend reopens your last commit — add the forgotten file, fix the message, seal it again
Note
The Problem: You just committed but missed a file, or there's a typo in your commit message. The commit has not been pushed yet.

Instead of creating a whole new commit, you can tack the missing file onto the one you just made:

Amend the last commit
git add src/forgotten_file.py   # Stage the missed file
git commit --amend --no-edit     # Add it to the last commit

# Or just fix the message:
git commit --amend

In VS Code, click the dropdown arrow next to the Commit button and select "Commit (Amend)". This adds your newly staged files to the last commit without needing the terminal. You can also find this under the ... menu → Commit → Commit Staged (Amend).

Warning
This rewrites your last commit. Amending is safe when nobody else could have based work on that commit — either it was never pushed, or it lives on your own personal branch and you follow up with a careful --force-with-lease push (see section 4.6). If teammates may have pulled the commit, don't amend — revert instead.
Vibe it

"I forgot to include the test file in my last commit — add it without creating a new commit"

"Fix my last commit message, it should say 'fix' not 'feat'"

4.4 "Nuke This Whole Feature" (Locally, Committed)

Sometimes the AI experiment was a dead end — three commits deep, and none of it is salvageable. If you haven't pushed yet, you can erase those commits entirely and start fresh.

Three reset modes from gentle (soft) to nuclear (hard)
Note
The Problem: Your last three commits were a single bad AI experiment. You have not pushed them. You want to permanently delete them.

This is where the nuclear option comes in. A hard reset rolls your branch back as if those commits never happened:

Hard reset: destroy commits and changes
git reset --hard HEAD~3   # Remove last 3 commits + all changes

"Remove," not quite "delete": the commits vanish from your branch, but Git keeps them around for ~30 days and the reflog (section 4.9) can bring them back. The truly unrecoverable loss is different — see the --hard card below.

The "Safer" Resets

--soft: Keep changes staged

Deletes commits but keeps changes in the Staging Area. Useful for "squashing" commits into one.

--mixed (default): Keep changes unstaged

Deletes commits but keeps changes in the Working Directory (unstaged).

--hard: Destroy everything

Removes commits AND all code changes. Your files reset to the older commit's state — including any uncommitted work sitting in your working tree, which is the one thing the reflog can never bring back. Run git status first; if it isn't clean, stash or commit before you reset.

Caution
CRITICAL: git reset rewrites history. Never use this on a branch your teammates have already pulled. This is for local cleanup only.
Vibe it

"The last 3 commits were all bad — nuke them but keep the code changes so I can redo it"

"Completely undo my last 2 commits, I want to start fresh from before them"

4.5 "I Pushed a Bug to the Team!" (Public, Pushed)

This is the "oh no" moment — you pushed a bad commit and your teammates already pulled it. You can't erase history, but you can create a new commit that perfectly reverses the damage.

Revert creates a new commit that undoes the damage — safe for shared branches
Caution
The Problem: You pushed a bad AI-generated commit. It's on main. Your teammates have already pulled it.

The WRONG Solution: You cannot use git reset. It rewrites history that others have, causing repository divergence.

The RIGHT Solution: Create a new commit that undoes the bad commit. This is a revert.

Here's how to create that revert commit in the terminal:

Safely undo a pushed commit
git log --oneline           # Find the hash: a1b2c3d
git revert --no-edit a1b2c3d # Create an inverse commit
git push                    # Push the revert

Without --no-edit, Git opens your editor so you can customize the revert message — fine once you expect it, startling the first time (especially if that editor turns out to be vim).

Notice that you're not erasing anything -- you're adding a new commit on top that reverses the damage:

Important
The bad commit stays in history, but a new "revert" commit undoes its changes. This is safe because no history is deleted. The history clearly shows: "Feature was added" → "Feature was reverted."

VS Code has this built in too. Open the ... menu in the Source Control panel -- this is your gateway to all advanced Git operations. From here you can access Commit, Changes, Pull, Push, Branch, Stash, and more:

VS Code
The ... menu is your Git command center. Look under Commit for Undo Last Commit, Commit (Amend), and other recovery options.

For reverting pushed commits, use the Source Control Graph: right-click any commit and select "Revert Commit" to create the inverse commit safely.

Vibe it

"I pushed a broken commit to main — safely undo it without rewriting history"

"Revert commit a1b2c3d, it introduced a bug in production"

4.6 The "Break Glass" Command

You rewrote local history with a reset or amend — and now Git refuses to push because local and remote have diverged. This is the emergency tool: a force push with a built-in safety net.

Force push with --force-with-lease protects teammates' work
Warning
The Problem: You used git reset or git commit --amend on a branch you already pushed. Local and remote history have diverged. Git refuses to let you push.

You have two flavors of force push, and picking the right one matters a lot:

git push --force

Replaces the server unconditionally. If a teammate pushed in the last 5 minutes, you blow their commits off the server (they survive only in that teammate's local clone).

git push --force-with-lease

Conditional force push. Only succeeds if the remote branch hasn't changed since your last fetch. Always use this instead.

One subtlety: the lease compares the remote against what you last fetched — so run git fetch first, review what changed on the remote, and only then push.

Caution
And one trap: anything that fetches in the background quietly renews the lease. VS Code's autofetch (git.autofetch), GitLens, and other IDE tooling can fetch every minute — after which --force-with-lease passes even though you never looked at what arrived. The lease proves the remote hasn't changed since the last fetch — it can't prove you reviewed it. Fetch, read, then push.
Caution
What does the error look like? When you try to push after rewriting history, Git will reject it with: ! [rejected] (non-fast-forward) — hint: Updates were rejected because the tip of your current branch is behind This is Git protecting you. But careful — the full hint goes on to suggest git pull, and after a deliberate amend or reset that's exactly wrong: pulling merges the old commit right back in, recreating the mess you just cleaned up. When you rewrote the history on purpose, the answer is the lease push, not a pull. Try it yourself below:

Try It: Reset and Force Push

Your feature branch has two bad commits already pushed. Use git reset --hard HEAD~2 to go back, watch a plain git push get rejected, then overwrite the remote with git push --force-with-lease. Never do this on shared branches!
Reset and Force Push

Loading playground...

Vibe it

"I amended a commit I already pushed and now I can't push — help me fix it safely"

"What's the safest way to force push after rewriting history on my branch?"

4.7 The Git "Undo" Recovery Matrix

The recovery matrix — match your mistake to the right undo tool

Before moving to advanced topics, here's a quick-reference matrix summarizing every undo technique and when to use it:

ScenarioCommandWhat It DoesSafe?VS Code
AI's change is bad, not committedgit restore .Discards edits to tracked files (new untracked files survive — see git clean)Safe (Local)Right-click file → "Discard Changes"
One file broke several commits agogit restore --source=HEAD~3 <file>Brings back that file's old content; history and other files untouchedSafe (Local)Timeline → click old commit → copy the old version
File staged by accidentgit restore --staged <file>Unstages a file, moving it from Staging back to ChangesSafe (Local)Right-click staged file → "Unstage Changes"
Typo in last commit messagegit commit --amendEdits the message of the most recent commitSafe (if not pushed yet)... → Commit → Commit (Amend)
Forgot a file in last commitgit add <file>
git commit --amend --no-edit
Adds new files to the most recent commitSafe (if not pushed yet)Stage files → ... → Commit Staged (Amend)
Last 3 local commits are badgit reset --hard HEAD~3Removes the last 3 commits and all their code changes (reflog can recover the commits)Local Only! (Rewrites history)... → Commit → Undo Last Commit (once per commit)
Commits "vanished" after a hard resetgit reflog
git reset --hard HEAD@{1}
Finds the lost commit in the reflog and moves the branch backSafe (Local)Terminal only (see section 4.9)
Pushed a bug to the teamgit revert <hash>Creates a new commit that is the inverse of the bad one100% Safe (Public)Source Control Graph → Right-click commit → "Revert Commit"
Reset a public branch, need to pushgit push --force-with-leaseForcefully overwrites remote, only if no one else pushedEnterprise "Break Glass"Git: Push (Force With Lease)
Stuck in "detached HEAD"git switch main
git switch -c keep-this
Reattaches HEAD to a branch — create one first if you made commits to keep (see section 4.8)Safe (Local)Click the branch name in the status bar

Try It: The Undo Toolkit

The playground includes git revert HEAD and git commit --amend. Try reverting the pushed bad commit, then amending after staging a fix.
Undo Operations

Loading playground...

4.8 Detached HEAD — Time Travel Safely

You (or the agent) checked out an old commit hash to see how the code looked before a refactor — and the terminal barks back: "You are in 'detached HEAD' state." It sounds like an injury. It isn't. It's Git telling you exactly where you are: in the past.

Detached HEAD: you're standing on a commit, not riding a branch
Note
The Problem: You ran git checkout a1b2c3d (or git checkout HEAD~2) to inspect an old version. Git warns about a "detached HEAD" and you're not sure if you broke something.

Normally, HEAD points at a branch, and the branch moves forward with every commit. When you check out a commit hash directly, HEAD points at that commit instead — no branch is along for the ride. Looking around is completely safe:

Time-travel to inspect an old commit
git log --oneline        # Find the commit you want to visit
git checkout HEAD~2      # Detach HEAD two commits back
cat src/app.py           # Look around — reading is 100% safe

The catch is committing while detached. Any commits you make there belong to no branch — the moment you switch away, nothing points at them anymore and they become orphaned. They don't disappear instantly (the reflog still remembers them — see section 4.9), but they vanish from git log and real Git will eventually garbage-collect them.

There are exactly two escape hatches, and choosing is easy:

The two ways out
# Made something worth keeping? Put a branch under your feet:
git switch -c inspect-v02

# Just sightseeing? Go back to the present:
git switch main
Tip
Think of detached HEAD as a read-only visit to the past. Inspect freely, run things, compare files. The instant you want to keep new work, run git switch -c <branch> — and even if you forget, the reflog is your safety net.

Try It: Time-Travel and Escape

Version 0.4 is misbehaving. Use git checkout HEAD~2 to visit version 0.2, inspect src/app.py, then escape — git switch -c inspect-v02 to keep a foothold, or git switch main to return to the present.
Try it: Time-travel and escape

Loading playground...

Vibe it

"Check out the commit from before yesterday's refactor so I can see how auth.py looked"

"I'm in detached HEAD state and I made a commit here — save it to a new branch and take me back to main"

4.9 The Reflog — Your Time Machine

The agent ran git reset --hard and the log now looks like two days of work never happened. Before you panic: those commits are not gone. Git keeps a private journal of every place HEAD has ever been — the reflog — and it's about to save your week.

The reflog records every HEAD movement — commits, resets, checkouts, merges
Note
The Problem: A hard reset (or a botched rebase, or an agent gone rogue) erased commits from git log. You need them back.

git log only shows commits reachable from your branches. The reflog shows everywhere HEAD has moved, reachable or not. Here's how to read it:

Anatomy of git reflog output
git reflog

a9f8e21 HEAD@{0}: reset: moving to HEAD~2         <- where you are NOW
4c7d3b9 HEAD@{1}: commit: feat: add caching layer <- one move ago (the "lost" work!)
8e2f1a0 HEAD@{2}: commit: feat: add ranking algorithm
1d0c5f4 HEAD@{3}: commit (initial): Initial commit

HEAD@{n} means "where HEAD was n moves ago" — HEAD@{0} is now, HEAD@{1} is one move back. Each line also shows the short hash, so you have two ways to name any point in time. Pick your recovery recipe:

Three recovery recipes
# 1. Undo the reset entirely — move the branch back:
git reset --hard HEAD@{1}

# 2. Rescue a single lost commit onto your current branch
#    (cherry-pick gets its own section in 5.4):
git cherry-pick 4c7d3b9

# 3. Inspect first — park a rescue branch on the lost commit:
git switch -c rescue 4c7d3b9
Warning
The reflog's limits: it is local-only — it's never pushed, and your teammates' machines each have their own. In real Git, entries expire (unreachable ones after about 30 days, the rest after about 90). And crucially: uncommitted work is NOT in the reflog — Git can only time-travel to states you committed. The real lesson of this whole part: commit early, commit often.
Tip
This completes the undo toolkit from the recovery matrix in section 4.7: restore for the working directory, revert for public history, reset for local history — and reflog when a reset itself was the mistake.

Try It: Rescue Lost Commits

An agent ran git reset --hard HEAD~2 and two commits vanished. Run git reflog to find where HEAD was before the reset, then git reset --hard HEAD@{1} to bring everything back.
Try it: Rescue lost commits

Loading playground...

Vibe it

"The last reset deleted commits I still need — check the reflog and restore them"

"Find the commit where tests still passed in the reflog and create a rescue branch on it"

The reflog answers "where did my commits go?" Its sibling question — "which commit broke this?" — has its own tool: git bisect binary-searches your history. Mark one commit as bad and one as good, and Git checks out midpoints for you to test — 1,000 commits take ten rounds, not a thousand. In the agent era this is the tool for "somewhere in the bot's forty commits, the tests started failing."

Try It: Find the Bad Commit

The tests fail on main but passed at v1.0, eight commits ago. Use git bisect plus the run-tests command to pin down the culprit in three rounds instead of eight.
Bisect: Binary-Search the History

Loading playground...

Vibe it

"The tests fail now but passed at v1.0 — bisect between them and tell me the first bad commit"

"Run git bisect to find which of your last 20 commits broke the login flow"

Challenge: Pick the Right Undo

Loading challenge...