Free lesson · Git Mastery
Undoing Changes Overview
Undoing Changes Overview
A note from Arun Selvam, Software Engineer at Apple iOS Frameworks.
The Question Every New Engineer Asks
The first time something went seriously wrong in my work at Apple - and by "seriously wrong" I mean an embarrassed walk to my mentor's desk to ask how to recover - was about six weeks into the job. I had been editing UIKit animation code, made a commit I wasn't happy with, then made three more commits trying to fix the direction, then realized the original commit had taken me down the wrong path entirely and the cleanest move was to throw away the last four commits and start over from where I'd been before.
I asked my mentor: how do I undo this? His answer was the most useful thing I learned that month: "It depends on what 'undo' means here. Tell me exactly what state you want to be in."
That conversation taught me that Git's undo operations are not one command. They are a family of commands, each of which addresses a specific shape of mistake. The trick is matching the command to what you're trying to do.
This topic is the map. Topics 5.2 through 5.8 dive into the individual commands. This one tells you which command to reach for in which situation.
The Five Situations You're Trying to Undo
Almost every undo question in Git falls into one of these five buckets:
flowchart TD
A[I want to undo something] --> B{What state is the<br/>thing currently in?}
B -->|Working directory<br/>changes not yet staged| C[git restore<br/>Topic 5.3]
B -->|Staged but not committed| D[git restore --staged<br/>Topic 5.3]
B -->|Last commit, not yet pushed| E[git commit --amend<br/>or git reset<br/>Topics 2.5, 5.4]
B -->|Older commit, not yet pushed| F[git reset or rebase<br/>Topics 5.4, 8.3]
B -->|Pushed commit on shared branch| G[git revert<br/>Topic 5.6]
This decision tree walks you through the five common undo situations. If the change is only in your working directory and not staged, git restore brings the file back to its committed state. If it's staged but not committed, restore with the dash dash staged flag unstages it. If it's in the most recent commit and not pushed, amend or reset can fix it. If it's in an older unpushed commit, reset or interactive rebase reshape history. And if the commit is already pushed and other people might have it, revert is the only safe option - it adds a new commit that undoes the old one rather than rewriting history.
The key question that determines which command you reach for: has the change been pushed to a shared branch yet? Before pushing, you have maximum flexibility - Git lets you rewrite local history freely. After pushing, the rules change because other people may have pulled what you pushed. Topic 8.4 covers when rewriting shared history is and isn't safe; the short rule for now is "default to revert for pushed commits, default to reset for unpushed."
The Vocabulary Distinction
A few terms that matter and are easy to confuse:
Discard - throw away changes that exist only in your working directory or staging area. Nothing about history changes. The recoverable-from-history file replaces your in-progress change. Tool: git restore (Topic 5.3) or git checkout (Topic 5.2) for older versions of Git.
Reset - move the current branch pointer to a different commit, optionally also updating the working directory and staging area. Useful for "go back to a previous state." Rewrites history if used on commits that have been pushed. Tool: git reset (Topic 5.4).
Revert - create a new commit that undoes the changes from a previous commit. The history is preserved; the undo is a forward-looking commit. Safe for pushed commits because no history is rewritten. Tool: git revert (Topic 5.6).
Amend - replace the most recent commit with a new one. Useful when you want to add a forgotten file or fix a commit message. The previous commit's identifier changes, which makes amend unsafe for pushed commits. Tool: git commit --amend (Topic 2.5).
Stash - save your current uncommitted work to a side area without committing it, so you can come back to a clean state. Useful when you need to switch context briefly. Tool: git stash (Topic 5.8).
Clean - delete untracked files from your working directory. Different from restore, which only operates on tracked files. Tool: git clean (Topic 5.7).
The five operations cover different needs. Knowing which is which is half the battle.
A Decision Walk-Through
A few common scenarios and the command for each:
"I edited a file and want it back the way it was."
The file is in your working directory; the change is unstaged. Use git restore <file> (Topic 5.3). The working directory version is replaced with the version from the most recent commit.
"I staged a file but haven't committed yet; I want to unstage it without losing my changes."
Use git restore --staged <file> (Topic 5.3). The staging area is cleared for that file; the working directory keeps your edits.
"I made my last commit but forgot to include a file."
Stage the missing file with git add, then git commit --amend (Topic 2.5). The previous commit is replaced with a new one that includes the missing file.
"I made a commit and want to rewrite the message."
git commit --amend opens the editor with the previous message ready to edit (Topic 2.5).
"I made three commits in the wrong order and want to redo them differently."
You're in interactive rebase territory (Topic 8.3) if the commits haven't been pushed. Reset to before the three commits (git reset --soft HEAD~3), then re-commit. Or use git rebase -i HEAD~3 for direct manipulation.
"I pushed a commit that broke production."
git revert <commit> (Topic 5.6). A new commit is created that undoes the changes from the broken one. Push the revert. Production is recovered without rewriting any history.
"I have local work in progress but need to switch to a different branch briefly."
git stash (Topic 5.8) saves your work; you switch branches, do the other thing, switch back, and git stash pop restores your work.
"I made several commits, none pushed, and want to abandon all of them and start over."
git reset --hard <known-good-commit> (Topic 5.4 / 5.5). The current branch's pointer moves back to the good commit; the working directory and staging area are reset to match. The intermediate commits are no longer reachable.
"I have untracked files I want to delete."
git clean -n first to see what would be deleted (Topic 5.7), then git clean -f to actually delete.
"I want to see what I might have lost."
git reflog shows every state your HEAD has been in recently, even ones not reachable from any branch (Topic 14.8). Useful for recovering work after a bad reset.
The Push Boundary
A pattern I now use almost automatically: the push boundary is what separates safe from unsafe undo operations.
Before pushing, your local history is yours. You can reset, rebase, amend, squash, reorder - anything goes. Mistakes are reversible because they only affect your machine.
After pushing, other people might have pulled. Rewriting that history means anyone who pulled the old version now has different history than the shared remote. They have to reconcile somehow; their reconciliation might lose work or duplicate work or simply be confusing.
The safe-after-push operation is git revert. It doesn't rewrite anything; it adds a new commit that undoes the old one. Everyone who pulled the old commit gets the revert as a normal forward-moving commit. Topic 8.4 covers the exceptions where rewriting shared history is acceptable; default to revert.
The Pattern That Saved Me at Apple
Going back to my embarrassed walk to my mentor: the actual answer to my "how do I undo four commits" question turned out to be git reset --soft HEAD~4. The --soft mode kept all my working files and the staging area intact - none of my code was lost - and only moved the branch pointer back. I then committed in one clean piece that captured what I'd actually been trying to do, instead of four flailing commits chasing the wrong direction.
The mentor's lesson: name the desired end state first, then pick the command. I'd been thinking about "how do I undo" instead of "what state do I want to be in." The reframing made the answer obvious.
I now use that same framing whenever I'm about to undo something. The five seconds spent asking myself "what state do I want to be in" prevents the wrong command nearly every time.
What Comes Next
Topic 5.2 covers git checkout for restoring files - the older form, still common in documentation. Topic 5.3 covers git restore, the modern replacement. Topic 5.4 covers git reset. Topic 5.5 dives into the reset modes (soft, mixed, hard) because they trip up new users routinely. Topic 5.6 covers git revert. Topic 5.7 covers git clean. Topic 5.8 covers git stash.
Take them as a sequence rather than picking randomly. They overlap in interesting ways and reading them in order builds the mental model for which to reach for in which situation.
What Goes Wrong
Warning: Common undo missteps before you read the specific topics:
Reaching for
git resetwhengit revertwas needed. Reset rewrites; revert appends. For pushed commits, default to revert.Using
--hardreset without considering the consequences. Working-directory changes are lost. Be sure before running it.Trying to undo with
git checkoutand confusing branches with files. The command does both; the syntax matters. Topic 5.2 covers the file form.Forgetting that
--cachedand--stagedare different flags for similar things in different commands.git rm --cached(Topic 3.1) andgit restore --staged(Topic 5.3) both deal with the staging area but mean opposite things.Believing untracked files are protected by
git reset. They aren't. Untracked files survive most undo operations butgit cleandeletes them deliberately.Not knowing the reflog exists. When something feels lost,
git reflog(Topic 14.8) is usually the answer.
Try It
Exercise: Practice the mental model:
- For each of the scenarios listed in the "Decision Walk-Through" section, write down which command you'd use without looking. Compare your answers against the topic.
- Identify a recent change you regret in any repository you have. Decide which command would undo it. Don't actually run it yet - just identify the right one.
- List the differences between reset, revert, and restore in your own words. Test your understanding by predicting what each would do to working directory, staging area, and history.
- Run
git reflogin a repository. Note the entries - every checkout, commit, merge, reset. Understand that this is your safety net.- Pick one of Topics 5.2 through 5.8 to read next based on a real undo scenario you've actually encountered. Read it; understand the command; apply it next time the situation comes up.