Use git reset safely to unstage, rewind, or recover work
You will finish able to choose the right form of git reset: remove a file from the index, rewind a local commit while keeping its changes, or deliberately discard tracked work. The commands below were checked with Git 2.43.0 and its installed git-reset(1) manual page. Allow about 15 minutes, plus time to inspect your repository before changing it.
The route
Jump straight to the step you need, or tick off Done means at the end.
You need Git, a working repository, and a clean enough understanding of which commit and files you intend to affect. No command in this guide needs elevated privileges. Do not use sudo to repair a repository: it can leave root-owned files behind and hide an ordinary path or permission problem.
1. Record the starting point
Run these read-only checks before resetting anything:
$ git --version
git version 2.43.0
$ git status --short
$ git log --oneline --decorate -5
$ git branch --show-current
Read the status output as a map of your current state. A leading M in the first column means the index differs from HEAD; a leading M in the second column means the working file differs from the index. Untracked files are not removed by an ordinary mixed reset, but --hard can delete an untracked path that is in the way of writing a tracked path.
Checkpoint: write down the current branch and the commit you want to keep. If the work matters, make a normal commit or a stash first. A reset moves the current branch name, so it is a history-changing operation even when it leaves file contents alone.
2. Unstage one file and keep its edits
Use a pathspec after -- when the aim is only to change the index:
$ git reset -- path/to/file.txt
Unstaged changes after reset:
M path/to/file.txt
This copies the path's state from HEAD into the index. It does not change the working file or the current branch. In practical terms, it is the opposite of git add path/to/file.txt. The -- separator prevents a filename beginning with a hyphen from being interpreted as an option.
Verify both sides of the change:
$ git diff --cached -- path/to/file.txt
$ git diff -- path/to/file.txt
The first diff should now be empty for that path. The second should still show your edits. If you meant to discard the edits as well, stop here and use a separately reviewed working-tree command such as git restore; this path form of reset is not the destructive step.
3. Rewind one local commit but keep the work
When the latest commit needs correcting, inspect it and then use a soft reset:
$ git show --stat --oneline HEAD
$ git reset --soft HEAD^
$ git status --short
MM path/to/file.txt
--soft moves HEAD, and therefore the current branch, to HEAD^. It leaves the index and working tree untouched. The committed changes consequently appear staged, ready for a revised commit. The old tip is also recorded as ORIG_HEAD before the operation.
Use the default mixed mode when you want the same rewind but want the changes unstaged:
$ git reset HEAD^
Unstaged changes after reset:
M path/to/file.txt
$ git status --short
M path/to/file.txt
Mixed mode resets the index to the target commit and leaves the working tree alone. It is the default when no mode is supplied. A reset without a commit, such as git reset, uses HEAD as the target and therefore normally just unstages indexed changes.
Checkpoint: confirm the branch tip with git log --oneline -2, then inspect git diff and git diff --cached before recommitting. If you need the original commit message, ORIG_HEAD names the previous tip immediately after this reset, although later history-changing commands can replace that convenience reference.
4. Understand the destructive mode before using it
Warning
git reset --hard <commit> changes the current branch, resets the index, and makes tracked files in the working tree match the target commit. Tracked edits not saved elsewhere are discarded. Untracked files or directories that block a tracked path can also be deleted. Treat this as destructive, especially in a shared checkout.
Only run it after identifying the exact target and saving anything you may need:
$ git log --oneline --decorate -5
$ git diff --exit-code
$ git diff --cached --exit-code
$ git reset --hard HEAD~1
HEAD is now at 0123456 previous commit
$ git status --short
The abbreviated commit in the final message is repository-specific, so do not copy it as a target. Replace HEAD~1 with a commit you have inspected. A clean status is useful evidence, but it does not prove that the discarded work is recoverable.
For a merge or pull you want to abandon, the manual documents git reset --hard ORIG_HEAD, but that also discards local edits. If local edits must survive, git reset --merge ORIG_HEAD can abort instead when its safety checks allow it. --keep is another guarded option: it aborts if a file changed between HEAD and the target has local changes.
5. Recover from a mistaken reset
Git records movements of HEAD in the reflog. Inspect it before attempting another reset:
$ git reflog --date=local -5
0123456 HEAD@{0}: reset: moving to HEAD~1
789abcd HEAD@{1}: commit: finish the change
$ git show --stat HEAD@{1}
If the earlier commit is the one you need, restore the branch to that reflog entry after checking it:
$ git reset --hard HEAD@{1}
This recovery command is itself destructive to uncommitted working-tree edits. Save or inspect those edits first. Reflogs are local records, are not a substitute for a remote backup, and can expire according to repository maintenance. If a reset rewrote commits already shared with others, coordinate before force-pushing; replacing public history can disrupt every clone based on it.
6. Avoid the common traps
- Do not confuse a path reset with a commit reset.
git reset -- filechanges the index for that path;git reset <commit>moves the branch. - Do not use
--hardto clear a conflict or a dirty tree until you have checked both staged and unstaged diffs. - Do not assume
ORIG_HEADis a permanent backup. Create a branch or tag when you need a durable reference. - Keep the target explicit in scripts. Git defaults the target to
HEAD, and the default mode is--mixed. - For submodules, a working-tree update with
--recurse-submodulesalso resets active submodule worktrees and detaches theirHEADat the recorded commit. Check submodule status first.
Done means
- You recorded the branch, target and staged or unstaged state before changing it.
- You used a path reset when you only needed to unstage a file.
- You chose
--soft, mixed,--mergeor--keepaccording to what must survive. - You treated
--hardas destructive and saved anything that mattered. - You checked
git status, the relevant diffs and the reflog after the operation.