Before this lesson

Understand the index, branches and shared history. Compare undo operations in a temporary repository first.

Use Git 2.35+ and a Bash-compatible terminal, such as Git Bash on Windows. Each complete example creates an independent temporary directory. Run it only in that practice repository. Configuration is repository-local and uses a practice identity. Temporary directories are not automatically removed; clean them up after finishing.

What you will be able to do

Choose an undo operation by state while preserving traceable history.

Understand the mechanism

First ask whether the change is staged, committed or shared. restore --staged unstages while retaining working edits. Plain restore overwrites working files and discards relevant uncommitted edits. Do not confuse the two.

revert creates a new commit reversing a previous change, preserving the original in shared history. It can conflict. reset has different reference-moving semantics; --hard also overwrites the index and worktree.

reflog records prior local reference positions and can help locate commits before a mistake. It is not a permanent cloud backup and cannot guarantee recovery of content Git never recorded.

Runnable example

lab=$(mktemp -d)
cd "$lab"
git init -b main .
git config user.name "Honeysuck Learner"
git config user.email 'learner'@'example.invalid'
git config commit.gpgsign false
git config core.autocrlf false
printf 'Start learning\n' > notes.txt
git add notes.txt
git commit -m "Start learning notes"
printf 'Draft only\n' >> notes.txt
git add notes.txt
git restore --staged notes.txt
git diff -- notes.txt
# The following line intentionally discards this practice-only draft.
git restore notes.txt
printf 'Wrong advice\n' >> notes.txt
git add notes.txt
git commit -m "Add advice to undo"
git revert --no-edit HEAD
cat notes.txt
git log --oneline -3
git reflog -3

Trace and verify

Unstaging retains Draft only in the worktree, then the draft is explicitly discarded. After revert, the file returns to Start learning while history retains initial, incorrect and reversal commits.

Common mistakes and boundaries

Do not experiment with discard commands on unbacked-up work. Reverting a merge requires understanding its mainline parent; this example handles an ordinary single-parent commit.

Practice challenge

Where should you stop if you only want to unstage and retain the text?

Reference solution and reasoning

Stop after git restore --staged notes.txt. Confirm with git diff, then edit or stage again without proceeding to plain restore.

Lab: restoring content differs from reverting history

For a bad shared commit, revert records a new inverse change while retaining the previous history. restore copies selected content into a target location and does not itself create a commit. Compare them in this disposable history experiment without reset --hard.

lab=$(mktemp -d)
cd "$lab"
git init -q -b main
git config user.name "Honeysuck Learner"
git config user.email 'learner'@'example.invalid'
git config commit.gpgsign false
git config core.autocrlf false
printf 'correct\n' > answer.txt
git add answer.txt
git commit -q -m "Correct answer"
printf 'wrong\n' > answer.txt
git add answer.txt
git commit -q -m "Mistake"
bad=$(git rev-parse HEAD)
git revert --no-edit "$bad" >/dev/null
cat answer.txt
git rev-list --count HEAD
git merge-base --is-ancestor "$bad" HEAD && printf 'Original commit retained\n'
git restore --source="$bad" --worktree answer.txt
cat answer.txt
git status --short
printf 'Practice directory: %s\n' "$lab"

Expected observations

After revert, the file is correct, the commit count is 3 and the ancestor check succeeds. After restore, the worktree says wrong and status is M answer.txt with a leading space; the commit count remains 3.

Why this happens and where it stops applying

revert does not move the branch pointer backward; it applies an inverse change in the current context, so later edits can cause conflicts. Reverting a merge also requires choosing a mainline parent and is outside this simple example. Reflog records local reference movements but can expire and is not a remote backup. First decide whether work was committed, whether history was shared and which later changes must survive.

Extension challenge

Why is revert usually preferable after colleagues have fetched the mistaken commit?

Reasoned solution

The inverse commit lets colleagues advance along existing history and preserves review context. Force-rewriting creates divergent histories requiring coordination. Even with revert, verify the resulting behavior; command success alone does not prove the business change is correct.

Further reading