Git 撤销:改错一个字、提错一次提交、删错一个分支
Git 的撤销命令之所以吓人,是因为它们长得像,后果差得远。reset --hard 和 restore 就差了三个字母,一个只是把文件还原,另一个能把你半小时的活直接抹掉。
所以别背命令,先问自己一句:现在这个东西在哪一层?
四层结构,先对上号
- 工作区:你正在编辑的文件,还没被 Git 记住。
- 暂存区:
git add之后待提交的快照。 - 本地仓库:
git commit之后,已经写进历史。 - 远端:
git push之后,别人也能看到。
层数越深,撤销越麻烦,但基本都还能救。真正救不回来的只有一种情况:没提交、也没进过暂存区,然后被覆盖了。
只改了工作区,还没 add
最常见的场景:改到一半发现方向错了,想回到上次提交的样子。
git restore 文件名 # 丢弃这个文件的修改
git restore . # 丢弃所有未暂存的修改
restore 只动工作区,不碰暂存区和历史。注意它不可逆——被丢掉的内容不会进 reflog,因为 Git 根本没存过。
如果只是想看看改了什么,先别急着还原:
git diff # 工作区 vs 暂存区
git diff --stat # 只看改了哪些文件、多少行
已经 add,但还没 commit
东西进了暂存区,工作区那层反而空了。
git restore --staged 文件名 # 把它退回工作区,修改还在
这一步很多人会误用 git reset HEAD 文件名,效果其实一样,只是老写法。区别是 --staged 更明确:只撤暂存,不动内容。
要连内容一起丢,两步走:
git restore --staged 文件名
git restore 文件名
已经 commit,但没 push
这是最舒服的位置,因为历史还没被别人看见,可以随便改。
只改提交说明:
git commit --amend -m "新的说明"
漏了一个文件:
git add 忘了的文件
git commit --amend --no-edit
--no-edit 表示说明沿用上一次,不弹编辑器。
整个提交不要了,但改动想留着:
git reset --soft HEAD~1
--soft 只把 HEAD 往回挪一格,暂存区原封不动。相当于“我刚才没提交,但东西都还在,随时可以重新提交”。
提交不要了,改动也不想留:
git reset --hard HEAD~1
--hard会同时清掉工作区和暂存区。执行前先git status看一眼,确认没有别的东西混在里面。它不会问你要不要确认,敲回车就生效。
如果只是想撤掉某一个文件在这个提交里的改动,用 git restore --source=HEAD~1 文件名 更精准,不用整个提交回滚。
已经 push 了
到这一层就别再 reset 了。你本地把历史改短,远端还是老的,下次 push 要么被拒,要么得强推——强推会把别人的提交一起冲掉。
正确做法是再加一个提交,把之前的改动反向抵消:
git revert 提交哈希
它会生成一个新提交,内容正好是那次提交的逆操作。历史只增不减,谁都不会被打断。
撤销一个合并提交要加 -m 1,告诉 Git 保留哪一边作为主线:
git revert -m 1 合并提交哈希
删错分支了
先别慌,git branch -D 删的只是分支指针,提交对象还在,通常能捞回来。
git reflog
reflog 记录的是 HEAD 的移动轨迹,包括你刚才删分支前所在的位置。找到那一行,然后:
git branch 救回来的名字 提交哈希
reflog 默认保留 90 天,够用了。但如果分支上的提交从来没被任何分支或标签引用过,又过了 gc 周期,那就真的没了。
一张表收尾
| 你现在的情况 | 用哪个 | 会不会丢内容 |
|---|---|---|
| 工作区改乱了 | git restore 文件 | 会,且不可恢复 |
| add 错了文件 | git restore --staged 文件 | 不会 |
| 提交说明写错 | git commit --amend | 不会 |
| 想合并进上一个提交 | git commit --amend --no-edit | 不会 |
| 撤销提交,保留改动 | git reset --soft HEAD~1 | 不会 |
| 撤销提交,连改动一起扔 | git reset --hard HEAD~1 | 会 |
| 已经 push 了 | git revert 哈希 | 不会,新增一条记录 |
| 删错分支 | git reflog 找回 | 通常不会 |
最后一条经验:动手前先 git status 和 git log --oneline -5。这两个命令不改任何东西,但能让你知道自己在哪、要回哪。慌乱的时候少打一个字母,代价可能是一个下午。