前端知库

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 就够了吗?源码回滚后还要重新构建和发布,参见《静态网站发布失败时怎样安全回滚?》。