DevToolBox免费
博客

Git 撤销最后一次提交:5 种保留更改的修复方法

7 分钟阅读作者 DevToolBox

提交错了?不要慌。Git 提供了多种方法来撤销提交,同时保留你的代码更改。无论你需要修复提交消息、取消暂存文件,还是完全撤销已推送的提交,本指南涵盖了所有 5 种方法,附带清晰的解释和实用示例。

快速解答:撤销上次提交(保留更改)

如果你只是想撤销上次提交并保留所有更改在暂存区(随时可以重新提交),运行这一条命令:

$ git reset --soft HEAD~1

这会将 HEAD 回退一个提交,但所有更改保留在暂存区。你的代码完全不变——你可以修改文件、修复提交消息,或添加更多更改后再次提交。

各部分含义:

  • git resetgit reset — 将 HEAD 移动到不同提交的命令
  • --soft--soft — 保留更改在暂存区(索引中)
  • HEAD~1HEAD~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 --softgit reset --soft HEAD~1是(已暂存)重新提交/修改消息
reset --mixedgit reset HEAD~1是(未暂存)选择性重新暂存
commit --amendgit commit --amend是(替换)快速修复上次提交
revertgit revert HEAD否(新提交)已推送/共享的提交
refloggit 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~5

HEAD~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。

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

🔀Git Command Generator.gi.gitignore Generator±Text Diff Checker

相关文章

Git 命令速查表:开发者必备命令大全

完整的 Git 命令速查表:涵盖配置、分支、合并、变基、暂存和高级工作流程。

Git Rebase vs Merge:何时使用哪个(图解对比)

理解 git rebase 和 merge 的区别。学习何时使用哪个,避免常见陷阱,掌握 Git 工作流。