Skip to content

Lab: Reset, Revert and Restore — Choose the Right One

Lesson 4 of 6Beginner → Intermediate5 min readHands-On Git & GitHub Labs · Git Recovery Labs
Time20 minutes
LevelBeginner → Intermediate
You needLocal Git only — no GitHub account needed

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.

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.

Terminal window
mkdir -p /tmp/lab-rrr && cd /tmp/lab-rrr
git init -q -b main .
git config user.email "lab@example.com"
git config user.name "Lab User"
printf 'line1\nline2\n' > config.txt
git add . && git commit -q -m "Initial config"
printf 'line1\nline2\nline3\n' > config.txt
git add . && git commit -q -m "Add line3"
printf 'line1\nline2\nline3\nline4\n' > config.txt
git add . && git commit -q -m "Add line4"
git log --oneline
189b403 Add line4
685027c Add line3
5b94860 Initial config

For each situation, decide which command applies before running it.

  1. Situation A — you edited a file and want the edit gone. Nothing staged, nothing committed.

    Terminal window
    echo "oops" >> config.txt
    git status --short

    Which command discards the edit and leaves everything else alone?

  2. 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.txt
    git add config.txt
    git status --short

    Which command unstages without touching the working tree? (Then discard the edit as in A.)

  3. 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~1
    git status --short
    git log --oneline

    Then recommit: git commit -q -m "Add line4".

  4. 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-edit
    git log --oneline
    cat config.txt
  5. Situation E — undo a commit in the middle of history. Reset the tip revert first so line4 is back, then revert Add line3:

    Terminal window
    git reset --hard HEAD~1 # remove the revert from D; line4 returns
    git revert HEAD~1 --no-edit

    It does not go cleanly. Read the output, then cat config.txt. Resolve it so the file contains line1, line2, line4, and continue the revert.

  6. The destructive one. Make an uncommitted edit, then:

    Terminal window
    echo "unsaved" >> config.txt
    git status --short
    git reset --hard HEAD
    git status --short
    git reflog | head -3

    Where did the edit go?

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.

A

Terminal window
git restore config.txt

B

Terminal window
git restore --staged config.txt # status: " M config.txt" — unstaged, edit kept
git restore config.txt # then discard it

C

M config.txt
685027c Add line3
5b94860 Initial config

The 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.txt
CONFLICT (content): Merge conflict in config.txt
error: could not revert 685027c... Add line3
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run
hint: "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".
line1
line2
<<<<<<< HEAD
line3
line4
=======
>>>>>>> parent of 685027c (Add line3)

Resolve by writing the file you want, then continue:

Terminal window
printf 'line1\nline2\nline4\n' > config.txt
git add config.txt
git revert --continue --no-edit
git log --oneline
b32d4d0 Revert "Add line3"
189b403 Add line4
685027c Add line3
5b94860 Initial config

(git revert --abort at any point returns you to before the revert began.)

6

M config.txt
HEAD 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.

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 isWhere you want itCommand
Working tree onlyGonegit restore <file>
StagedWorking tree onlygit restore --staged <file>
Last commitStagedgit reset --soft HEAD~1
Last commitWorking treegit reset HEAD~1
Any shared commitUndone, history keptgit revert <commit>
Working tree, staged, or unshared commitsGonegit reset --hardunrecoverable 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.

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.

Terminal window
cd /tmp && rm -rf lab-rrr

Parallel work with worktrees — fix a production bug without stashing, switching, or losing your place.

Choose a learning pathA sequenced route through the curriculum for wherever you are now.