Scenario
Section titled “Scenario”Three commands, all of which “undo” something, all of which people confuse. The confusion is
expensive in one specific direction: reset --hard is the only one that can destroy work Git
never saw, and it is the one people reach for first because the name sounds decisive.
This lab puts you in five situations and asks you to pick. Each one is a thing that happens weekly on a real team.
Objective
Section titled “Objective”Choose correctly between restore, restore --staged, reset --soft and revert; see why
reverting a commit in the middle of history can conflict; and see exactly what reset --hard
throws away.
Prerequisites
Section titled “Prerequisites”- Git installed
- The working tree and the index — the three places a change can live are the whole subject here
Starting state
Section titled “Starting state”mkdir -p /tmp/lab-rrr && cd /tmp/lab-rrrgit init -q -b main .git config user.email "lab@example.com"git config user.name "Lab User"
printf 'line1\nline2\n' > config.txtgit add . && git commit -q -m "Initial config"printf 'line1\nline2\nline3\n' > config.txtgit add . && git commit -q -m "Add line3"printf 'line1\nline2\nline3\nline4\n' > config.txtgit add . && git commit -q -m "Add line4"
git log --oneline189b403 Add line4685027c Add line35b94860 Initial configFor each situation, decide which command applies before running it.
-
Situation A — you edited a file and want the edit gone. Nothing staged, nothing committed.
Terminal window echo "oops" >> config.txtgit status --shortWhich command discards the edit and leaves everything else alone?
-
Situation B — you staged a file by mistake. The edit should stay; it just should not be in the next commit.
Terminal window echo "staged" >> config.txtgit add config.txtgit status --shortWhich command unstages without touching the working tree? (Then discard the edit as in A.)
-
Situation C — you committed too early. The last commit should not exist, but its changes should stay staged so you can recommit properly.
Terminal window git reset --soft HEAD~1git status --shortgit log --onelineThen recommit:
git commit -q -m "Add line4". -
Situation D — a commit is already pushed and must be undone. Rewriting is not an option. Undo the tip commit:
Terminal window git revert HEAD --no-editgit log --onelinecat config.txt -
Situation E — undo a commit in the middle of history. Reset the tip revert first so
line4is back, then revertAdd line3:Terminal window git reset --hard HEAD~1 # remove the revert from D; line4 returnsgit revert HEAD~1 --no-editIt does not go cleanly. Read the output, then
cat config.txt. Resolve it so the file containsline1,line2,line4, and continue the revert. -
The destructive one. Make an uncommitted edit, then:
Terminal window echo "unsaved" >> config.txtgit status --shortgit reset --hard HEADgit status --shortgit reflog | head -3Where did the edit go?
Practice Mode (in your browser)
Educational Git simulator. Supports the commands needed for this exercise; it is not a complete Git installation. Nothing runs on your machine and nothing is sent anywhere — progress is kept in this browser only.
3 situations: Discard an unstaged edit · Unstage a file without losing the edit · Undo a commit that is already shared. Estimated 10 minutes. Hints, a worked solution and a restart are available; success is checked against the repository state, so any correct route counts.
Open Practice Mode
Loading the simulator…
Keep building your Git skills. Get one practical technique, one workflow, and one security lesson each week. Modern Git Weekly is not taking subscriptions yet — read thesample issue, or follow new lessons by RSS.
Validation
Section titled “Validation”After step 5, git log --oneline shows a Revert "Add line3" commit on top of the three
originals and config.txt has three lines. After step 6, git status is clean and the word
unsaved appears nowhere — not in the file, not in any commit, not in the reflog.
A. The edit is only in the working tree. git restore <file> overwrites the working tree from
the index.
B. git restore --staged <file> copies the index entry back from HEAD. The working tree is
untouched, so the edit survives.
C. --soft moves the branch pointer and leaves both the index and working tree as they were.
--mixed (the default) would also unstage; --hard would also discard.
D. revert creates a new commit whose changes are the inverse. History only grows.
E. line4 was added on the line after line3. Removing line3 while line4 stays touches
adjacent lines, and Git cannot tell whether line4 depended on line3. That is a conflict, and it
is a judgement call — which is why Git stops.
6. It went nowhere. The reflog records commits; this edit was never committed.
Solution
Section titled “Solution”A
git restore config.txtB
git restore --staged config.txt # status: " M config.txt" — unstaged, edit keptgit restore config.txt # then discard itC
M config.txt685027c Add line35b94860 Initial configThe commit is gone; its change is staged (M in the first column).
D
[main 399dfa2] Revert "Add line4" 1 file changed, 1 deletion(-)config.txt now has three lines. The Add line4 commit is still in history.
E — the conflict:
Auto-merging config.txtCONFLICT (content): Merge conflict in config.txterror: could not revert 685027c... Add line3hint: After resolving the conflicts, mark them withhint: "git add/rm <pathspec>", then runhint: "git revert --continue".hint: You can instead skip this commit with "git revert --skip".hint: To abort and get back to the state before "git revert",hint: run "git revert --abort".line1line2<<<<<<< HEADline3line4=======>>>>>>> parent of 685027c (Add line3)Resolve by writing the file you want, then continue:
printf 'line1\nline2\nline4\n' > config.txtgit add config.txtgit revert --continue --no-editgit log --onelineb32d4d0 Revert "Add line3"189b403 Add line4685027c Add line35b94860 Initial config(git revert --abort at any point returns you to before the revert began.)
6
M config.txtHEAD is now at b32d4d0 Revert "Add line3"Status is clean. The reflog’s top entry is the revert — there is no entry for the edit, because the reflog records commits and this was never one.
Explanation
Section titled “Explanation”Three places, three commands. A change can be in the working tree, the index, or a commit.
restore operates on the first two. reset moves the branch pointer and optionally resets the
first two to match. revert creates a commit. Choosing correctly is a matter of asking where is
the change now, and where should it be?
| Where the change is | Where you want it | Command |
|---|---|---|
| Working tree only | Gone | git restore <file> |
| Staged | Working tree only | git restore --staged <file> |
| Last commit | Staged | git reset --soft HEAD~1 |
| Last commit | Working tree | git reset HEAD~1 |
| Any shared commit | Undone, history kept | git revert <commit> |
| Working tree, staged, or unshared commits | Gone | git reset --hard — unrecoverable for uncommitted work |
Revert of a middle commit is a merge. Git applies the inverse patch on top of everything that came after. If later commits touched the same region, that patch does not apply cleanly. The conflict markers show HEAD’s version (with both lines) against the reverted parent’s version (with neither), and you decide.
Troubleshooting
Section titled “Troubleshooting”Step 5’s revert applied cleanly with no conflict. You skipped the reset --hard HEAD~1 that
brings line4 back. Without line4 present there is nothing adjacent to conflict with.
git revert --continue opens an editor. Add --no-edit, or save and close the editor.
Step 3 left the working tree with the change but nothing staged. You ran git reset HEAD~1
(mixed) rather than --soft. Both are valid; --soft keeps it staged.
Clean up
Section titled “Clean up”cd /tmp && rm -rf lab-rrrRelated lessons
Section titled “Related lessons”Next lab
Section titled “Next lab”Parallel work with worktrees — fix a production bug without stashing, switching, or losing your place.
How did this go?
Unexpected output. Compare your output with the expected block line by line; the first differing line is usually the cause. Then check the version-sensitive notes.
Missing prerequisite. Most "stuck" moments here come from one earlier concept. The prerequisites list names it; the learning path puts it in order.
Unclear explanation. Read the explanation section, then the mental model, then retry the step — the command usually makes sense once the model is in place.
Instructions did not match my environment. Check `git --version` and your shell (Git Bash vs PowerShell vs zsh). Platform notes on the page name the differences that matter; the verification step confirms you are back to a known state.
Good. The next step is to do it without the page open — or move to the recommended next activity below.
All help on this page: Troubleshooting on this page · Prerequisites for this page · The explanation section · Validation and recovery steps