Gitgit reset (soft, mixed, hard)

git reset (soft, mixed, hard)

git reset is the command that moves the current branch pointer to a different commit. Depending on the flag you pass, it can leave the index and working directory alone, reset the index too, or reset everything. It is one of Git's most powerful — and most easily misused — commands.

What reset actually does

Reset moves the branch pointer, then optionally the trees

Text
Before:                After  git reset HEAD~1:

  A ─ B ─ C  ◀── main      A ─ B  ◀── main
              ▲                 ▲
              HEAD              HEAD     (C is now unreachable from main,
                                          but still in the object DB)
The three modes

Mode

Branch pointer

Index (staging)

Working dir

Use when

--soft

Moved

Untouched

Untouched

You want to redo the last commit with the same files staged.

--mixed (default)

Moved

Reset to target

Untouched

You want to uncommit and unstage, but keep your edits.

--hard

Moved

Reset to target

Reset to target

You want to throw away everything since the target. Destructive.

--soft: uncommit, keep everything staged

Soft reset moves the branch pointer back but does not touch the index or working tree. The files you committed are still staged and ready to be re-committed.

Redo the last commit

Bash
git reset --soft HEAD~1
git status
#   Changes to be committed:
#       modified:   src/util.js

# Now you can re-commit with a different message,
# combine in more files, split into multiple commits, etc.
--mixed (default): uncommit and unstage

Mixed reset moves the pointer and rewrites the index to match. Your working-tree edits stay, but they become unstaged modifications.

The everyday uncommit

Bash
git reset HEAD~1            # --mixed is implied
git status
#   Changes not staged for commit:
#       modified:   src/util.js
--hard: throw it all away

Discard the last commit AND its changes

Bash
git reset --hard HEAD~1
git status
#   nothing to commit, working tree clean

# Wipe all local changes back to the last commit
git reset --hard HEAD

# Reset to whatever the remote has
git fetch origin
git reset --hard origin/main
Warning
`--hard` deletes uncommitted edits from your working directory with no second chance. Anything that was not at least once committed is gone forever — the reflog cannot save working-tree edits, only committed objects. Stash or branch first.
Path-mode reset (per-file)

When you pass a path to git reset, it does not move the branch pointer. Instead it copies the file from the target commit into the index only. This is the old way to unstage a file (the modern way is git restore --staged).

Unstage one file the old way

Bash
git reset HEAD src/util.js
# Index reverts for that one file; working tree untouched.
# Modern equivalent:
git restore --staged src/util.js
--keep: safer than --hard for switching

--keep resets the index and working tree to the target — but fails if local modifications would be overwritten. It is a safer way to jump to another commit when you suspect you might have unsaved work.

A safer hard

Bash
git reset --keep origin/main
# error: Entry 'src/util.js' not uptodate. Cannot merge.
# (so you know to stash or commit first)
reset vs revert vs checkout/restore

Command

What it moves

Rewrites history?

Safe to push?

git reset

Branch pointer (+ trees)

Yes

No — force-push required

git revert

Nothing — appends a new commit

No

Yes

git restore

Files only

No

Yes (only changes your files)

Recovery if you reset to the wrong place

Every reset is logged in the reflog. If you regret a reset --hard, you can almost always come back as long as the lost commits had been committed at some point.

Undo a bad reset

Bash
git reflog
# 9c1a2b3 HEAD@{0}: reset: moving to HEAD~3
# 7f8d9e0 HEAD@{1}: commit: the work I just blew away

git reset --hard HEAD@{1}     # back to safety
# Or with the SHA directly
git reset --hard 7f8d9e0
Mental model
Think of a branch as a sticky-note on a commit. `git reset` peels the note off and sticks it on a different commit. The `--mode` flag decides what happens to your index and files at the same time.
Tip
If your finger is hovering over Enter for `git reset --hard`, first run `git switch -c backup-$(date +%s)`. That branch is now a permanent rescue rope you can drop after you confirm the reset did what you wanted.