18 — reset / restore / switch(重置三兄弟:轻 / 中 / 重)# 18 — reset / restore / switch(重置三兄弟:轻 / 中 / 重)
摘要:本文系统讲解 Git 中三个易混淆的撤销命令——reset、restore 和 switch。通过“书桌/作业篮/档案柜”三棵树模型,清晰区分各命令的作用范围:reset 用于回退提交历史(分 soft、mixed、hard 三档),restore 用于恢复文件内容,switch 用于切换分支。文章包含详细对比表格、决策流程图、安全等级评估及多个实战场景,帮助读者建立安全的 Git 操作习惯。
reset 三种模式对比
三区(书桌 / 作业篮 / 档案柜)详解
写在前面:这一章要解决什么
学完后你应该能:
- 一眼分清 reset、restore、switch 各自干什么——再也不会混
- 看到别人写
git reset --hard时知道这有多危险 - 想撤销某步操作时,知道该用哪个命令、加什么参数
- 说出 “三棵树”(书桌 / 作业篮 / 档案柜)各自的含义,以及每条命令动了哪棵
读者设定:大一同学,刚接触命令行,零项目经验。你只需要会add和commit,剩下的这章带你走。
1. 定位
1.1 一句话先记住
- reset= 往回拨指针(轻 / 中 / 重,看参数)
- restore= 把拿出来的东西放回去
- switch= 换频道(切到别的分支)
1.2 搞不清这三兄弟会怎样
- 把
reset --hard当成 “撤销提交” 的一般操作 → 工作区所有未提交的改动一瞬间消失,找都找不回来 - 把
restore和reset混用 → 该放回文件的地方却动了提交历史 - 把
switch和reset混用 → 该换分支的时候却把当前分支指针拨飞了 - 网上搜 “git 撤销” 出来一堆
checkout的老教程,越看越晕
1.3 和你已经会的事对比
你已经会git add(把书桌上的文件放进作业篮)和git commit(把作业篮里的文件归档到档案柜)。
现在要学的三个命令,本质上就是在问:
- “我想往回拨档案柜的指针” →reset
- “我想把文件放回原来的样子” →restore
- “我想换一个分支(频道)” →switch
以前这三个活儿都让checkout一个命令干,所以新手经常搞混。Git 2.23 之后把它们拆成了三个命令,各管各的,清楚多了。
2. 本质
三棵树模型(复习)
Git 里有三个 “地方” 存着你的文件状态,我们用生活场景来记:
| 术语 | 白话 | 类比 |
|---|---|---|
| 工作区(worktree) | 你正在编辑的文件夹 | 书桌——你眼前摊开的作业 |
| 暂存区(index / staging area) | 下一次提交要收录的内容 | 作业篮——准备交但还没交的作业 |
| HEAD / 分支 | 最近一次提交指向的快照 | 档案柜——已经归档的作业 |
它们之间的关系:
书桌 ──(git add)──→ 作业篮 ──(git commit)──→ 档案柜 ←─(git restore)── ←─(git reset)──────git add:书桌 → 作业篮git commit:作业篮 → 档案柜git restore:从作业篮或档案柜把东西放到书桌上(恢复文件内容)git reset:把档案柜的指针往回拨(移动分支指向哪个提交)
三兄弟的比喻
想象你在学校犯了点小错,老师有三种处理方式:
| 命令 | 比喻 | 动了什么 | 危险程度 |
|---|---|---|---|
git reset --soft | 轻轻提醒——“下次注意”,不罚你 | 只动 HEAD(档案柜的标签) | 安全 |
git reset --mixed | 警告——记录扣掉,你要重做 | 动 HEAD + 暂存区(档案柜标签 + 作业篮) | 中等 |
git reset --hard | 重罚——一切清零,全部重来 | 动 HEAD + 暂存区 + 工作区(三棵树全动) | 核武器级别! |
git restore | 把拿出来的东西放回去 | 恢复工作区或暂存区的文件内容 | 安全(不动提交) |
git switch | 换频道——换到别的节目 | 只动 HEAD(换到另一个分支) | 安全 |
3. 建议学习顺序
- 先理解三棵树(书桌/作业篮/档案柜)→ 知道每个命令动了哪棵
- 先学switch(最安全,只换频道)
- 再学restore(恢复文件,不动历史)
- 最后学reset三档(从轻到重,最危险放最后)
- 看对照表,把全章串起来
- 做小实验,亲手验证
4. 动手准备
mkdirgit-reset-lab&&cdgit-reset-labgitinitgitconfig user.name “Ada Example”gitconfig user.email “ada@example.com”白话翻译:创建一个实验仓库,设好身份。这个仓库随时可以删,放心折腾。
git--versiongit version 2.43.0白话翻译:确认 Git 版本。本书所有示例基于 2.43.0,你的版本只要 >= 2.23(有 switch/restore)就行。
先造点提交记录用来做实验:
echo"第一版">file.txtgitaddfile.txtgitcommit-m“v1: 第一版”echo"第二版">>file.txtgitaddfile.txtgitcommit-m“v2: 加了第二版”echo"第三版">>file.txtgitaddfile.txtgitcommit-m“v3: 加了第三版”白话翻译:造了三个提交,就像把三份作业依次放进档案柜。现在file.txt里有三行文字。
5. 跟着做
5.1 switch:换频道
gitswitch-cfeatureSwitched to a new branch 'feature'白话翻译:创建并切到feature分支——就像换了一个频道,但书桌上的东西没变。
gitbranch* feature main白话翻译:星号在feature旁边,说明你当前在这个分支。
gitswitch mainSwitched to branch 'main'白话翻译:换回main频道。书桌上的文件会跟着main分支的状态变。
关键点:switch只动 HEAD(换频道),不会动书桌上还未提交的改动。如果有未提交的改动和目标分支冲突,Git 会提示你先处理。
5.2 restore:把拿出来的东西放回去
先搞点改动:
echo“随手写的”>>file.txt白话翻译:在书桌上随便加了点东西,还没 add。
gitstatusOn branch main Changes not staged for commit: (use "git add <file>..." to update what will commit) (use "git restore <file>..." to discard changes in working directory) modified: file.txt白话翻译:Git 说 file.txt 在书桌上被改了,还没放进作业篮。注意 Git 已经在提示你用restore了!
恢复工作区(书桌上的改动不要了):
gitrestore file.txt白话翻译:把 file.txt 恢复成档案柜里最新提交的样子。书桌上那行 “随手写的” 没了。
catfile.txt第一版 第二版 第三版白话翻译:确认 “随手写的” 这一行确实消失了,文件回到了最近一次提交的状态。
现在试另一种情况:已经 add 了,想从作业篮里拿出来
echo“放进去又后悔的”>>file.txtgitaddfile.txtgitstatusOn branch main Changes to be committed: modified: file.txt白话翻译:改了文件并且 add 了,改动在作业篮里。你后悔了,想把作业篮里的改动退回书桌。
gitrestore--stagedfile.txt白话翻译:--staged的意思是 “我操作的对象是作业篮”。把 file.txt 从作业篮里拿出来,放回书桌状态,但书桌上的文件内容不变。
gitstatusOn branch main Changes not staged for commit: (use "git add <file>..." to update what will commit) (use "git restore <file>..." to discard changes in working directory) modified: file.txt白话翻译:改动从作业篮回到了书桌。如果连书桌上的改动也不想要了,再执行一次git restore file.txt就行。
restore 小结:
git restore 文件→ 书桌上的改动不要了(恢复工作区)git restore --staged 文件→ 作业篮里的改动拿出来(取消暂存),书桌上的内容不变
它永远不会动 HEAD / 分支指针,只管文件内容。
5.3 reset --soft:轻轻提醒
先看看当前的提交历史:
gitlog--onelinea3c1d2e v3: 加了第三版 b4e5f6a v2: 加了第二版 c7d8e9b v1: 第一版白话翻译:三个提交,最新的叫 v3。
gitreset--softHEAD~1白话翻译:把档案柜的指针往回拨一个提交(HEAD~1表示 “上一个提交”)。--soft= 轻轻提醒,只动档案柜的标签。
gitlog--onelineb4e5f6a v2: 加了第二版 c7d8e9b v1: 第一版白话翻译:v3 不见了!档案柜的标签指到了 v2。
gitstatusOn branch main Changes to be committed: modified: file.txt白话翻译:但改动并没有丢!v3 的内容还在作业篮里,等着你重新提交。
相当于:老师说了 “这次不算,你重新交”,但你的作业还在篮子里,不用重写。
如果你后悔了,想恢复 v3:
gitcommit-m"v3: 加了第三版(重新提交)"白话翻译:把作业篮里的东西重新提交就行了,什么都没丢。
5.4 reset --mixed(默认):警告
先把历史搞回三个提交:
echo"第三版">>file.txtgitaddfile.txtgitcommit-m"v3: 加了第三版"现在来试--mixed(这是 reset 的默认行为,不写也行):
gitreset HEAD~1白话翻译:等同于git reset --mixed HEAD~1。把档案柜标签往回拨一个,同时清空作业篮。
gitlog--onelineb4e5f6a v2: 加了第二版 c7d8e9b v1: 第一版白话翻译:v3 又从历史里消失了。
gitstatusOn branch main Changes not staged for commit: (use "git add <file>..." to update what will commit) modified: file.txt白话翻译:注意和--soft的区别!改动不在作业篮里了,而是回到了书桌上。你需要重新add才能提交。
相当于:老师把你的作业从篮子里拿出来扔回书桌,“重新抄一遍再交”。
5.5 reset --hard:重罚(核武器!)
先把历史搞回三个提交:
gitaddfile.txtgitcommit-m“v3: 加了第三版”echo"还没提交的新东西">>file.txt白话翻译:提交了 v3,又在书桌上写了新内容但没 add。
gitreset--hardHEAD~1白话翻译:核武器发射!档案柜标签往回拨 + 作业篮清空 + 书桌上所有未提交的改动全部消灭。
gitlog--onelineb4e5f6a v2: 加了第二版 c7d8e9b v1: 第一版gitstatusOn branch main nothing to commit, working tree clean白话翻译:干干净净,就像 v3 从来不存在,书桌上那行 “还没提交的新东西” 也消失了。找不回来了(除非你用 reflog,后面会说)。
相当于:老师把你作业撕了、篮子倒了、桌上也擦干净了。一切归零。
永远不要对已经 push 到远程的提交使用reset --hard!你的队友已经基于那个提交工作了,你把历史改了,他们就乱套了。
6. 命令分组
换频道组(只动 HEAD)
| 命令 | 白话 | 动了什么 |
|---|---|---|
git switch 分支名 | 换到指定分支 | HEAD 指向新分支 |
git switch -c 新分支名 | 创建新分支并切过去 | 先创建分支,再动 HEAD |
恢复内容组(动文件,不动 HEAD)
| 命令 | 白话 | 动了什么 |
|---|---|---|
git restore 文件 | 书桌上的改动不要了 | 工作区文件恢复成暂存区的样子 |
git restore --staged 文件 | 作业篮里的改动拿出来 | 暂存区恢复成 HEAD 的样子,工作区不变 |
git restore --staged --worktree 文件 | 两个都恢复 | 暂存区和工作区都恢复成 HEAD 的样子 |
拨指针组(动 HEAD,可能连带更多)
| 命令 | 白话 | 动了什么 |
|---|---|---|
git reset --soft 目标 | 轻轻提醒 | 只动 HEAD |
git reset --mixed 目标 | 警告(默认) | 动 HEAD + 暂存区 |
git reset --hard 目标 | 重罚(核武器) | 动 HEAD + 暂存区 + 工作区 |
7. 对照表(这是全章最重要的部分)
哪条命令动了哪棵树
| 命令 | 工作区(书桌) | 暂存区(作业篮) | HEAD/分支(档案柜标签) |
|---|---|---|---|
git switch 分支 | 跟着变* | — | 移动 |
git restore 文件 | 恢复 | — | — |
git restore --staged 文件 | — | 恢复 | — |
git restore --staged --worktree 文件 | 恢复 | 恢复 | — |
git reset --soft 目标 | — | — | 移动 |
git reset --mixed 目标 | — | 恢复 | 移动 |
git reset --hard 目标 | 恢复 | 恢复 | 移动 |
* switch 换分支时,工作区的文件会变成目标分支的样子。如果有未提交的改动和目标分支冲突,Git 会拒绝切换。
决策流程:“我想撤销什么?”
你想撤销什么? │ ├─ 想撤销书桌上的修改(还没 add) │ └→ git restore 文件 │ ├─ 想撤销 add 操作(文件还在书桌上,但不想放进作业篮了) │ └→ git restore --staged 文件 │ ├─ 想撤销最近一次提交(内容还要,只是提交本身想撤) │ └→ git reset --soft HEAD~1 │ ├─ 想撤销最近一次提交 + 取消暂存(内容回书桌,重新整理再交) │ └→ git reset HEAD~1 │ ├─ 想把所有改动全部清空,回到某个历史状态(确定不要了!) │ └→ git reset --hard 目标 │ └─ 想换到另一个分支 └→ git switch 分支名reset 三档直观对比
reset --soft: HEAD 移动 ──→ 暂存区不动 ──→ 工作区不动 (档案柜标签拨了,篮子和书桌原封不动) reset --mixed: HEAD 移动 ──→ 暂存区重置 ──→ 工作区不动 (档案柜标签拨了,篮子清空,书桌上的东西还在但需要重新 add) reset --hard: HEAD 移动 ──→ 暂存区重置 ──→ 工作区重置 (三个全动!标签拨了、篮子倒了、桌子擦了)安全等级一览
| 安全程度 | 命令 | 能否轻松恢复 |
|---|---|---|
| 最安全 | switch | 天然安全,不会丢东西 |
| 安全 | restore | 天然安全,只管文件内容,不动历史 |
| 安全 | reset --soft | 随时重新 commit 就回来 |
| 中等 | reset --mixed | 改动回到书桌,重新 add 就行 |
| 危险! | reset --hard | 需要reflog才可能抢救 |
8. 安全习惯
- 按回车之前先
git status—— 看看当前状态,想清楚你要动的是书桌、作业篮还是档案柜 - 永远不要对已 push 的提交使用
reset --hard—— 你改了别人的历史,队友的仓库会乱套 - 拿不准就用
--soft—— 最安全,什么都没丢,随时可以重来 - 书桌上永远只放当前任务相关的东西—— 乱糟糟的书桌容易误操作
- 小步提交—— 每次只提交一个逻辑改动,这样
reset回退时粒度更细,不会一刀切太多 - 记住
reflog这根救命稻草——万一reset --hard后悔了,git reflog能看到最近的 HEAD 移动记录,有可能救回来 - 在可丢弃的仓库里练习——本章的实验仓库随时可以删掉重来
9. 真实场景
场景一:提交信息写错了
gitcommit-m"fix bux"# 拼写错误!解法:还没 push,用--soft撤回,重新写提交信息。
gitreset--softHEAD~1gitcommit-m"fix bug"白话翻译:轻轻拨回一个提交,改动还在作业篮里,重新提交就行了,什么都没丢。
场景二:add 了一个不该提交的文件
gitadd.# 哎呀,debug.log 也被 add 了解法:从作业篮里拿出来。
gitrestore--stageddebug.log白话翻译:只把 debug.log 从作业篮里拿出来,回到书桌上。其他已经在篮子里的文件不受影响。
场景三:提交了两个功能,想拆成两次提交
gitlog--onelinea1b2c3d 加了功能A和功能B解法:
gitreset HEAD~1# 现在所有改动回到书桌上gitadd功能A的文件gitcommit-m“加功能A”gitadd功能B的文件gitcommit-m“加功能B”白话翻译:用默认的--mixed把提交撤回,改动回到书桌,然后分两次 add + commit。
场景四:实验搞砸了,全部重来
gitreset--hardHEAD~3白话翻译:核武器!最近三个提交 + 所有未提交的改动全部消灭。只有在确定不要这些内容时才用。
如果后悔了:
gitrefloga1b2c3d HEAD@{0}: reset: moving to HEAD~3 d4e5f6a HEAD@{1}: commit: v3: 加了第三版 ...白话翻译:reflog 记录了 HEAD 的每一次移动。找到你想回去的那个编号,然后:
gitreset--hardd4e5f6a白话翻译:用 reflog 里的哈希值恢复回去。但这是最后一根稻草,别指望每次都能救回来。
场景五:切分支做热修
gitswitch-chotfix# 修 bug ...gitadd.gitcommit-m“修复紧急 bug”gitswitch maingitmerge hotfix白话翻译:开一个热修分支,修完再合回主分支。switch只管换频道,安全操作。
10. 进阶补充
为什么 restore/switch 替代了 checkout?
在 Git 2.23 之前,git checkout一个命令干了太多事:
git checkout 分支→ 切分支(现在是git switch)git checkout -- 文件→ 恢复工作区(现在是git restore)git checkout HEAD -- 文件→ 恢复暂存区 + 工作区(现在是git restore --staged --worktree)
一个命令干三件事,参数还容易搞混。拆开之后,每个命令只干一件事,不容易出错。
老命令现在还能用(Git 不会删老功能),但新命令更清晰,推荐优先使用。
HEAD~1是什么意思?
HEAD= 当前提交HEAD~1= 当前提交的父提交(往前一个)HEAD~2= 往前两个HEAD~3= 往前三个
你也可以用提交的哈希值代替,比如git reset --soft a1b2c3d。
checkout和新命令的对应关系
| 旧命令 | 新命令 | 说明 |
|---|---|---|
git checkout 分支 | git switch 分支 | 切分支 |
git checkout -b 新分支 | git switch -c 新分支 | 创建并切到新分支 |
git checkout -- 文件 | git restore 文件 | 恢复工作区 |
git checkout HEAD -- 文件 | git restore --staged --worktree 文件 | 恢复暂存区和工作区 |
reflog:后悔药
git reflog记录了 HEAD 的每一次移动,默认保留 90 天。即使你reset --hard了,只要 90 天内,大概率能找回来:
gitreflog# 找到你想回去的那个条目gitreset--hard那个哈希但 reflog 不是万能的:
- 只记录 HEAD 的移动,不记录单个文件的修改
- 超过 90 天会被自动清理
git gc(垃圾回收)后可能就真没了
所以,预防永远比补救重要。
11. 小实验
在前面创建的git-reset-lab仓库里完成以下实验。每一步先猜结果,再运行命令验证。
实验 1:验证 restore 不动 HEAD
- 记录当前 HEAD:
git log --oneline -1 echo “测试” >> file.txt && git add file.txtgit restore --staged file.txt- 再看 HEAD:
git log --oneline -1 - 验证:HEAD 哈希没变——restore 确实不动提交历史
实验 2:对比 soft 和 mixed
- 造一个新提交:
echo "新内容" >> file.txt && git add file.txt && git commit -m "测试提交" git reset --soft HEAD~1git status→ 改动应该在暂存区git reset HEAD~1(即 --mixed)git status→ 改动应该回到工作区(不在暂存区)- 对比两次
git status的区别
实验 3:体验 hard 的 “核爆”
echo "会消失的东西" >> file.txt(不 add)echo "也会消失的" >> another.txt && git add another.txtgit reset --hard HEAD- 检查
file.txt和another.txt→ 都没了 - 尝试
git reflog看看有没有后悔药
实验 4:switch 的安全保护
echo "未提交" >> file.txtgit switch -c another-branch- 如果成功了(没冲突),说明 Git 允许带着未提交的改动换分支
- 回去改出冲突:在另一个分支上提交对 file.txt 的不同修改
- 再在有未提交改动时
git switch main→ 会报错 - 体会:switch 在有冲突时会拒绝切换,保护你的改动
12. 常见问题 FAQ
问 1:reset 和 restore 到底什么区别?
核心区别:reset 动的是指针(分支指向哪个提交),restore 动的是文件内容。
reset是把 “档案柜的标签” 往回拨,连带可能影响作业篮和书桌restore是把 “文件的内容” 恢复成某个版本的样子,不动标签
问 2:reset --hard 之后真的找不回来了吗?
大概率能救——用git reflog找到之前的 HEAD 哈希,再用git reset --hard 那个哈希恢复。但如果已经过了 90 天或者执行过git gc,那就真没了。所以别把它当常规操作。
问 3:我应该用checkout还是新命令?
用新命令。switch和restore拆开之后语义更清楚,不容易搞混。老命令还能用,但新项目建议从头就用新命令。
问 4:git reset HEAD不加--soft也不加--hard会怎样?
默认是--mixed,也就是动 HEAD + 暂存区,不动工作区。等同于git reset --mixed HEAD。
问 5:restore 可以恢复到任意历史版本吗?
可以。git restore --source=某个哈希 文件能把文件恢复到任意提交时的样子。不写--source默认从暂存区恢复,写了--staged则从 HEAD 恢复。
问 6:switch 和 reset 都动 HEAD,区别在哪?
switch是把 HEAD 指向另一个分支,不改变当前分支指向的提交reset是把当前分支本身的指向往回拨,改变了历史
打个比方:switch 是换频道(别的频道还在),reset 是把当前频道的进度条往回拉。
问 7:已经 push 到远程的提交被 reset 了怎么办?
如果你 reset 之后又 push,需要git push --force。这会覆盖远程的历史,所有基于旧历史工作的队友都会出问题。
正确做法:对已 push 的提交,用git revert(下一章会讲)来新建一个 “反向提交”,而不是改写历史。
问 8:实验里的哈希值和书上不一样正常吗?
正常。哈希值是根据你的文件内容和提交时间算出来的,每台机器都不一样。关键看命令结构和git status的输出模式是否一致。
13. 总结
三个命令,三个职责
| 命令 | 一句话 | 核心记忆 |
|---|---|---|
| switch | 换频道 | 只动 HEAD,切分支 |
| restore | 把东西放回去 | 只动文件内容,不动历史 |
| reset | 往回拨指针 | 动 HEAD,根据参数可能连带动暂存区和工作区 |
reset 三档
| 参数 | 比喻 | 动了什么 | 口诀 |
|---|---|---|---|
--soft | 轻轻提醒 | HEAD | 提交撤了,篮子还在 |
--mixed | 警告 | HEAD + 暂存区 | 提交撤了,篮子清了,桌子还在 |
--hard | 重罚(核武器) | HEAD + 暂存区 + 工作区 | 三个全动,想清楚再按回车 |
最重要的安全规则
- 不确定就用
--soft - 已 push 的提交不要 reset
- 按回车之前先
git status - 核武器(
--hard)只在可丢弃的仓库里用
参考资料与延伸阅读
以Git 2.43.0验证。演示身份:Ada Example <ada@example.com>。
- Pro Git 中文
- git-switch 文档
- git-restore 文档
- git-reset 文档
- 图示署名:
assets/diagrams/ATTRIBUTION.md