TL;DR: git revert adds a new commit that undoes an old one — history is preserved, safe on branches other people have pulled. git reset moves the branch pointer backward — history is rewritten, only safe on commits you haven’t pushed. Use revert on shared branches, reset on local cleanup. reset --hard also deletes uncommitted working-tree changes; that’s the one that eats real work.
What is the difference between git revert and git reset?
Both make your project look like a past commit never happened. They disagree about how:
git revert <sha>computes the inverse patch of a commit and commits it. Your history grows by one commit that says “undo that one”. The bad commit remains in the log, followed by its cancellation. SHAs of everything else stay untouched.git reset <sha>moves the current branch’s pointer to<sha>. The commits after it are detached — still in the object database until garbage collection, but no longer reachable from any branch orgit log.
One rewrites nothing, the other pretends nothing happened. That single property decides which one each situation calls for:
# Demo: a runnable throwaway repo
git init /tmp/revert-vs-reset && cd /tmp/revert-vs-reset
echo one > file.txt && git add . && git commit -m "one"
echo two > file.txt && git add . && git commit -m "two"
# Revert: history keeps both commits, plus a third that undoes "two"
git revert --no-edit HEAD
git log --oneline # 3 commits: revert, two, one
# Reset: branch pointer moves back, "two" vanishes from the log
git reset --hard HEAD~1
git log --oneline # 1 commit: one Run both and inspect git log — the asymmetry is the whole answer.
What do git reset —soft, —mixed, and —hard actually do?
The three modes control where your changes survive after the pointer moves:
| Mode | Branch pointer | Staging area | Working tree | Your edits |
|---|---|---|---|---|
--soft | moves back | kept staged | untouched | fully kept |
--mixed (default) | moves back | unstaged | untouched | kept, unstaged |
--hard | moves back | reset | reset | deleted |
Concrete reads of that table:
git reset --soft HEAD~1— “I committed too early.” Everything goes back to staged, ready to recommit, perhaps together with more work.git reset HEAD~1(mixed) — “I staged the wrong files.” Edits survive in the working tree, nothing is staged. Pair it withgit remove untracked fileswhen you also want stray files gone.git reset --hard HEAD~1— “This commit and my uncommitted edits are garbage.” Git will not ask twice; there is no undo for the working-tree part unless you stashed or committed it first.
revert has no modes because it never destroys anything — it only ever adds a compensating commit.
When should you use git revert instead of git reset?
Ask one question: has anyone else pulled this commit? If yes, reset is off the table.
Resetting a branch others have based work on rewrites shared history. Their next pull meets divergent branches, and the “fix” is usually a force-push that turns your mistake into everyone’s problem. Revert appends a normal commit, so a plain git pull merges cleanly for everyone — that’s why revert is the default answer on main and on GitHub pull requests, where the “Revert” button exists precisely because rewriting a merged PR’s history is not an option.
Reset is for the window before sharing: the commit you made thirty seconds ago, the staging mistake, the local experiment branch. If the only copy of the work lives on your machine, you are free to rearrange history — that is the entire point of the pre-push window.
There is a middle case: you pushed to your own feature branch and nobody else builds on it. A force-push after reset is socially acceptable there, but revert still costs less coordination. Reserve force-pushes for branches you own alone.
Is git revert safer than git reset for shared branches?
Yes, structurally — not just by convention:
- Revert produces an ordinary commit; CI runs on it, the diff is reviewable, and
git logexplains to future-you why the change disappeared. - Reset silently discards the intermediate state. Nobody reviewing later can see what was undone or when, because the log shows a straight line that never contained it.
Two practical sharp edges:
- Reverting a merge commit needs
git revert -m 1 <sha>(keep the first parent’s line). Without-m, git refuses and leaves you to read the error. - Revert is not a time machine: it undoes one commit’s diff. If later commits touched the same lines, you may resolve conflicts — that is git telling you the undo is entangled, which is useful information, not a malfunction.
For undoing your very last commit specifically — keeping or discarding its changes — the options are laid out in git undo last commit: keep the changes.
What about git restore — where does it fit?
git restore (and git switch, its pair) arrived in git 2.23 to take over the jobs reset was doing badly by name overload:
| Task | Old way | Clear way |
|---|---|---|
| Discard working-tree edits in a file | git checkout -- file / git reset --hard | git restore file |
| Unstage a file | git reset HEAD file | git restore --staged file |
| Move branch to another commit | git reset <sha> | git reset <sha> (no replacement — this stays) |
So the modern split is: restore fixes files, reset moves branches, revert undoes published commits. Autocomplete shows people search “git revert vs reset vs restore” together for good reason — they are one mental model split across three commands. The old git checkout forms still work everywhere; the new forms just stop you from sending a whole branch to the shredder when you meant to unstage one file.
If your goal is copying a good commit forward rather than erasing a bad one, that is cherry-pick’s job — see git cherry-pick: multiple commits, branches, conflicts.
Quick decision recap
- Commit is public (pushed, others pulled) →
git revert <sha>. - Commit is local-only, want to redo it →
git reset --softor--mixedand recommit. - Local-only, want it gone with the uncommitted mess →
git reset --hard, after one honest look at what else the working tree holds. - One file got mangled →
git restore <file>and leave the branch alone.
The only genuinely dangerous entry in that list is --hard — everything else can be talked back with git reflog. The permanent losses in git are narrow and mostly require you to type it explicitly; the habit worth building is pausing half a second before --hard, not avoiding git’s sharp tools altogether.
— mrsaynothing
Systemd Service Not Starting? How to Fix It
Enjoying the write-ups? I build like this for a living. hire me