提交错了?不要慌。Git 提供了多种方法来撤销提交,同时保留你的代码更改。无论你需要修复提交消息、取消暂存文件,还是完全撤销已推送的提交,本指南涵盖了所有 5 种方法,附带清晰的解释和实用示例。
快速解答:撤销上次提交(保留更改)
如果你只是想撤销上次提交并保留所有更改在暂存区(随时可以重新提交),运行这一条命令:
$ git reset --soft HEAD~1这会将 HEAD 回退一个提交,但所有更改保留在暂存区。你的代码完全不变——你可以修改文件、修复提交消息,或添加更多更改后再次提交。
各部分含义:
git reset— git reset — 将 HEAD 移动到不同提交的命令--soft— --soft — 保留更改在暂存区(索引中)HEAD~1— HEAD~1 — 当前 HEAD 的前一个提交
# Before: You committed something wrong
$ git log --oneline
a1b2c3d (HEAD -> main) wrong commit message
f4e5d6c previous good commit
# Undo it:
$ git reset --soft HEAD~1
# After: Commit is gone, changes are staged
$ git log --oneline
f4e5d6c (HEAD -> main) previous good commit
$ git status
Changes to be committed:
modified: src/app.tsx
modified: src/utils.ts方法一:git reset --soft(撤销提交,保留暂存)
git reset --soft HEAD~1 是最温和的撤销提交方式。它将 HEAD 回退一个提交,但你的更改保留在暂存区。当你想用不同的消息重新提交或添加更多文件时,这是最佳选择。
适用场景:
- 你想更改提交消息
- 你忘记在提交中添加某个文件
- 你想将一个提交拆分为两个
- 你想与其他更改合并后再提交
# Undo last commit, keep changes staged
$ git reset --soft HEAD~1
# Fix the commit message and recommit
$ git commit -m "correct commit message"
# Or add a forgotten file and recommit
$ git add forgotten-file.ts
$ git commit -m "feature: add login with all files"
# Or split into two commits
$ git reset --soft HEAD~1
$ git reset HEAD src/unrelated-file.ts # unstage one file
$ git commit -m "feature: add login"
$ git add src/unrelated-file.ts
$ git commit -m "fix: update config"运行此命令后,git status 显示你的更改为"已暂存的更改"(绿色)。你可以立即再次提交。
方法二:git reset --mixed(撤销提交,保留未暂存)
git reset --mixed HEAD~1(或简写为 git reset HEAD~1,因为 --mixed 是默认选项)撤销提交并取消暂存更改。你的文件被保留,但显示为未暂存的修改。
适用场景:
- 你想有选择地只重新暂存部分文件
- 你想在重新提交前审查更改
- 你提交了错误的文件,需要重新组织
- 你想将一个大提交拆分为几个小提交
# Undo last commit, unstage all changes
$ git reset HEAD~1 # --mixed is the default
# or explicitly:
$ git reset --mixed HEAD~1
# Now selectively stage what you need
$ git status
Changes not staged for commit:
modified: src/auth.ts
modified: src/config.ts
modified: src/test.ts
# Stage only what belongs in this commit
$ git add src/auth.ts
$ git commit -m "feature: add authentication"
# Stage the rest separately
$ git add src/config.ts src/test.ts
$ git commit -m "chore: update config and tests"运行此命令后,git status 显示你的更改为"未暂存的更改"(红色)。你需要先 git add 文件才能再次提交。
与 --soft 的区别:--soft 保持更改在暂存区。--mixed 取消暂存更改。两者都保留你的文件完整性。
方法三:git commit --amend(修改上次提交)
git commit --amend 严格来说不是"撤销"提交——它用一个新提交替换上次提交。这是修复提交消息或添加遗忘文件的最快方式,不会在历史中产生额外的提交。
适用场景:
- 修复提交消息中的拼写错误
- 添加忘记包含的文件
- 移除不应该被提交的文件
- 用少量额外更改更新提交
# Fix commit message only
$ git commit --amend -m "fix: correct the typo in error handler"
# Add a forgotten file to the last commit
$ git add forgotten-file.ts
$ git commit --amend --no-edit # keeps the original message
# Remove a file from the last commit
$ git reset HEAD^ -- secrets.env # unstage the file
$ git commit --amend --no-edit
# Change both message and content
$ git add extra-changes.ts
$ git commit --amend -m "feature: complete login with validation"
# View the amended result
$ git log --oneline -1
b2c3d4e (HEAD -> main) feature: complete login with validation警告:--amend 会重写提交哈希。不要修改已推送到共享分支的提交,除非你与团队协调好。
方法四:git revert(已推送提交的安全撤销)
git revert 创建一个新提交来撤销之前提交的更改。与 reset 不同,它不会重写历史——而是添加历史。这是在共享/公共分支上撤销提交的唯一安全方式。
适用场景:
- 提交已推送到共享分支
- 其他开发者已拉取该提交
- 你需要可审计的撤销(revert 本身会被记录)
- 在 main/master 或任何受保护分支上工作
# Revert the last commit (creates a new commit)
$ git revert HEAD
# Git opens editor with default message:
# Revert "add broken feature"
# This reverts commit a1b2c3d.
# Skip the editor, use default message
$ git revert HEAD --no-edit
# Revert a specific commit by hash
$ git revert a1b2c3d
# Revert without committing (stage changes only)
$ git revert HEAD --no-commit
$ git status
Changes to be committed:
modified: src/feature.ts # changes are reversed
$ git commit -m "revert: remove broken feature"
# Result: history shows both the original and the revert
$ git log --oneline
e5f6g7h (HEAD -> main) Revert "add broken feature"
a1b2c3d add broken feature
f4e5d6c previous good commit工作原理:如果你的提交添加了一行,revert 提交会删除那一行。如果你的提交删除了一个文件,revert 提交会恢复该文件。
与 reset 的关键区别:Reset 从历史中移除提交。Revert 添加一个新提交来撤销更改。Reset 用于本地/未推送的提交。Revert 用于已推送/共享的提交。
方法五:git reflog(恢复丢失的提交)
git reflog 是你的安全网。它记录本地仓库中 HEAD 的每一次移动——即使在 reset --hard 等破坏性操作之后。如果你意外丢失了提交,reflog 可以帮你恢复。
适用场景:
- 你运行了 git reset --hard 并丢失了更改
- 你需要在错误的 rebase 后恢复提交
- 你意外删除了包含未合并提交的分支
- 你需要找到仓库的先前状态
# View the reflog (recent HEAD movements)
$ git reflog
a1b2c3d (HEAD -> main) HEAD@{0}: reset: moving to HEAD~1
f4e5d6c HEAD@{1}: commit: add important feature
b7c8d9e HEAD@{2}: commit: previous work
...
# Oh no! I accidentally reset --hard and lost my commit!
# Find it in reflog:
$ git reflog
b7c8d9e (HEAD -> main) HEAD@{0}: reset: moving to HEAD~1
f4e5d6c HEAD@{1}: commit: add important feature # <-- there it is!
# Recover it:
$ git reset --hard f4e5d6c
# or create a new branch from it:
$ git branch recover-branch f4e5d6c
# Recover after a bad rebase:
$ git reflog
c1d2e3f (HEAD -> feature) HEAD@{0}: rebase (finish)
a1b2c3d HEAD@{1}: rebase (start)
x9y8z7w HEAD@{2}: commit: my work before rebase # <-- before rebase
$ git reset --hard x9y8z7w # back to pre-rebase state
# Recover a deleted branch:
$ git branch -D feature-branch # oops!
$ git reflog | grep feature
h1i2j3k HEAD@{5}: commit: work on feature-branch
$ git checkout -b feature-branch h1i2j3k # recovered!重要提示:Reflog 仅限本地。它跟踪你的机器上 HEAD 的移动。它不跟踪其他人的操作,条目默认在 90 天后过期。
对比:5 种方法一览
| 方法 | 命令 | 保留更改? | 重写历史? | 对已推送安全? | 最适合 |
|---|---|---|---|---|---|
| reset --soft | git reset --soft HEAD~1 | 是(已暂存) | 是 | 否 | 重新提交/修改消息 |
| reset --mixed | git reset HEAD~1 | 是(未暂存) | 是 | 否 | 选择性重新暂存 |
| commit --amend | git commit --amend | 是(替换) | 是 | 否 | 快速修复上次提交 |
| revert | git revert HEAD | 否(新提交) | 否 | 是 | 已推送/共享的提交 |
| reflog | git reflog + reset | 恢复丢失的 | - | - | 恢复丢失的提交 |
# Visual: How the 3 reset modes affect your changes
Working Dir Staging Area Repository
─────────── ──────────── ──────────
reset --soft ✓ kept ✓ kept ✗ commit removed
reset --mixed ✓ kept ✗ unstaged ✗ commit removed
reset --hard ✗ DELETED ✗ DELETED ✗ commit removed
commit --amend ✓ kept ✓ merged ✗ commit replaced
revert ✓ kept ✓ new commit ✓ preserved (adds new)已经推送了?如何处理共享分支
撤销后的操作取决于提交是否已推送以及其他人是否已拉取。
场景 1:已推送,但还没人拉取
你可以使用 reset + 强制推送,但要用 --force-with-lease 以避免覆盖他人的工作:
# Undo the commit locally
$ git reset --soft HEAD~1
# Make your fix
$ git add .
$ git commit -m "correct commit"
# Force push (safe version)
$ git push --force-with-lease origin my-branch警告:强制推送会重写远程历史。只在你自己拥有的分支上这样做。
场景 2:已推送,且其他人已拉取
使用 git revert。这是唯一安全的选择:
# Create a revert commit
$ git revert HEAD --no-edit
# Push normally (no force needed)
$ git push origin main场景 3:推送到了受保护分支(main/master)
大多数团队保护 main 分支不允许强制推送。你必须使用 revert:
# On a protected branch, revert is your only option
$ git revert HEAD --no-edit
$ git push origin main
# Or revert via a pull request:
$ git checkout -b revert-broken-feature
$ git revert HEAD --no-edit
$ git push origin revert-broken-feature
# Then create a PR to merge the revert经验法则:如果不确定,使用 git revert。它始终是安全的。
撤销多个提交:HEAD~2、HEAD~3
你可以通过更改 HEAD~ 后面的数字来撤销多个提交。数字指定要回退多少个提交。
# Undo last 2 commits (keep changes staged)
$ git reset --soft HEAD~2
# Undo last 3 commits (keep changes unstaged)
$ git reset HEAD~3
# Undo last 5 commits (DISCARD all changes - dangerous!)
$ git reset --hard HEAD~5HEAD~N 表示"HEAD 之前 N 个提交"。你也可以使用提交哈希:
# Reset to a specific commit hash
$ git log --oneline
a1b2c3d (HEAD) fourth commit
e4f5g6h third commit
i7j8k9l second commit
m0n1o2p first commit
# Go back to "second commit"
$ git reset --soft i7j8k9l
# Now "third commit" and "fourth commit" changes are staged警告:在共享分支上使用 reset 撤销多个提交是危险的。应该对每个提交使用 revert:
# Safe way to undo multiple pushed commits: revert each one
# Revert in reverse order (newest first)
$ git revert HEAD --no-edit # undo commit 3
$ git revert HEAD~1 --no-edit # undo commit 2
# Or revert a range (Git 1.7.2+)
$ git revert HEAD~2..HEAD --no-edit
# Push all reverts at once
$ git push origin main提示:撤销多个提交时,按倒序(从最新开始)逐个 revert 以避免冲突。
常见问题
git reset --soft、--mixed 和 --hard 有什么区别?
--soft 保留更改在暂存区(随时可提交)。--mixed(默认)保留更改但取消暂存。--hard 永久丢弃所有更改。--soft 和 --mixed 都是安全的——你的代码会被保留。只有 --hard 是破坏性的。
可以撤销 git reset --hard 吗?
如果及时操作,可以。使用 git reflog 找到 reset 之前的提交哈希,然后运行 git reset --hard <hash> 来恢复。但是,被 --hard 丢弃的未提交更改无法被 Git 恢复(它们从未被记录)。
应该使用 reset 还是 revert?
对本地/未推送的提交使用 reset——历史更干净。对已推送/共享的提交使用 revert——安全且不会破坏其他开发者的历史。如果不确定,用 revert。
如何撤销合并提交?
对于未推送的合并:git reset --hard HEAD~1(丢弃合并)。对于已推送的合并:git revert -m 1 HEAD(-m 1 告诉 Git 回退到哪个父提交,通常是主分支)。注意:撤销合并后,重新合并同一分支会变得复杂。
HEAD~1 和 HEAD^ 有什么区别?
在大多数情况下,HEAD~1 和 HEAD^ 是相同的——都指向当前提交的父提交。区别在合并提交时才重要:HEAD^ 是第一个父提交(通常是 main),HEAD^2 是第二个父提交(被合并的分支)。HEAD~2 表示"祖父提交"(在第一父链中回退两步)。
如何撤销提交但保留磁盘上的文件?
使用 git reset --soft HEAD~1(保留更改在暂存区)或 git reset HEAD~1(保留更改为未暂存)。两者都会保留你的文件。只有 git reset --hard HEAD~1 会删除你的更改。如果你已经用了 --hard,立即检查 git reflog。