2.7 撤销与恢复(git restore / git reset)


2.7 撤销与恢复(git restore / git reset)

本节摘要:改错了是 Git 日常的常态,关键是知道该用哪个命令救。本节讲清两个主角的分工:git restore 管文件级恢复(丢弃工作区修改、取消暂存),git reset 管提交级回退(移动 HEAD 指针,分 soft/mixed/hard 三档)。最后给出一张"什么场景用什么命令"的决策表,并提醒 hard 模式的危险。

学习目标

阅读完本节,你应当能够:

  1. 用 git restore 丢弃工作区修改、取消暂存、从特定提交恢复文件。
  2. 解释 git reset 的三个模式(soft/mixed/hard)各自影响哪些区。
  3. 根据场景选择 restore 或 reset,避开 hard 模式的危险区。
  4. 理解 restore 与 reset 在"是否动 HEAD、是否动历史"上的本质区别。

一、问题与直觉

Git 的价值一半在"记录",另一半在"反悔"。现实开发里,反悔是高频动作:

  • 改了半天发现方向错了,想回到改之前。
  • 把不该 add 的文件 add 了,想撤下暂存。
  • 提交完才发现提交错了东西,想撤回这次提交。
  • 一不留神 reset 过头,把不该丢的提交弄丢了。

没有撤销能力,版本控制就只是"保险柜"——只能存,取不出来。Git 在"反悔"上提供了成套工具,但正因为命令太多,很多人遇到问题第一反应是"完蛋了",而不是"该用哪条"。

本节的目标很实在:把"撤销"这件事按场景拆清楚,给你一张"出错地图"——每种错误对应哪条命令、会动哪个区、有没有风险。读完之后,遇到撤销场景你不该再慌,而是像查菜单一样找到对应那一条。

先说结论性的分工,后面展开:

  • 动文件不动历史git restore
  • 动提交指针git reset

两条线分别管"文件层的反悔"和"历史层的反悔",大部分撤销需求都落在其中一条上。

这里值得先打破一个神话:很多人以为"Git 的撤销很危险,一不小心就删库"。真实情况是,Git 的撤销绝大多数都是可逆的——文件内容在对象库里存着、提交对象在仓库里躺着,你删掉的往往只是"指向它们的指针",而指针有 reflog 可以找回。真正不可逆的场景其实很少(最典型的就是 reset --hard 丢弃未提交的工作区改动)。所以遇到反悔需求,第一反应不该是"完了",而是"这条命令会动哪个区、丢的是指针还是内容"。带着这个框架读下面的原理,你会觉得清晰很多。

二、核心原理

restore:文件级恢复

git restore 是 Git 2.23 起专为"恢复文件"设计的命令,意图明确,不碰 HEAD、不碰历史,只在工作区与暂存区之间搬运内容。

用法一:丢弃工作区的修改。

git restore 文件名 git restore . # 恢复当前目录全部文件

效果:把工作区里未暂存的修改丢掉,文件回到暂存区(或最近提交)里的版本。这条命令不可逆——未暂存的修改一旦被丢弃就找不回来了(除非你之前 stash 过,第 5 章讲)。

用法二:取消暂存。

git restore --staged 文件名

效果:把文件从暂存区撤下,回到"已修改未暂存"状态,工作区的修改保留。这是"add 错了"的标准解药。

用法三:从特定提交恢复文件。

git restore --source HEAD~1 文件名

效果:把文件恢复到指定提交(这里是上一个提交)时的内容。--source 指定来源提交,默认是 HEAD 或暂存区。

restore 的三条通路:默认从暂存区/HEAD 恢复工作区,--staged 反向撤下暂存,--source 从任意提交取内容。全部不碰历史。

reset:移动 HEAD 指针

git reset 是更强大的命令,它的核心动作是移动 HEAD 指针,让当前分支指向另一个提交,从而"回到过去"。根据模式不同,它还会顺带重置暂存区和工作区。

reset:移动 HEAD 指针

图 2-7 reset 三种模式的影响范围

图 2-7 reset 三种模式的影响范围

模式一:--soft(最温柔)

git reset --soft 提交

只移动 HEAD 指针,暂存区和工作区原样保留。原来"被跳过"的提交的改动全部留在暂存区,等你重新整理。适合"把最近几个提交合并成一个"的场景。

模式二:--mixed(默认)

git reset 提交 # 等价于 --mixed git reset --mixed 提交

移动 HEAD,并把暂存区重置为目标提交的状态,工作区保留。被跳过的提交的改动变成未暂存状态,需要重新 add。这是默认模式,也是"想撤销提交但保留改动"的常用选择。

模式三:--hard(最暴力)

git reset --hard 提交

移动 HEAD,暂存区和工作区全部重置为目标提交的状态。被跳过的提交的改动彻底丢弃。这是最危险的模式——工作区未提交的修改也会一起消失。

三个模式用一张表总结:

模式 移动 HEAD 重置暂存区 重置工作区 危险度
--soft
--mixed
--hard

一个直觉:reset 是"把时间轴往回拨"

soft/mixed/hard 的区别,就像"回拨时钟"的三种程度:soft 只把表针拨回去(其他不动);mixed 拨表针 + 把闹钟设置清零;hard 拨表针 + 清零闹钟 + 把房间也还原成当时的样子。参数越强,波及面越大,丢失风险越高。

一次完整的 reset 实操演示

假设你的历史是 A → B → C → D(HEAD 在 D)。分别体验三种回退:

# 回退到 B,保留 C、D 的改动在暂存区 git reset --soft B git status # C、D 的所有改动都在 "to be committed" # 回退到 B,C、D 的改动变成未暂存 git reset B # 默认 mixed git status # C、D 的改动都在 "Changes not staged" # 回退到 B,彻底丢弃 C、D(危险) git reset --hard B git log --oneline # 只剩 A、B

留意第三种的后果:C、D 两个提交从分支上"消失"了,但如果它们的哈希你还记得,理论上能通过 reflog 找回(第 5 章)。这也是 reset 的隐藏后门——它丢的是"指针指向",不是"对象本身"。

一次完整的 restore 实操演示

对应地,把文件级的反悔也走一遍:

echo bug > app.js git add app.js # 误暂存了 git restore --staged app.js # 撤下暂存,修改保留 git status # app.js 回到未暂存状态 git restore app.js # 丢弃工作区修改 git status # 工作区干净

注意第二条 restore 之后文件内容并没有变——restore --staged 只动暂存区。真正把内容改回去的是第三条 restore(从暂存区/HEAD 恢复)。两条命令一前一后,先把"登记"撤了,再把"内容"还原。

三、工程实践要点

出错场景决策表

场景 命令 丢东西吗
丢弃工作区未暂存修改 git restore 文件
取消暂存(保留修改) git restore --staged 文件 不丢
把文件恢复到历史版本 git restore --source 提交 文件 可能丢
撤销最近提交但保留改动 git reset --soft HEAD~1 不丢
撤销提交且重置暂存区 git reset --mixed HEAD~1 不丢
彻底回退到某提交 git reset --hard 提交 丢(危险)

两条重要原则

原则一:先想清楚"我要反悔的是文件还是提交"。 文件层的反悔用 restore,提交层的反悔用 reset。混用是最常见的错误——比如用 reset --hard 只为了丢一个文件的修改,结果把提交也回退了。

原则二:reset --hard 前先确认。 敲之前问自己:这些被丢弃的改动我真的不要了吗?有没有没提交的工作区改动会一起被清掉?如果有一丝犹豫,先 git stashgit commit 把现场保住,再 reset。

撤销操作的安全等级

把 Git 的撤销工具按"破坏性"排个序,心里有数哪个绝对安全、哪个要三思:

工具 安全等级 说明
git restore --staged 完全安全 只撤暂存登记,内容都在
git restore 文件 低风险 丢未暂存修改,但已提交的历史不受影响
git reset --soft 安全 改动全在暂存区,随时可重新提交
git reset --mixed 较安全 改动变未暂存,重新 add 即可
git reset --hard 高风险 未提交改动全部消失
git push --force 极高风险 改写远程历史,影响他人(第 4、5 章)

这张表的实用价值在于:绝大多数日常反悔需求,都能落在安全区解决。真正危险的命令就那么两三个,记住它们的名字、每次用之前多问一句,你就规避了 Git 里绝大部分事故。

养成"动手前留后路"的习惯

有经验的 Git 用户有一个共同习惯:在执行任何有风险的操作前,先让工作区处于可恢复状态。具体做法:

  • 有未提交的改动想先保住 → git stash(第 5 章细讲)。
  • 想留个安全锚点 → 先 git commit 一个 WIP(工作进行中)提交,之后随便折腾。
  • 做历史改写前 → 先记下当前 HEAD 的哈希(git rev-parse HEAD),作为回退坐标。

这个习惯的价值在于,它把"高风险操作"降级为"可逆操作"。reset --hard 之后再也不用祈祷运气——你有明确的坐标可以回去。这比任何"小心操作"的叮嘱都实在。换个说法:Git 事故里九成不是工具坏了,而是没给自己留退路。

关于 git reset 文件模式

git reset 文件名 等价于 git restore --staged 文件名——它把指定文件从暂存区撤下,不动工作区。这是老命令的遗留用法,Git 2.23 后推荐用 restore 表达更清晰。了解它的存在即可,不必刻意使用。

⚠️ 常见坑:git reset --hard 之后才想起工作区有没保存的修改——这些修改已经没了。所以硬重置的铁律是:先 status 看有没有未提交改动,先 stash 或 commit 保存,再动手。

💡 关键直觉:restore 是"擦掉草稿纸上的字",reset 是"把整个日历撕回某一页"。擦字只影响当前这页,撕日历会连带后面所有页。想清楚你要擦字还是撕日历,再选命令。

常见问题与 FAQ

"restore 和 checkout 到底啥区别?" checkout 是老命令,一身兼数职(切分支、恢复文件),容易让人混淆。Git 2.23 起把"恢复文件"拆给了 restore、把"切分支"拆给了 switch。现代实践:恢复文件用 restore,切分支用 switch。

"reset --hard 误操作了,能救吗?" 分情况。如果被丢的提交有哈希记得住,用 git reflog 找回来(第 5 章详细讲);如果只是未提交的工作区改动,基本没救了。所以再强调一遍:hard 前先保存现场。

"撤销提交用 reset 还是 revert?" reset 是"本地回退"(移动指针),revert 是"新增一个反向提交"(不动历史)。如果提交已推送远程,用 revert 安全;只在本地,reset 更干净。第 5 章会展开 revert。

"我想回到某个历史版本,用哪个?" 看你想"看"还是想"回"。只查看历史状态用 git checkout 提交 或 restore --source;真正把分支回退到那个提交用 reset。前者不改变分支位置,后者会。

"git restore 会不会把暂存区也清了?" 默认不会——restore 文件只动工作区;restore --staged 只动暂存区;restore --source 会同时更新工作区和暂存区。按你选的选项决定波及范围。

"撤销命令记不住怎么办?" 背一张最小速查表就够:想丢工作区改动用 restore,想撤暂存用 restore --staged,想回退提交用 reset,彻底回退且不怕丢用 reset --hard。四个场景,两条命令,其余都是变体。把这四条刻在脑门上,遇到 90% 的撤销需求都不会慌。剩下的 10% 属于 reflog 和 revert,那是第 5 章的事。

"restore 能恢复被删除的文件吗?" 能。如果文件在最近一次提交里存在、只是工作区里被删了,git restore 文件名 能把它从暂存区/提交里恢复回来。这正是"文件丢失"场景的标准救法——先别慌,Git 往往比你想象的更"记得"东西。

本节速览

  • restore 管文件:丢弃工作区修改、取消暂存、从历史提交恢复,不碰历史。
  • reset 管提交:移动 HEAD 指针,soft/mixed/hard 三档对应不同波及范围。
  • 三档语义:soft 保留全部、mixed 重置暂存区、hard 连工作区一起重置。
  • 决策原则:先想清楚反悔的是"文件"还是"提交",再选命令。
  • hard 是禁区入口:用前必看 status、必保存现场。
  • 可逆性认知:Git 撤销大多可逆,丢的常是指针而非内容,reflog 能救命。
  • 后路习惯:风险操作前先 stash、留锚点提交、记 HEAD 哈希。
  • 老命令对照:restore 替代 checkout 的文件恢复职能,switch 接管分支切换。
  • revert vs reset:推送过的提交用 revert,本地未推送用 reset。
  • 最小速查:restore 丢工作区、restore --staged 撤暂存、reset 回退、reset --hard 彻底回退。

下一章进入 Git 的灵魂——分支管理:分支的本质、增删切换、合并与冲突,Rebase 变基。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U