Git Undo & History | reset, revert, and rebase explained
이 글의 핵심
reset vs revert vs restore, interactive rebase (squash, fixup, reword), conflict resolution, force push and reflog recovery, merge vs rebase team rules—practical Git workflow.
Git in practice #4: Undo, rebase, and cleanup
Previous: Remote repos and collaboration (#3) covered push, pull, and PRs.
When you need to undo commits or tidy history, the usual tools are reset (move HEAD and optionally index/worktree), revert (create a new commit that undoes another), and rebase (replay commits on top of another tip). This article explains what each does and when to be careful.
After reading you will:
- Understand
git reset --soft,--mixed,--hard, and how they differ fromgit restore/git switch - Know when to pick
git revertvsgit reset, including after a push - Use
git rebase, interactive rebase (squash, fixup, reword, edit), and resolve rebase conflicts - Recognize risks of
git reset --hardandgit push --force, recover with reflog, and align with merge vs rebase team rules
Table of contents
- Overview
- git reset
- git revert
- git rebase
- Practical cautions
- FAQ
- Command comparison
- Rebase deep dive
- Scenarios
- Dangerous commands and recovery
- History and team policy
1. Overview
| Goal | Command | Behavior |
|---|---|---|
| Undo the last commit but keep changes staged | git reset --soft HEAD~1 | Moves HEAD; index and working tree unchanged |
| Undo commit + unstage | git reset HEAD~1 (or --mixed) | Commit and staging undone; files stay modified |
| Hard reset to a commit (destructive) | git reset --hard HEAD~1 | Commit, index, and working tree match that commit |
| Undo a pushed change safely | git revert <commit> | Adds a new commit that reverses the target |
Linear history on top of main | git rebase main | Replays your commits (rewrites hashes) |
If a commit is already on the remote, avoid “deleting” it with reset --hard + force push—use revert to add an explicit undo commit. Use rebase mainly on local branches not yet pushed (or per team rules). |
2. git reset
reset —soft
Only removes the commit; staging and working tree stay:
git reset --soft HEAD~1
Typical uses:
# Fix the last commit message
git commit -m "Fix bug"
git reset --soft HEAD~1
git commit -m "Fix authentication bug"
# Add a forgotten file to the last commit
git commit -m "Add feature"
git reset --soft HEAD~1
git add forgotten_file.js
git commit -m "Add feature"
# Squash last 3 commits locally
git reset --soft HEAD~3
git commit -m "Implement user authentication"
HEAD shortcuts: HEAD~1, HEAD^, HEAD~3, or a specific hash.
reset —mixed (default)
Undoes the commit and staging; changes remain unstaged.
git reset HEAD~1
# or
git reset --mixed HEAD~1
Then git add selectively and commit again.
reset —hard
Moves commit, index, and working tree to the target—uncommitted work is lost. Stash or branch first if you might need it.
git reset --hard HEAD~1
Avoid on shared branches; combined with force push it rewrites teammates’ bases.
3. git revert
revert applies the inverse of a commit as a new commit—history stays linear and auditable.
git revert HEAD
git revert abc1234
git revert -m 1 <merge_commit_sha> # pick parent for merge commits
reset vs revert (already pushed):
# reset: removes C from history (bad if others pulled C)
# revert: adds C' that undoes C (safe for collaboration)
Conflicts during revert: fix files → git add → git revert --continue, or git revert --abort.
4. git rebase
Rebase replays your branch’s commits on top of another tip (often main). Commit hashes change—do not rebase commits others have already pulled unless coordinated.
git switch feature/login
git rebase main
On conflicts: edit → git add → git rebase --continue, or git rebase --abort to cancel.
Rules of thumb:
- Prefer rebase on local-only feature branches.
- Do not rebase shared integration branches like
maincasually.
5. Practical cautions
- Before push: reset/rebase locally as you like.
- After push: prefer revert for bad releases; force push only with team agreement.
- merge vs rebase: many teams merge into
mainand rebase feature branches only—pick one policy and stick to it.
6. FAQ
Can I recover after reset --hard?
Often yes, immediately via git reflog to find the old HEAD and reset to it. Older entries may be pruned—do not rely on it as backup.
revert vs reset?
reset moves HEAD and can drop commits from the recorded history. revert adds a new commit; safer when others share the branch.
Conflicts during rebase?
Edit conflict markers, git add the files, git rebase --continue. Use git rebase --abort to go back to pre-rebase state.
7. Command comparison
A quick lookup table for “how do I undo X” across the tools this article covers, plus the two you’ll reach for less often (restore, switch) but that avoid ambiguity in scripts and muscle-memory habits.
| Situation | Command | Affects history? | Safe after push? |
|---|---|---|---|
| Unstage a file, keep edits | git restore --staged <file> | No | ✅ |
| Discard uncommitted edits to a file | git restore <file> | No | ✅ (but destroys local edits) |
| Undo last commit, keep changes staged | git reset --soft HEAD~1 | Yes (local) | ⚠️ only if unpushed |
| Undo last commit, unstage changes | git reset HEAD~1 | Yes (local) | ⚠️ only if unpushed |
| Undo last commit, discard changes | git reset --hard HEAD~1 | Yes (local) | ❌ never on pushed/shared |
| Undo an already-pushed commit | git revert <sha> | No (adds new commit) | ✅ |
| Move current branch onto another tip | git rebase <branch> | Yes (rewrites hashes) | ⚠️ only if unpushed or coordinated |
| Switch branches | git switch <branch> | No | ✅ |
Rule of thumb: if the commit has been pushed and anyone else might have pulled it, prefer commands in the right column marked ✅ — they add history instead of rewriting it. |
8. Rebase deep dive
Interactive rebase: squash, fixup, reword
git rebase -i opens an editable list of commits, letting you reshape recent history before it’s shared.
git rebase -i HEAD~5
pick a1b2c3d Add login form
squash e4f5g6h Fix typo in login form
fixup h7i8j9k Remove debug print
reword k1l2m3n Add password validation
pick n4o5p6q Add remember-me checkbox
- pick: keep the commit as-is
- squash: merge into the previous commit, keeping both messages (editable)
- fixup: merge into the previous commit, discarding this commit’s message
- reword: keep the commit’s changes, edit only its message
- edit: pause here to amend the commit’s content
--onto: replaying onto a different base
--onto moves a range of commits to a new base without carrying commits from the branch’s old base — useful when a feature branch was accidentally built on top of another unmerged feature branch.
# Move commits unique to feature (since it diverged from old-base)
# onto new-base instead
git rebase --onto new-base old-base feature
Autosquash
Combine git commit --fixup=<sha> with git rebase -i --autosquash to automatically place fixup commits next to their target without manually reordering the todo list.
git commit --fixup=a1b2c3d
git rebase -i --autosquash HEAD~5 # fixup line is pre-sorted into place
9. Scenarios
“I committed to the wrong branch"
git branch correct-branch # create a branch at the current (wrong) commit
git reset --hard HEAD~1 # remove the commit from the wrong branch
git switch correct-branch # the commit now lives here instead
"I need to split one commit into two"
git rebase -i HEAD~3 # mark the commit as `edit`
git reset HEAD~1 # commit is undone, changes are staged
git add -p # stage half the changes
git commit -m "First half"
git add .
git commit -m "Second half"
git rebase --continue
"I want to drop a commit entirely, not just its message"
git rebase -i HEAD~5 # delete the line for that commit, or mark it `drop`
"My feature branch is 40 commits behind main and has conflicts everywhere”
Rebasing all 40 commits at once often means resolving the same conflict repeatedly. git rebase --rebase-merges combined with a merge of main first (creating one conflict-resolution point) is usually less painful than a raw rebase across a long-diverged branch — check team policy before choosing either.
10. Dangerous commands and recovery
The danger list
| Command | Why it’s dangerous | Recovery path |
|---|---|---|
git reset --hard | Discards uncommitted work permanently | git reflog finds the pre-reset HEAD, but uncommitted (never-staged) work is unrecoverable |
git push --force | Overwrites remote history; can erase others’ pushed commits | The remote’s reflog (if available) or the collaborator’s local copy |
git clean -fd | Deletes untracked files and directories with no undo | None — there is no reflog for untracked files |
git branch -D | Force-deletes a branch even with unmerged commits | git reflog still shows the commit’s SHA if you find it before GC |
Recovering with reflog
git reflog records every place HEAD has pointed, including commits that no longer belong to any branch — this is the safety net for most “I think I lost work” situations, as long as garbage collection hasn’t run yet.
git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~1
# e4f5g6h HEAD@{1}: commit: Add feature X ← the "lost" commit
git reset --hard e4f5g6h # restore it
# or, to keep current work and just recover the commit as a branch:
git branch recovered-work e4f5g6h
push --force vs push --force-with-lease
--force-with-lease refuses to push if the remote has commits you haven’t fetched yet — it protects against silently overwriting a teammate’s work that landed after your last fetch, which plain --force does not check.
git push --force-with-lease # ✅ fails safely if remote moved since your last fetch
git push --force # ⚠️ overwrites unconditionally
11. History and team policy
Merge vs rebase workflows
| Policy | How feature branches land on main | History shape |
|---|---|---|
| Merge commits | git merge feature (no --ff-only) | Preserves branch topology, every merge visible |
| Rebase + fast-forward | git rebase main then git merge --ff-only feature | Linear history, no merge commits |
| Squash merge | git merge --squash feature | One commit per feature, internal commits discarded |
Choosing a policy
- Merge commits: best when you want to preserve exactly how a feature was built, or need to trace which merge introduced a bug (
git log --merges) - Rebase + fast-forward: best for small teams that want a clean, linear
git logand are comfortable with local rebasing before push - Squash merge: best when a feature branch has messy work-in-progress commits and only the final combined result matters to
main’s history
The one rule that matters most
Whatever the team picks, the non-negotiable part is: never rewrite history on a branch other people have already pulled from, unless the whole team is explicitly coordinating the rewrite (e.g. a scheduled history cleanup). Rebase and reset --hard are safe experimentation tools on your own unpushed or exclusively-owned branches; they are a liability the moment a commit is shared.
Closing
reset --soft/--mixed: undo commits (and maybe staging) while keeping work—great locally.- reset —hard: destructive; avoid on pushed shared branches.
- revert: add an undo commit—preferred for shared history.
- rebase: linearize history—best on unpushed feature branches. One line: Use reset/rebase locally; use revert on shared branches. Continue from the Git series index. Previous: #3 Remote collaboration
Reset touches three layers (summary)
flowchart LR
subgraph layers [Per commit]
H[HEAD / commit]
I[Staging index]
W[Working tree]
end
H --> I --> W
--soft moves HEAD only; --mixed also matches the index; --hard aligns all three to the target commit.
Practical tips
Debugging
- Read compiler and linter messages first
- Reproduce with a minimal example
Performance
- Measure before optimizing
- Define what “fast enough” means
Code review
- Match team conventions
- Check edge cases reviewers care about
Checklist
Before you rewrite history
- Is this the smallest safe change?
- Will teammates understand the result?
- Any performance or release constraints?
While resolving conflicts
- Are merges intentional and tested?
- Edge cases covered?
- Error handling appropriate?
During review
- Intent clear?
- Tests updated?
- Docs updated if behavior changed?
Keywords (search)
Git, undo, reset, revert, rebase, reflog, restore, interactive rebase, force push, commit history
Related posts
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. reset vs revert vs restore, interactive rebase (squash, fixup, reword), conflict resolution, force push and reflog.
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- Git push pull 차이 | 원격 저장소·GitHub 협업·Pull Request 완벽 가이드
- Git 브랜치와 병합 | ‘merge conflict 났어요’ 충돌 해결 방법 (branch, merge)
- Git 실전 가이드 시리즈 목차 | 기초·브랜치·원격·rebase
Keywords Covered in This Article (Related Search Terms)
This article covers Git, Undo, reset, revert, rebase, git reset, git revert, reflog, restore, interactive rebase.