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
|
|
| |
|---|---|---|---|
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
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
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
# 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
# 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
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
git revert HEAD # History grows: 9c1a2b3 ─ <revert> ─ HEAD # Plain git push works; nobody'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 mustgit push --force-with-leaseand coordinate with anyone who has the old history.git revert— always safe. Other people pull your new commit like any other.