Git 工作区、暂存区和版本库的区别
Git 初学者如何撤销工作区修改并保留暂存内容
一个文件同时出现在 git status 的“Changes to be committed”和“Changes not staged for commit”两栏,是理解 Git 分区模型最直观的入口。它说明磁盘上那份文件、暂存区里那份内容、最新提交里那份快照,此刻是三个互不相同的版本。把这三个版本各自归属哪个区域搞清楚,大多数“改乱了不知道怎么退”的问题会自行消解。
三个区域各自负责什么
工作区是编辑器直接读写的那份文件,Git 对它基本只是旁观:除你显式执行命令,Git 不会替它留副本。暂存区(index)是一份独立于工作区的索引,记录下一次提交将要包含的内容。版本库则是提交历史,每次提交在其中固化一个不可变快照。三者的关系不是“文件加历史”两层,而是“磁盘内容—待提交内容—已提交内容”三层,因此允许同时存在三个版本。
由此可以推出两条容易被误解的语义:git add 并非“保存文件”,而是把工作区当前内容写入暂存区;commit 也不是“保存暂存区”,而是把暂存区当前内容固化为一次提交。工作区后续的编辑不会自动流入暂存区,暂存区的内容也不会因为工作区变化而回退。
差异比较的方向决定一切
git diff 默认比较工作区与暂存区,输出的正是尚未暂存的那部分改动;git diff --cached 比较暂存区与最新提交,输出的是准备提交的内容。记住 diff 的两端默认是“工作区对暂存区”,加上 --cached 相当于把左端换成暂存区、右端换成 HEAD,方向就不会弄反。前者对应准备丢弃的内容,后者对应准备保留的内容,核对时混淆这两者是最常见的失误来源。
命令的差异在于“源”
git restore -- 文件 默认把工作区恢复到暂存区版本,不加 --staged 就不触碰暂存区;git restore --staged -- 文件 只改暂存区,工作区保持不动;两者同时加上,才会把两边都退回 HEAD。旧版本中的 git checkout -- 文件 与前者等效,同样只作用于工作区。
真正危险的是源为 HEAD 的命令。git checkout HEAD -- 文件 和 git reset --hard 之所以会连带清掉暂存内容,原因不在作用范围大小,而在于它们把恢复源设成了最新提交,而非暂存区。暂存区里那些精心挑过的改动会被一并覆盖,且 Git 不提供撤销。
判断某个命令是否安全,只需问两个问题:它的源是哪一份内容,它的目标是哪个区域。源是暂存区的命令只会让工作区回退;源是 HEAD 的命令会让暂存区一起失守。在执行任何以 HEAD 为源的恢复之前,先用 git diff --output 导出补丁或直接复制文件到项目目录外,这一步的成本远低于重做一遍挑选。



参与讨论
那 git stash 存的算哪个区?
reset –hard 丢过一次暂存,长记性了
原来三个版本可以同时存在,这下理顺了