TechEarl

git reset vs git revert: When to Use Each

git reset rewrites history; git revert records a new undo commit. Here is the conceptual split, the three reset modes, and why revert is safe on shared branches.

Ishan Karunaratne⏱️ 8 min readUpdated
Share thisCopied
git reset vs git revert compared: when to rewrite history versus record an undo commit

git reset and git revert both undo work, but they do it in opposite ways. git reset moves your branch pointer backward and rewrites history, as if the later commits never happened. git revert leaves history intact and adds a brand new commit that cancels out an earlier one. The short rule: use reset on commits you have not shared yet, and use revert on anything you have already pushed to a branch other people pull from.

The one-line decision

If you are still deciding which to reach for, here is the whole thing in two lines:

bash
# Local, not pushed yet: rewrite history, the bad commit disappears
git reset --hard HEAD~1

# Already pushed / shared: record an undo commit, history stays
git revert HEAD

Both leave you in a sane state. The difference is what they do to the commit graph, and whether anyone else has to deal with the fallout.

How they actually differ

reset is a history-rewriting operation. It says "pretend my branch was always here," and the commits you reset past are no longer reachable from the branch tip. They still exist for a while (see recovering lost commits with the reflog), but as far as the branch is concerned they are gone.

revert is an additive operation. It computes the inverse of a commit's changes and applies that as a new commit on top of your current history. The original commit stays in the log forever; you just have a follow-up commit that says "and then I took that back."

git resetgit revert
What it doesMoves the branch pointer to an older commitAdds a new commit that reverses an old one
HistoryRewritten (commits dropped from the branch)Preserved (everything stays in the log)
Safe on shared branchesNoYes
Creates a new commitNoYes (one per reverted commit)
Touches working treeDepends on mode (--soft, --mixed, --hard)Yes, applies the inverse diff
Typical useClean up local commits before pushingUndo a commit that is already public
Recoverable if wrongVia the reflog, time-limitedTrivially, just revert the revert

The three reset modes

git reset always moves the branch pointer. What it does to the staging area (the index) and your working files depends on the mode. This is the part that trips people up, so go slowly.

--soft: move the pointer, keep everything staged

bash
git reset --soft HEAD~1

The branch pointer moves back one commit. Your staging area and working files are untouched. The changes from the commit you just "undid" are sitting right there, staged and ready. This is the move when you want to redo the last commit, maybe to reword it or combine it with new work. It is the simplest way to undo the last commit without losing a single line.

--mixed: move the pointer, unstage the changes (the default)

bash
git reset --mixed HEAD~1
# same as:
git reset HEAD~1

--mixed is what you get when you do not pass a mode. The pointer moves back, and the changes are unstaged, but they stay in your working tree. So your files still have all the edits; they are just no longer staged. Reach for this when you want to re-stage things differently before committing again. It is also how you pull a file back out of the staging area without losing edits, which overlaps with discarding local changes when you go further.

--hard: move the pointer and throw the changes away

bash
git reset --hard HEAD~1

This is the destructive one. The pointer moves back, the staging area is reset, and your working files are overwritten to match. Any uncommitted work in those files is gone. Use --hard only when you are certain you want the changes erased. If you run it by accident, do not panic and do not keep committing on top: go straight to the reflog to recover the lost commits before the dangling commits get garbage-collected.

Here is the same set of modes summarized:

ModeBranch pointerStaging areaWorking tree
--softMoved backKept (changes stay staged)Kept
--mixed (default)Moved backReset (changes unstaged)Kept
--hardMoved backResetReset (changes discarded)

Why revert is the safe choice on shared branches

The reason reset is dangerous on a shared branch comes down to one fact: it rewrites history. Once you have pushed a commit, that commit's hash is part of the history everyone else has pulled. If you reset past it and force-push the rewritten branch, your branch no longer contains commits that other people's branches still do. The next time a teammate runs git pull, their history and yours have diverged, and you get the painful "histories have diverged" mess that ends in conflicts or, worse, someone re-pushing the commits you tried to delete. At that point everyone has to reconcile the diverged branches before any of them can push cleanly again.

git revert sidesteps all of that. It never removes a commit; it only adds one. So the shared history grows forward the way Git expects, no force-push, no rewritten hashes, nothing for teammates to reconcile. They pull a normal new commit that happens to undo an old one.

bash
# Undo a specific public commit by hash
git revert 9f1c0a2

# Undo a range (each commit gets its own revert, newest first)
git revert HEAD~3..HEAD

# Stage the inverse changes without committing yet, so you can group them
git revert --no-commit 9f1c0a2

If a revert turns out to be wrong, the fix is almost funny in how simple it is: you revert the revert. The change comes right back, history still intact. That reversibility is exactly why this is the tool you want anywhere other people are pulling from. For a deeper picture of why divergent histories cause so much grief, see git pull vs git fetch.

A worked example

Say you committed an API key by accident and have not pushed yet. You want it gone from the last commit:

bash
# Not pushed: rewrite history, the commit vanishes
git reset --hard HEAD~1

Now say the same bad commit is already on main and three teammates have pulled it. Resetting is off the table, and if you have set up undoing a commit on a branch protected by rules, the force-push that a reset would need is blocked outright. That is exactly why revert is the right tool on a shared or protected branch. Instead:

bash
# Pushed: add a commit that undoes the bad one
git revert <commit-hash>
git push

One important caveat on secrets specifically: revert undoes the change, but the secret still sits in the repository's history and can be checked out from the old commit. Reverting is not enough to scrub a leaked credential. Rotate the key, and if you truly need it gone from history, follow the steps in removing a secret from Git history. The exposed .git directory attack is a good reminder of why a leaked history is a real risk and not a theoretical one.

reset and revert are not the only undo tools

These two cover most undo situations, but Git has neighbors worth knowing:

If you are still building the mental model for how commits, branches, and the staging area fit together, start at Git for beginners and the branching explainer. The undo commands make a lot more sense once the graph clicks.

Sources

Authoritative references this article was fact-checked against.

Tagsgit reset vs revertGitVersion Controlgit resetgit revertundo

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts