Pushing a broken commit to the production branch right before the weekend triggers absolute panic. Finding the correct command to undo that mistake without destroying your teammates' code is the difference between a calm fix and a full team standstill.

  • Git Reset: Rewrites history completely and permanently. Use strictly for local, unpushed changes.
  • Git Revert: Appends a new commit that negates the old one. Completely safe for public and shared branches.
  • Data Recovery: The git reflog command acts as your safety net and recovers virtually any deleted commit.

Quick Comparison: Git Revert vs. Git Reset

Featuregit revertgit reset
How it worksCreates a new commit that undoes a previous oneMoves the branch pointer, erasing commits
Project historyPreserved. A new revert commit is added to the logRewritten. Commits can be permanently lost
Best use caseUndoing commits on public/shared branchesCleaning up local-only commits not yet pushed
Risk levelLow. Safe for all collaborationHigh. Dangerous on shared branches

The 3-Question Decision Logic

Navigating the terminal under pressure leads to catastrophic typos. Ask yourself these three questions before typing anything:

  1. Have you pushed the code to a remote repository? If yes, you must use revert.
  2. Are other developers actively working on this branch? If they are, resetting will create a nightmare of divergent histories.
  3. Do you just want to fix a typo in your last commit message? That's a job for git commit --amend, not reset or revert.

The Three Trees Git Uses Internally

Understanding Git requires visualizing its three internal storage areas. The working directory holds your raw, unsaved files on disk. The staging area is a waiting room for your upcoming commit. The repository history contains the permanent record of all saved changes.

This is why --hard deletes your work permanently (it wipes all three areas), while --soft only moves the pointer and leaves your files staged. Knowing which tree you're targeting prevents accidental data loss.

Using Git Reset for Local Unpushed Changes

Cleaning up a messy local branch before submitting a pull request is a standard daily task. You can squash, remove, or modify recent local commits freely because nobody else relies on that history yet.

Warning: Never use git reset on a public or shared branch (like main or develop) that other developers have already pulled. Rewriting shared history causes severe conflicts and breaks your team's workflow. This is as disruptive as force-deleting a Git branch others are actively using.

The Difference Between --soft, --mixed, and --hard

# --soft: moves HEAD back, keeps all changes in the STAGING AREA
git reset --soft HEAD~1
# --mixed (default): moves HEAD back, keeps changes in WORKING DIRECTORY (unstaged)
git reset HEAD~1
# --hard: moves HEAD back, PERMANENTLY DELETES all changes from that commit
git reset --hard HEAD~1
  • --soft is the safest: your changes are still staged and ready to re-commit.
  • --mixed keeps your files but un-stages them. You need to git add again before committing.
  • --hard wipes everything. There is no undo for this, unless you use git reflog (see below).

Using Git Revert for Shared Public Branches

Fixing a bug on the main branch requires a transparent audit trail. git revert generates a brand-new commit that performs the exact opposite of your target commit. Your repository history stays intact, and your team experiences zero synchronization issues.

# 1. Find the commit hash
git log --oneline
# 2. Revert it
git revert a1b2c3d
# 3. Push the new revert commit
git push

Git opens your editor to confirm the revert commit message. The default is fine. Save and close to complete it.

How to Safely Revert a Merge Commit

Undoing an entire feature branch merge requires an extra parameter. Without it, Git throws an error because it doesn't know which side of the merge to keep.

# This will FAIL on a merge commit
git revert 4s9f6b1
# This is correct: -m 1 keeps the mainline (main branch) history
git revert -m 1 4s9f6b1

Handling Revert Conflicts

Trying to revert a three-month-old commit rarely goes smoothly. Git will likely pause the process with a conflict because the surrounding code has changed since then. Open the affected files, resolve the conflict markers (<<<<<<<, =======, >>>>>>>), then:

git add <resolved-file>
git revert --continue

If you want to bail out entirely: git revert --abort.

Modernizing Your Workflow: Git Restore and Git Switch

The older git checkout handles too many different tasks at once, which confuses everyone. Recent Git versions separate these jobs cleanly:

  • git switch <branch> replaces git checkout <branch> for changing branches. When you need to rename a Git branch, git switch handles the transition cleanly.
  • git restore <file> replaces git checkout -- <file> for discarding local file changes.

If you're on Git 2.23 or later, prefer these commands. They're explicit about intent and far less error-prone.

Recovering Lost Code with Git Reflog

Accidentally typing a hard reset feels like the end of the world. Git actually records every single HEAD pointer movement locally for 90 days by default.

# See every recent action
git reflog
# Output example:
# a1b2c3d HEAD@{0}: reset: moving to HEAD~1
# f4e5d6c HEAD@{1}: commit: add login feature
# ...
# Restore to the state before the mistake
git reset --hard HEAD@{1}

Find the index number of the state before your mistake and reset to it. This works even after a --hard reset.

Common Undo Scenarios

Scenario A: I misspelled my last commit message.

Don't use reset or revert for this. The right tool:

git commit --amend

This rewrites the most recent commit message only.

Scenario B: I committed locally, haven't pushed, want to undo and rework.

# Keeps files staged, ready to re-commit
git reset --soft HEAD~1
# Or keeps files in working directory (unstaged)
git reset HEAD~1

Scenario C: I pushed a bad commit to main and my team already has it.

You must use git revert:

git revert <bad-commit-hash>
git push

Scenario D: I made 3 local commits and want to delete all of them.

# WARNING: this destroys the last 3 commits and all their changes
git reset --hard HEAD~3

Scenario E: I need to undo multiple pushed commits.

# Creates a separate revert commit for each of the last 3 commits
git revert HEAD~3..HEAD
git push

Advanced Revert Scenarios

Reverting a Revert

Sometimes a reverted feature actually needs to go back into the next release. Reverting the original revert commit restores the original code cleanly without touching shared history. It feels circular, but it's the mathematically correct approach.

# Find the revert commit hash
git log --oneline
# Revert the revert
git revert <revert-commit-hash>

Undoing Multiple Commits in a Range

# Reverts the last 3 commits, one revert commit per original commit
git revert HEAD~3..HEAD

Each commit in that range gets its own clean reversal commit, keeping your history fully readable.

The rule stays consistent no matter the scenario: if the commit is on a shared branch, revert it. If it's local-only and you're certain, reset it. And if you make a mistake either way, git reflog is always there to recover from.