Build the mental model
`git reset` and `git revert` both undo changes, but take opposite approaches to history — reset moves the branch pointer backward and shortens history, while revert never touches existing commits and instead adds a new one.
| Tool | What happens to history |
|---|---|
| reset | Moves branch pointer backward; later commits become detached, history shortens |
| revert | Adds a new commit undoing another; nothing existing is rewritten, history grows |
If a bad commit is private and unpushed, reset is fine. Once pushed and pulled by a teammate, reset plus force-push rewrites their history out from under them — revert is the safe choice instead.
- `--soft` — leaves changes staged, ready to recommit
- `--mixed` (default) — unstages them but leaves them in your working directory
- `--hard` — discards everything completely, no undo button
- Reset
- Moves the current branch pointer to a different commit, optionally changing the staging area and working directory too — rewrites local history.
- Revert
- Creates a new commit whose changes undo an existing commit, without altering any commit that already exists — safe for shared history.
RESET (SHORTENS HISTORY) VS REVERT (ADDS COMMIT)
------------------------------------------------
RESET (SHORTENS HISTORY) VS REVERT (ADDS COMMIT)
----------------------------------------------------
Before: C1 --- C2 --- C3 <- main
git reset --hard C1:
C1 <- main (C2, C3 detached, shorter)
git revert C3:
C1 --- C2 --- C3 --- C4 <- main
(C4 undoes C3, longer)Connect it to a real scenario
For a bad commit that's already shared, `git revert <hash>` (with `--no-edit`) creates a new commit reversing its changes.
Comparing `git log --oneline` before and after shows the original commit still present, with the new revert commit on top undoing its effect.
| Mode | Where changes end up |
|---|---|
| --soft | Staged, ready to recommit ('Changes to be committed') |
| HEAD (mixed) | Unstaged but still in your files ('Changes not staged for commit') |
git reset --hard destroys uncommitted work
`--hard` discards staged AND unstaged changes permanently — there's no straightforward way to get them back. Always run `git status` before a `--hard` reset to see exactly what you're about to lose.
Try the working example
git init -q -b main
git config user.name "Thuta Learner"
git config user.email "learner@example.com"
# --- Part 1: revert (safe on shared history) ---
mkdir part1 && cd part1
git init -q -b main
git config user.name "Thuta Learner"
git config user.email "learner@example.com"
export GIT_AUTHOR_NAME="Thuta Learner"
export GIT_AUTHOR_EMAIL="learner@example.com"
export GIT_COMMITTER_NAME="Thuta Learner"
export GIT_COMMITTER_EMAIL="learner@example.com"
echo "body { color: black; }" > style.css
git add style.css
GIT_AUTHOR_DATE="2026-01-04T09:00:00" GIT_COMMITTER_DATE="2026-01-04T09:00:00" git commit -m "Add base styles"
echo "body { color: red; }" >> style.css
git add style.css
GIT_AUTHOR_DATE="2026-01-04T10:00:00" GIT_COMMITTER_DATE="2026-01-04T10:00:00" git commit -m "Change text to red (bad idea)"
BAD_COMMIT=$(git rev-parse HEAD)
git log --oneline
GIT_AUTHOR_DATE="2026-01-04T11:00:00" GIT_COMMITTER_DATE="2026-01-04T11:00:00" git revert --no-edit $BAD_COMMIT
git log --oneline
cat style.css
cd ..
# --- Part 2: reset --soft / mixed (separate fresh repo) ---
mkdir part2 && cd part2
git init -q -b main
git config user.name "Thuta Learner"
git config user.email "learner@example.com"
echo "console.log('start');" > app.js
git add app.js
GIT_AUTHOR_DATE="2026-01-05T09:00:00" GIT_COMMITTER_DATE="2026-01-05T09:00:00" git commit -m "Initial commit"
echo "console.log('oops typo comit');" >> app.js
git add app.js
GIT_AUTHOR_DATE="2026-01-05T10:00:00" GIT_COMMITTER_DATE="2026-01-05T10:00:00" git commit -m "Bad commit with typo"
git log --oneline
git reset --soft HEAD~1
git status
git reset HEAD
git status$ git log --oneline
23ae617 Change text to red (bad idea)
a43964a Add base styles
$ git revert --no-edit 23ae617
[main b1d22c5] Revert "Change text to red (bad idea)"
Date: Sun Jan 4 11:00:00 2026 +0900
1 file changed, 1 deletion(-)
$ git log --oneline
b1d22c5 Revert "Change text to red (bad idea)"
23ae617 Change text to red (bad idea)
a43964a Add base styles
$ cat style.css
body { color: black; }
--- Part 2: reset ---
$ git log --oneline
e379b53 Bad commit with typo
1e07e9f Initial commit
$ git reset --soft HEAD~1
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: app.js
$ git reset HEAD
Unstaged changes after reset:
M app.js
$ git status
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: app.js
no changes added to commit (use "git add" and/or "git commit -a")5-minute try-it
Make two commits in a scratch repo. Use `git revert` to undo the second one and confirm with `git log --oneline` that history grew rather than shrank. Then, in a separate scratch repo, make a commit you regret, and try all three reset modes in sequence (`--soft`, then unstage with plain `git reset HEAD`) — after each, run `git status` and note exactly what changed.
One important caution
Using `git reset --hard` (or force-pushing after any reset) on commits that have already been pushed and pulled by others rewrites shared history and breaks their clones.
`git reset --hard` deletes uncommitted staged and unstaged changes with no confirmation and no easy recovery — always check `git status` first.
Quick Check: Undoing a Shared Commit
Git Documentation: git-revert — Git & GitHub