git reset、revert 和 restore 有什么区别?
reset 移动当前分支指向的提交,并按 --soft、--mixed、--hard 决定是否同时重置暂存区和工作区;revert 新增一个反向提交来撤销旧提交,不改写历史;restore 只恢复文件内容,不移动分支。已推送到共享分支的提交应使用 revert。
参考答案
简答
git reset <提交>把当前分支移到指定提交。--soft保留暂存区和工作区,--mixed(默认)重置暂存区但保留工作区,--hard把三者都重置,未提交的修改会丢失。git revert <提交>生成一个新提交,内容是旧提交的反向修改。原提交仍在历史里,适合已经推送到共享分支的情况。git restore只处理文件:默认用暂存区恢复工作区,--staged用 HEAD 恢复暂存区,--source可以指定从哪个提交取文件。它不移动分支。- reset 改写的是本地分支指针,如果被移走的提交已经推送,再推送需要强制覆盖远端,会影响其他协作者。
三个区域
Git 的撤销命令围绕三个位置:HEAD 指向的提交、暂存区(index)和工作区。区分命令的关键是它改动了哪几个位置。
| 命令 | 移动分支 | 暂存区 | 工作区 |
|---|---|---|---|
reset --soft | 是 | 不变 | 不变 |
reset --mixed | 是 | 重置 | 不变 |
reset --hard | 是 | 重置 | 重置 |
revert | 新增提交 | 写入反向修改 | 写入反向修改 |
restore | 否 | 仅 --staged 时 | 默认 |
reset
在只有本地提交时,reset 适合整理提交:
bash
# 撤销最近一次提交,修改仍在暂存区,可以直接重新提交
git reset --soft HEAD~1
# 撤销最近一次提交和暂存,修改留在工作区
git reset HEAD~1
# 丢弃最近两次提交以及工作区的未提交修改
git reset --hard HEAD~2
--hard 丢掉的已提交内容通常还能通过 reflog 找回,做法见《误删分支或 reset --hard 之后怎样用 reflog 找回提交?》。从未提交过的工作区修改不在任何提交里,reflog 也找不回。
git reset --keep HEAD~1 也会移动分支并更新工作区,但会保留与这次移动无关的本地修改;如果有本地修改的文件也在被撤销的提交里,它会报错并停止,不会覆盖修改,比 --hard 安全。
reset、merge、rebase 等操作会把移动前的位置记录在 ORIG_HEAD。刚执行完一次 reset 想撤回时,git reset --hard ORIG_HEAD 可以直接回到原位。
revert
bash
git revert --no-edit HEAD
执行后历史多出一个 Revert "..." 提交,文件回到被撤销提交之前的内容。撤销合并提交时需要用 -m 1 指定以哪个父提交为主线。revert 不删除历史,所以可以照常推送,也可以再 revert 一次恢复。
restore
git restore 从 Git 2.23 起提供,用来分担 git checkout 中恢复文件的那部分功能:
bash
git restore src/app.ts # 放弃工作区修改,回到暂存区的内容
git restore --staged src/app.ts # 取消暂存,工作区修改保留
git restore --source=HEAD~3 src/app.ts # 取回三个提交之前的文件内容
被 git restore 覆盖的工作区修改没有提交过,无法恢复。
常见追问
- 已经推送的提交能不能 reset?技术上可以,但推送需要
--force-with-lease,其他人基于旧历史的提交会产生分歧。共享分支上用 revert。 git reset <文件>和git restore --staged <文件>效果相同吗?都会让该文件的暂存内容回到 HEAD,工作区不变。- 线上发布出问题时 revert 就够了吗?源码回滚后还要重新构建和发布,参见《静态网站发布失败时怎样安全回滚?》。