Git offers several ways to undo work, and beginners often mix them up because the commands affect different places:
The good news is that each command has a clear purpose once you separate them.
Tip: Before undoing anything, run
git statusand ask yourself: am I trying to undo a file change, unstage something, move a branch pointer, or create a reverse commit?
git restore: Undo File-Level ChangesUse git restore when you want to discard changes in files or unstage them.
git restore app.jsThis replaces the working copy with the version from the index or the latest commit, depending on context.
git restore --staged app.jsThat removes the file from the staging area but keeps your actual file edits.
echo "debug=true" >> config.env
git add config.env
git restore --staged config.envNow the change still exists in the file, but it is no longer staged for commit.
git reset: Move HEAD and Possibly Moregit reset changes where the current branch points. Depending on the mode, it may also affect the staging area and working directory.
git reset --soft HEAD~1This moves the branch back one commit but keeps the changes staged.
Use it when the last commit message was wrong or you want to recommit differently.
git reset --mixed HEAD~1This is the default reset mode. It moves the branch back and unstages the changes, but keeps them in your working directory.
Use it when you want to redo the commit more carefully.
git reset --hard HEAD~1This moves the branch back and discards staged and working directory changes to match the target commit.
Note: Hard reset is destructive. Use it only when you are sure you no longer need the discarded work.
git revert: Safely Undo a Commit in Shared Historygit revert does not remove a commit. Instead, it creates a new commit that reverses the changes from an earlier commit.
git revert a1b2c3dThis is often the safest choice for undoing something that has already been pushed and shared with others.
Because it adds a new commit instead of rewriting history, teammates do not have to deal with force pushes or changed commit IDs.
A common production fix looks like this:
git log --oneline
git revert 8d92a11
git push origin maingit clean: Remove Untracked Filesgit clean deals with files Git is not tracking.
Preview what would be removed:
git clean -nDelete untracked files:
git clean -fDelete untracked files and directories:
git clean -fdThis is useful when build artifacts or temporary generated files clutter your working directory.
Tip: Always start with
git clean -nso you can see what Git plans to remove.
Here is the simplest way to separate them:
| Command | Main purpose |
|---|---|
git restore | Discard file changes or unstage files |
git reset | Move branch history and optionally unstage or discard changes |
git revert | Create a new commit that undoes an earlier commit |
git clean | Remove untracked files and folders |
git restore --staged secrets.txtgit reset --mixed HEAD~1maingit revert <commit-id>git clean -n
git clean -fdBefore using destructive options like reset --hard or clean -f, inspect the current state:
git status
git log --oneline -5If you are unsure, make a backup branch first:
git branch backup-before-resetThat small step can save a lot of stress.
Undoing changes in Git is easier when you match the tool to the problem. Use restore for file-level undo, reset for local history changes, revert for safely undoing shared commits, and clean for removing untracked files. The biggest caution is with destructive commands like reset --hard and clean -f.
When in doubt, prefer the safer option and inspect first.
Scenario: You accidentally staged config.env, but you still want to keep the actual file change in your working directory.
Question: Which command from the tutorial does exactly that?
<MCQExercise
exercise={{
id: 'git-undoing-changes-q1',
title: 'Exercise 1: Unstaging without losing edits',
description: 'Choose the command that removes a file from the staging area while preserving its edits.',
options: [
{ id: 'a', text: 'git clean -fd config.env' },
{ id: 'b', text: 'git reset --hard HEAD~1' },
{ id: 'c', text: 'git restore --staged config.env' },
{ id: 'd', text: 'git revert config.env' },
],
correctId: 'c',
explanation: 'The lesson used git restore --staged for exactly this staging-area problem. It removes the file from the index while leaving your working copy untouched, so you can keep editing or stage it later.',
wrongExplanations: {
a: 'clean removes untracked files, which is unrelated and potentially destructive here.',
b: 'A hard reset changes history and can discard work, which is much broader than simply unstaging one file.',
d: 'revert creates a new commit that undoes an earlier commit. It does not operate on the staging area this way.',
},
}}
/>
Scenario: Your most recent commit should be redone more carefully, but you want the changes to remain in your working directory and no longer be staged.
Question: Which reset mode from the tutorial fits that goal?
<MCQExercise
exercise={{
id: 'git-undoing-changes-q2',
title: 'Exercise 2: Redoing the last local commit',
description: 'Identify the reset mode that moves HEAD back and unstages changes while keeping the file edits.',
options: [
{ id: 'a', text: 'git reset --soft HEAD1' },
{ id: 'b', text: 'git reset --mixed HEAD1' },
{ id: 'c', text: 'git reset --hard HEAD~1' },
{ id: 'd', text: 'git clean -n' },
],
correctId: 'b',
explanation: 'The tutorial described mixed reset as moving the branch pointer back while leaving the changes in your working directory and removing them from the staging area. That makes it ideal for reworking the commit more carefully.',
wrongExplanations: {
a: 'soft reset keeps the changes staged, which is not what this scenario asked for.',
c: 'hard reset would discard staged and working directory changes to match the target commit.',
d: 'clean previews untracked-file deletion and does not move HEAD at all.',
},
}}
/>
Scenario: A bad commit is already on the shared main branch, and teammates may have pulled it.
Question: Which command is usually the safest choice according to the tutorial?
<MCQExercise
exercise={{
id: 'git-undoing-changes-q3',
title: 'Exercise 3: Undoing shared history safely',
description: 'Pick the safest command for undoing a pushed commit on a shared branch.',
options: [
{ id: 'a', text: 'git reset --hard' },
{ id: 'b', text: 'git clean -fd' },
{ id: 'c', text: 'git restore app.js' },
{ id: 'd', text: 'git revert <commit-id>' },
],
correctId: 'd',
explanation: 'The lesson emphasized git revert for shared history because it adds a new commit that reverses the old one. That preserves history instead of rewriting it, which is much safer when others may already depend on the existing commit chain.',
wrongExplanations: {
a: 'hard reset rewrites local branch history and is risky for commits that were already shared.',
b: 'clean only removes untracked files, not pushed commits.',
c: 'restore discards working-tree file changes, not already-published commit history.',
},
}}
/>
git reset --soft HEAD~1 moves the branch back one commit and leaves everything staged, ready to recommit, which is what you want when you simply mis-worded the message or forgot a file. git reset HEAD~1 (mixed, the default) does the same but unstages the changes, leaving them in your working directory. Use git commit --amend instead if you only want to fix the message or add a file to the commit that is already there, since it avoids the reset entirely.
All three move HEAD; they differ in how much they touch. --soft moves the pointer only, leaving your staging area and files exactly as they were, so the undone changes are staged. --mixed, the default, also resets the staging area, so the changes sit unstaged in your working directory. --hard resets the working directory too, discarding the changes completely. Only --hard destroys work, which is why it is the one to pause over.
Committed work, yes. git reflog lists every position HEAD has held, including the commit you reset away from, and git reset --hard HEAD@{1} puts you back. Reflog entries persist for about 90 days by default, so recovery is realistic long after the mistake. Work that was never committed is genuinely gone, --hard overwrites the working directory and there is no record of it. That asymmetry is the strongest argument for committing early and often, even on messy work-in-progress.
git restore <file> discards unstaged changes in that file, and git restore --staged <file> unstages it while keeping the edits. Those two replace the overloaded git checkout -- <file> and git reset HEAD <file> forms, which did the same jobs less clearly. To do both, unstage and discard, use git restore --staged --worktree <file>. Discarding is not recoverable, since the changes were never recorded anywhere, so check git diff before running it.
git clean -fd deletes untracked files and directories, with -f required because Git refuses to do something this destructive without it, and -d extending it to directories. Always run git clean -nd first: -n is a dry run that lists exactly what would be removed. By default .gitignored files are left alone, and -x includes them. Useful for wiping build output and node_modules, and a fast way to delete a local .env you meant to keep.
Mark releases with Git tags: lightweight versus annotated, why push ignores tags, deleting them safely, tagging older commits and semantic-versioned releases.
Follow the GitHub pull request workflow end to end: fork versus clone, keeping a branch current, squash versus merge versus rebase, and reviewing well.
Learn advanced Git: cherry-pick a commit, find a regression with git bisect, recover lost work with reflog and trace changes with blame, in learning order.