“Undo the last change” is not a precise Git operation. The unwanted state may live only in a file, in the index prepared for the next commit, in a local commit, or in history that other people have already fetched. restore, reset, and revert act on different parts of that model, and choosing by command familiarity can destroy work that was not part of the request.
Choose by the state you intend to change, name the source you want to copy from, and inspect the diff both before and after the operation.
Map the repository before changing it
Git exposes at least four relevant states:
- The working tree contains the files currently on disk.
- The index contains the snapshot proposed for the next commit.
HEADidentifies the current commit, usually through a branch.- A remote-tracking reference records the last fetched state of a remote branch.
Start with commands that show each boundary:
git status --short --branch
git diff
git diff --staged
git log --oneline --decorate -n 8
An unstaged line appears in git diff. A staged line appears in git diff --staged. A committed change appears in history. A published commit requires a fetch and comparison with the relevant remote-tracking reference; local status alone cannot prove who else has consumed it.
Before a risky operation, preserve valuable uncommitted content or create a named backup branch for committed history. Recovery is easier when the old state has an explicit reference.
Use restore to copy file content between states
git restore is path-oriented. By default it replaces selected working-tree paths with their index versions. With --staged, it replaces selected index entries, using HEAD by default. The official restore documentation describes the source and destination rules explicitly.
To unstage one path while keeping its working-tree edits:
git restore --staged -- src/billing/invoice.ts
git diff -- src/billing/invoice.ts
git diff --staged -- src/billing/invoice.ts
To discard unstaged edits in a tracked path and copy the index version into the working tree:
git diff -- src/billing/invoice.ts
git restore --worktree -- src/billing/invoice.ts
The second operation is destructive for the unstaged tracked edits in that path. Read the diff first. It does not remove untracked files, and it does not move a branch.
To recover a file version from another commit without changing the current branch:
git restore --source=<verified-commit> --worktree -- src/billing/invoice.ts
This writes content into the working tree; it does not preserve the original file automatically. Use --patch when only selected hunks should be restored, and verify the resulting working-tree diff.
Use reset only when the reference or index is the target
git reset has two broad forms. A path-limited reset updates the index. A commit-level reset can move the current branch and, depending on mode, update the index and working tree. That breadth is why “just reset it” is unsafe advice.
The git reset manual defines the modes. A practical summary is:
--softmovesHEADwhile leaving index and working tree content in place.--mixed, the default commit-level mode, movesHEADand resets the index while leaving working-tree files.--hardmovesHEADand replaces tracked index and working-tree content.--mergeand--keephave specialized preservation behavior and still require state inspection.
For a private local commit that must be recommitted differently, first create a backup branch, then use a mode chosen for the desired destinations:
git branch backup/before-recommit HEAD
git reset --soft HEAD^
git status --short --branch
git diff --staged
This example moves the current branch back one parent and leaves the reverted commit's content staged. It changes commit history. Do not use it on shared history merely because the file result looks convenient.
Avoid reset --hard as a generic cleanup command. If the real task is to unstage a file, restore the index. If the task is to abandon one path's unstaged edits, restore that path after reviewing it. A narrower operation reduces collateral loss.
Use revert for a shared commit that must remain visible
git revert creates a new commit that applies the inverse of an existing commit. It preserves the original object and the fact that the project later backed it out. This is usually the appropriate model for a bad change already shared on a collaborative branch.
git show --stat <bad-commit>
git revert <bad-commit>
git show --stat --format=fuller HEAD
The revert documentation requires a clean working tree for the ordinary workflow. A revert can conflict because later changes may touch the same lines or depend on the behavior being removed. Resolve the conflict according to current intent, run tests, and use git revert --abort when the proposed inverse cannot be justified.
Reverting a merge needs an explicit mainline parent and has long-term merge consequences. Do not guess the parent number. Inspect the merge topology and decide which parent's view should be treated as the mainline.
Choose with a state transition, not a slogan
Use this decision sequence:
- If the unwanted content is unstaged in tracked files, consider a path-limited restore from the index after inspecting the diff.
- If it is staged but should remain edited, restore only the index entry from
HEAD. - If it is in an unshared local commit, preserve the old tip and choose reset or rebase based on the intended new history.
- If it is in shared history, prefer a revert commit so consumers see an additive correction.
- If the desired object or boundary is unclear, perform no mutation and investigate with status, log, show, diff, and reflog.
After every operation, rerun the four inspection commands from the opening. Confirm the working tree, index, HEAD, and remote relationship separately. The correct undo is the smallest state transition that removes the unwanted effect without silently removing unrelated work or rewriting history that other people rely on.
Was this guide useful?
Your rating helps us prioritize clearer, more practical technical content.
Review diffs, run checks, and prepare the release in Arezgit.
Keep Git review, security scanning, API checks, database inspection, and release preparation together in one local desktop application.
Explore Arezgit