Undoing a local commit in Git is a common task that every developer encounters, whether they need to fix a typo, rework a feature, or simply discard an experiment. In practice, because Git stores commits as immutable snapshots, “undoing” does not erase history; instead, it moves the branch pointer or creates a new commit that reverses changes. This leads to understanding the different tools Git provides—git reset, git revert, git checkout, and git reflog—lets you choose the safest method for your situation. This guide walks through each approach, explains when to use it, and offers step‑by‑step examples so you can confidently manage your local workflow.
Understanding Git Commits and the HEAD Pointer
Before diving into commands, it helps to visualize what a commit represents. Each commit is a node in a directed acyclic graph (DAG) that points to its parent(s). The special reference HEAD always points to the tip of the current branch, indicating which commit your working tree reflects Simple, but easy to overlook..
The moment you run git commit, Git creates a new node, updates HEAD to reference it, and stores a snapshot of the staged files. Undoing a commit therefore means moving HEAD (and optionally the branch reference) to a different node, or adding a new node that negates the previous changes The details matter here. Turns out it matters..
It sounds simple, but the gap is usually here.
Key concepts to keep in mind:
- Soft reset – moves HEAD but leaves the index and working directory untouched.
- Mixed reset (default) – moves HEAD and resets the index to match the new HEAD, but keeps the working directory unchanged.
- Hard reset – moves HEAD, resets the index, and discards any changes in the working directory.
- Revert – creates a new commit that applies the inverse of a previous commit, preserving history.
- Reflog – records every change to HEAD, allowing you to recover commits that appear lost after a reset.
Common Scenarios for Undoing a Local Commit
Different goals dictate which command to use. Below are the most frequent situations developers face:
| Goal | Recommended Command | What It Does |
|---|---|---|
| Keep changes staged for further editing | git reset --soft HEAD~1 |
Moves HEAD back one commit; index and working tree stay as they were. |
| Keep changes unstaged (ready to modify) | git reset HEAD~1 (or git reset --mixed HEAD~1) |
Moves HEAD back; index is reset, but working directory retains the modifications. |
| Discard the commit and all its changes | git reset --hard HEAD~1 |
Moves HEAD back and resets both index and working directory to match the new HEAD. |
| Create a new commit that undoes the previous one (safe for shared branches) | git revert HEAD |
Adds a new commit with the inverse changes; history stays linear. |
| Temporarily save work before resetting | git stash → git reset --hard → git stash pop |
Shelves changes, moves HEAD, then reapplies the shelved work. |
| Recover a commit after a hard reset | git reflog → git reset --hard <hash> |
Finds the lost commit’s hash and moves HEAD back to it. |
Using git reset to Move HEAD
The git reset command is the primary tool for moving the branch pointer. It accepts a mode (--soft, --mixed, --hard) and a commitish (often expressed relative to HEAD, like HEAD~1 or a full SHA) Worth knowing..
Soft Reset – Keep Changes Staged
# Move HEAD back one commit, keep index and working tree unchanged
git reset --soft HEAD~1
After this command:
- The commit you just “undid” disappears from the branch’s history.
- The index (staging area) still contains the exact file states that were committed.
- Your working directory reflects those same staged changes, ready for you to edit, re‑stage, and commit again.
When to use: You realized the commit message was wrong, or you want to combine this commit with the next one before creating a final snapshot Easy to understand, harder to ignore. Surprisingly effective..
Mixed Reset – Keep Changes Unstaged (Default)
# Equivalent to `git reset --mixed HEAD~1`
git reset HEAD~1
Result:
- HEAD moves back one commit.
- The index is reset to match the new HEAD, so previously staged changes become unstaged.
- The working directory retains the modifications exactly as they were before the reset.
When to use: You want to uncommit but keep the edits visible in your editor so you can modify them before staging again.
Hard Reset – Discard Commit and Changes
# Move HEAD back and discard all changes in index and working tree
git reset --hard HEAD~1
Result:
- HEAD points to the parent commit.
- Both the index and working directory are overwritten to match that commit.
- Any uncommitted work introduced by the undone commit is permanently lost (unless you have a reflog entry).
When to use: The commit was a mistake, and you are certain you do not need any of its changes. Use with caution on shared branches because it rewrites history That's the part that actually makes a difference. Practical, not theoretical..
Resetting Multiple Commits
You can move HEAD back more than one step by increasing the tilde count or specifying an exact SHA:
# Undo the last three commits, keeping changes unstaged
git reset --mixed HEAD~3
# Or, using a specific commit hash
git reset --hard a1b2c3d
Using git revert to Create an Inverse Commit
The moment you need to preserve a clear, linear history—especially on branches that others may have pulled—git revert is the safest option. Instead of moving HEAD, it adds a new commit that applies the opposite patch of the target commit.
# Revert the most recent commit
git revert HEAD
# Revert an older commit by specifying its hash
git revert
Git will open your default editor to let you edit the revert commit message. After saving, a new commit appears, effectively canceling out the changes while leaving the original commit intact in the history.
Advantages:
- History remains non‑destructive; collaborators can still see what happened.
- Works well on public branches where rewriting history (
reset --hard) would cause conflicts.
Drawbacks:
-
It creates a new commit rather than erasing the original, so the project history grows longer and can become noisier over time.
-
If later commits touched the same lines of code, the revert may trigger merge conflicts that you must resolve manually.
-
Reverting a merge commit requires the
-mflag to specify which parent lineage should be treated as the mainline. -
The original commit remains visible in
git log, so this method does not remove sensitive or problematic data from the repository’s history Worth knowing..
Choosing Between git reset and git revert
Use git reset when the commits exist only on your local branch and you want a clean, linear history without extra “revert” entries. It is ideal for fixing a bad commit message, squashing work-in-progress snapshots, or discarding a mistaken local commit before anyone else has pulled it That alone is useful..
Use git revert when the commits have already been pushed to a shared branch or when other teammates may have based work on them. Because it appends an inverse commit instead of rewriting history, it avoids forcing collaborators to reconcile diverged branches.
A Word of Caution on Shared Branches
Never run git reset --hard (or any history-rewriting reset) on a branch that others are actively using, and never force-push a reset branch without coordinating with your team. Doing so invalidates the commit history your colleagues have already fetched, leading to complicated recovery and lost work. When in doubt, revert rather than reset.
Conclusion
Git gives you multiple ways to undo work, but the right choice depends on whether the commit is still private or has already been shared. git reset lets you move HEAD backward and reshape recent, local history quickly, while git revert safely cancels changes on public branches by adding a new inverse commit. Here's the thing — always inspect git status and git log before running a reset, and treat --hard as a destructive operation that can permanently discard uncommitted work. With these tools, you can correct mistakes confidently without jeopardizing your repository’s integrity.