Gitreset vs revert vs restore

reset vs revert vs restore

These three commands all undo something, but they undo different things at different layers. Once you can place each one on the three-trees diagram, choosing between them becomes automatic.

The one-line definitions
  • git restore — change files. Never touches history or the branch pointer.

  • git reset — move the branch pointer (and optionally the trees). Rewrites history.

  • git revert — add a new commit that undoes an old one. History grows forward; nothing is rewritten.

Side-by-side comparison

git restore

git reset

git revert

Touches files?

Yes

Yes (with --mixed/--hard)

Yes (via the new commit)

Touches the index?

Yes (with --staged)

Yes (with --mixed/--hard)

Yes (stages the inverse diff)

Moves the branch pointer?

No

Yes

Forward only (new commit)

Creates a new commit?

No

No

Yes

Rewrites history?

No

Yes

No

Safe on a shared branch?

Yes

No

Yes

Reversible by reflog?

Only committed state

Yes

Yes (revert the revert)

Per-file?

Yes (default)

Yes (with path)

No (whole commits only)

Visual: how each moves the pointers

Start: A ─ B ─ C ◀── main, HEAD on main, C is broken

Text
git restore --source=B src/util.js
  → A ─ B ─ C  ◀── main
    Files in working dir change. main still points at C.

git reset --hard B
  → A ─ B  ◀── main           (C is now unreachable)
    Branch pointer rewound. Working dir matches B.

git revert C
  → A ─ B ─ C ─ C'  ◀── main
    New commit C' whose changes are the inverse of C.
    main moves forward, history preserved.
Decision flowchart

Pick one

Text
Has the bad commit been pushed / pulled by anyone else?
│
├── YES  → git revert            (only safe option)
│
└── NO
    │
    ├── You only need to change FILE CONTENTS (no history fuss)
    │       → git restore
    │
    └── You need to MOVE THE BRANCH POINTER
            (uncommit, drop commits, jump to a SHA)
            → git reset
            (use --soft / --mixed / --hard depending on what you want
             to happen to the index and working tree)
Same starting point, three different fixes

Setup

Bash
# Imagine this history (newest at bottom)
git log --oneline
# 9c1a2b3 (HEAD -> main, origin/main) Broken: change everything
# 7f8d9e0  Add greeting
# 3a4b5c6  Initial commit

Option A: restore — keep history, just fix the file

Bash
# Replace src/util.js with the version from before the bad commit
git restore --source=HEAD~1 src/util.js
git commit -am "Fix broken util.js"

Option B: reset — pretend the bad commit never happened

Bash
git reset --hard HEAD~1
# History is now: 7f8d9e0 ─ 3a4b5c6
# Requires git push --force-with-lease since origin still has 9c1a2b3

Option C: revert — append a fix on top of the bad commit

Bash
git revert HEAD
# History grows: 9c1a2b3 ─ <revert> ─ HEAD
# Plain git push works; nobody&apos;s clone breaks.
Which one is safe to push?
  • git restore — your changes are normal edits, push them like any other commit.

  • git reset — only safe if you have not pushed yet. After reset on a pushed branch you must git push --force-with-lease and coordinate with anyone who has the old history.

  • git revert — always safe. Other people pull your new commit like any other.

Warning
Do not `git reset --hard` a branch that other developers have pulled. They will keep pulling your old commits back into their clones (and yours when you fetch), causing endless confusion. Use `git revert` on shared branches.
Mental model
Think of history as a row of commits and your branch as a sticky-note clipped to one of them. **restore** rewrites the files under the sticky-note. **reset** moves the sticky-note. **revert** clips the sticky-note to a brand-new commit that undoes an older one — the older commit stays right where it was.
Tip
When in doubt on a shared branch, default to `git revert`. It is slightly noisier in `git log` but it never breaks anyone's clone — and that is a price worth paying every time.