你做了一个想要撤销的 Commit。怎么办?
这种事每个人都会遇到。你 commit 得太早了,包含了不该包含的文件,写了错误的消息,或者发现代码就是不对。好消息是:Git 就是为处理这种情况而设计的。更好的消息是:在大多数情况下,你可以撤销 commit 而不丢失任何工作。
正确的方法取决于一个关键问题:commit 已经 push 到 remote 了,还是仍在本地?本地 commit 给你更多的灵活性。已 push 的 commit 需要更安全的策略,因为其他人可能已经 pull 了你的更改。
用 git reset 撤销本地 Commit
如果 commit 还在本地(未 push),git reset 是最常用的工具。它将 branch 指针移回到之前的 commit,实际上"取消"了你的 commit。这些更改会怎样取决于你使用的标志。
git reset --soft:撤销 commit,保持所有内容在 staging 中
git reset --soft HEAD~1
这会撤销最后一个 commit,但保持所有更改在 staging(索引)中。你的工作目录不受影响。就好像你从未运行过 git commit——你回到了 commit 之前的那一刻。
使用时机:你想更改 commit 消息、向 commit 添加更多文件,或将 commit 拆分为更小的部分。你的代码没问题,只是 commit 太早了。
git reset --mixed:撤销 commit,将更改从 staging 中移除
git reset --mixed HEAD~1
这是 git reset 的默认行为(你可以省略 --mixed 标志)。它撤销 commit 并将更改从 staging 中移除,但你的文件在工作目录中仍保持修改状态。不会丢失任何东西。
使用时机:你想重新考虑 commit 中包含什么。也许你只想 staging 某些文件,或者想在重新 commit 之前检查更改。
git reset --hard:撤销 commit,丢弃所有更改
git reset --hard HEAD~1
这会撤销 commit 并永久删除所有更改。你的工作目录被 reset 为与上一个 commit 匹配的状态。通过正常的 Git 操作无法恢复被丢弃的更改。
使用时机:你确定完全不需要这些更改。也许你 commit 了不起作用的实验性代码,想要一个全新的开始。请谨慎使用。
回退更多步
HEAD~1 指的是当前 HEAD 之前的一个 commit。你可以回退更远:
git reset --soft HEAD~3
这会撤销最近三个 commit,将它们所有的更改合并到 staging 区域。你也可以通过哈希值指向特定的 commit:
git reset --soft a1b2c3d
用 git commit --amend 修复最后一个 Commit
如果你只需要微调最后一个 commit 而不是完全撤销它,--amend 是最快的选择:
git commit --amend -m "corrected commit message"
这会用新的 commit 替换之前的 commit。你也可以用它来添加遗忘的文件:
git add forgotten-file.ts
git commit --amend --no-edit
--no-edit 标志保持原始 commit 消息不变。结果是一个修正后的 commit,而不是一系列"哎呀"commit。
重要:和 reset 一样,amend 也会重写历史。只在未 push 的 commit 上使用它。如果你对已 push 的 commit 做了 amend 然后 force-push,任何已 pull 原始 commit 的人都会遇到问题。
用 git revert 撤销已 Push 的 Commit
一旦 commit 被 push 到共享 remote,你不应该重写历史。其他开发者可能已经基于该 commit 开展了工作。改用 git revert:
git revert HEAD
这会创建一个新 commit,执行与目标 commit 完全相反的操作。如果原始 commit 添加了一行,revert commit 就删除它。如果它删除了一个文件,revert commit 就恢复它。原始 commit 保留在历史中,revert 添加在其上面。
使用时机:任何时候你需要撤销一个已经 push 的 commit。它是安全的,因为它是在历史中添加而不是重写。
Revert 较早的 commit
你可以 revert 任何 commit,不仅仅是最近的:
git revert a1b2c3d
Git 将尝试创建一个 revert commit,只撤销该特定 commit 的更改。如果这些更改与后续工作重叠,你可能会遇到需要手动解决的冲突。
Revert 多个 commit
要 revert 一个 commit 范围:
git revert HEAD~3..HEAD
这会为范围内的每个 commit 创建单独的 revert commit。如果你希望只有一个 revert commit,添加 --no-commit 标志并手动 commit:
git revert --no-commit HEAD~3..HEAD
git commit -m "revert last 3 commits"
选择正确的方法
| 场景 | 方法 | 你的更改会怎样 |
|---|---|---|
| Commit 太早,想重新 staging | git reset --soft HEAD~1 |
更改保留在 staging 中 |
| Commit 太早,想重新修改 | git reset --mixed HEAD~1 |
更改保留在工作目录中 |
| Commit 了想丢弃的内容 | git reset --hard HEAD~1 |
更改被删除 |
| 需要修复 commit 消息或添加文件 | git commit --amend |
前一个 commit 被替换 |
| 需要安全地撤销已 push 的 commit | git revert HEAD |
新 commit 撤销更改 |
从错误中恢复
即使你使用了 git reset --hard 并意识到不该这样做,通常也有恢复的办法。Git 保存了 HEAD 所指向位置的日志,称为 reflog:
git reflog
这会显示最近 HEAD 位置的列表。你可以找到你以为丢失的 commit 并 reset 回去:
git reset --hard HEAD@{2}
Reflog 是你的安全网。条目默认保留 90 天,所以你有充足的时间从错误中恢复。
在 GitSquid 中撤销 Commit
GitSquid 简化了最常见的撤销操作。你可以在 commit 后立即按 Cmd+Z(Windows 和 Linux 上为 Ctrl+Z)来撤销最后一个 commit,保持你的更改在 staging 中,准备重新修改。对于更具体的操作,右键点击图中的任意 commit 可以通过上下文菜单访问 reset 和 revert 选项,这样你可以精确定位想要撤销的 commit,无需记忆各种标志。
撤销 Git commit 不必让人紧张。理解 reset、amend 和 revert 之间的区别,知道哪个适合你的情况,你就能自信地修复错误。最重要的区别很简单:如果是本地的,用 reset 或 amend。如果已 push,用 revert。