Git Revert: Undo Pushed Commits Without Rewriting History
Git revert safely undoes pushed commits by creating new commits.
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
- ✓Basic Git commit/push workflow
- ✓Understanding of Git history as a DAG
Use git revert to create a new commit that reverses the changes of a specific commit. Unlike reset, revert is safe for public branches because it doesn't alter existing history.
Imagine you're editing a shared document and someone accidentally deletes a paragraph. Instead of erasing their edit from the history (which would confuse everyone), you write a new paragraph that says 'Ignore the deletion — here's the original text back.' That's revert: a new commit that cancels out the old one.
Most devs learn git reset first. It's clean, it's fast, and it's absolutely the wrong tool for shared branches. I've seen teams rewrite a release branch's history at 2 AM and spend the next day untangling merge conflicts across 12 developers. Reset is for local-only work. Revert is for anything pushed.
The core problem: once you push a commit, other people may have based work on it. Rewriting that commit (via reset --hard, amend, rebase) breaks their history. Git revert solves this by creating a new commit that applies the inverse of the target commit's diff. History stays linear and everyone's local branches remain compatible.
By the end of this, you'll know exactly when to revert, how to revert merges safely, and the three gotchas that will burn you if you only read the man page.
Why Revert Exists: The Shared Branch Problem
You pushed a commit. Someone pulled it. Now you can't rewrite history without breaking their clone. Reset --hard is a local-only tool — it moves the branch pointer backward, discarding commits. If you force-push that, everyone else's local branches point to commits that no longer exist on remote. The next pull creates a mess of diverged history and merge conflicts.
Revert solves this by moving forward. It creates a new commit whose diff is the exact inverse of the target commit. The branch history remains a straight line. Everyone's local branches can fast-forward or merge cleanly. This is why every CI/CD pipeline and shared branch workflow mandates revert over reset.
Here's the production scenario: a developer pushes a commit that accidentally deletes a critical config file. The commit is now in main. Three other devs have pulled it. You can't reset — you'd break their repos. Instead, you revert: git revert a1b2c3d. This creates a new commit that restores the deleted file. Everyone pulls, gets the fix, and moves on.
git revert --no-edit to skip the editor and auto-generate a revert commit message. Saves time in automated rollback scripts.Reverting a Merge Commit: The -m Flag
Merges are special. A merge commit has two parents: the mainline branch and the feature branch. When you revert a merge, Git needs to know which parent to keep. The -m flag specifies the parent number (1 = first parent, usually mainline; 2 = second parent, the feature branch).
Without -m, Git refuses to revert a merge. It's too dangerous — it can't guess which side to undo. The classic mistake: reverting a merge without -m 1 and getting an error. Then using -m 2 by accident, which reverts the mainline changes and keeps the feature branch's changes — the opposite of what you want.
Production scenario: a feature branch was merged into main, but the feature has a critical bug. You need to undo the merge, but keep mainline history intact. Run git revert -m 1 <merge-hash>. This reverts the diff that the merge introduced, effectively undoing the feature branch's changes while preserving mainline history.
Reverting Multiple Commits: Order Matters
To revert a range of commits, use git revert OLD..NEW. But Git applies reverts in reverse chronological order — it starts from the newest commit and works backward. This avoids conflicts: if commit B depends on commit A, reverting A first would break B's context. By reverting B first, then A, each revert applies cleanly.
You can also revert a list of individual commits. Git will process them in the order you specify, but it's smart enough to reorder if needed to avoid conflicts. For safety, always revert from newest to oldest.
Production scenario: the last three commits all introduced bugs. Instead of reverting one by one, run git revert HEAD~3..HEAD. This reverts HEAD, then HEAD~1, then HEAD~2, creating three new revert commits. Your history stays linear and clean.
git revert --no-commit with a range unless you understand the conflict risk. It stages all reverts as a single commit, which can cause merge conflicts if the commits touch the same lines.When Revert Breaks Down: Conflicts and Revert of Revert
Revert is not magic. If the files changed by the target commit have been modified by later commits, revert can cause merge conflicts. Git tries to apply the inverse diff, but if the context has shifted, it can't. You'll see the same conflict markers as any merge.
Another failure mode: reverting a commit that was itself a revert. This is common when you need to re-introduce a feature that was reverted. The fix is to revert the revert — but if there have been intervening commits, you may get conflicts.
Production scenario: a feature was merged, then reverted. Later, the team decides to bring it back. They try to revert the revert, but another commit has modified the same files. Now they have to resolve conflicts manually. The lesson: revert is not a time machine. It's a forward-moving undo that respects the current state of the branch.
git log --oneline for intervening commits before reverting a revert.Revert vs Reset vs Restore: When to Use What
git reset: moves the branch pointer backward. Destructive for shared branches. Use only for local, unpushed commits.git revert: creates a new commit that undoes a previous commit. Safe for shared branches. Use for any pushed commit.git restore: unstages or discards changes in the working directory. Doesn't affect commit history. Use for local changes you haven't committed.
The rule of thumb: if the commit has been pushed to a shared branch, use revert. If it's only local, you can use reset. If you haven't committed yet, use restore.
Production scenario: a junior dev pushes a commit with a typo in a variable name. The commit is on main. They ask if they can reset. No — use revert. Then they can fix the typo in a new commit. The history shows the mistake and the fix, which is honest and auditable.
The Merge Revert That Lost a Feature
git revert <revert-hash>. Or use git rebase --onto to rewrite the feature branch.- Reverting a merge doesn't erase the merge from history — it just adds an anti-merge.
- To re-introduce the same changes, you must revert the revert.
fatal: commit <hash> is a merge but no -m option was givengit revert -m 1 <hash> to keep the mainline branch. 3. Resolve any conflicts, then git revert --continue.CONFLICT (content): Merge conflict in <file> during revertgit status to see conflicted files. 2. Edit files to resolve conflicts. 3. git add <file> for each resolved file. 4. git revert --continue to finish.git revert <revert-hash>. 3. Now the feature branch can be merged again.`git cat-file -p <hash> | head -5``git log --oneline --graph <hash>^..<hash>`git revert -m 1 <hash>| File | Command / Code | Purpose |
|---|---|---|
| revert-basic.sh | echo "delete me" > critical_config.txt | Why Revert Exists |
| revert-merge.sh | git revert -m 1 M --no-edit | Reverting a Merge Commit |
| revert-range.sh | git revert HEAD~3..HEAD --no-edit | Reverting Multiple Commits |
| revert-conflict.sh | echo "line1" > file.txt | When Revert Breaks Down |
| compare-undo.sh | git commit -m "wip" | Revert vs Reset vs Restore |
Key takeaways
-m 1 when reverting merge commits to keep the mainline branch.Interview Questions on This Topic
You pushed a commit to main, then realized it's buggy. How do you undo it without breaking other developers' clones?
git revert <hash>. It creates a new commit that undoes the changes. Unlike reset, revert doesn't rewrite history, so other developers can pull without conflicts.Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
That's Git. Mark it forged?
3 min read · try the examples if you haven't