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。这两个命令不改任何东西,但能让你知道自己在哪、要回哪。慌乱的时候少打一个字母,代价可能是一个下午。