Git如何判断文件是已暂存(staged)还是已提交(committed)状态
首先纠正一个常见误解:Git 从来不会给 index(索引/暂存区)里的文件条目加「暂存」「已提交」的专属标记,git status 的判断完全基于多份基准数据的比对,没有单独的状态字段存这个属性。
先明确比对用到的三份基准数据:
- HEAD 提交树:当前分支最新一次已提交的所有文件快照哈希集合,代表已经进仓库的正式版本
- Index 文件:存储文件哈希、路径、创建/修改时间、文件大小等元数据的二进制文件,代表下一次提交的候选快照
- 工作区文件:磁盘上当前你能直接编辑的实际文件
已提交(无变更)状态的判定规则
一个文件被判定为已提交、无修改,不会出现在变更列表里,需要同时满足两个条件:
- Index 中存储的该文件哈希值,和 HEAD 提交树里同路径文件的哈希值完全一致
- Index 中存储的该文件修改时间、文件大小等元数据,和当前工作区同路径文件的元数据完全一致
元数据比对是Git做的性能优化:如果每次跑
git status都重新计算全量工作区文件的SHA1哈希,大仓库的执行速度会慢到没法用,只有元数据对不上的时候,Git才会重新读取文件内容算哈希,确认内容是不是真的发生了变更。
暂存状态的判定规则
只要 Index 中某文件的哈希值,和 HEAD 提交树里同路径文件的哈希值不一致,那么这两个哈希对应的内容差异,就属于已经暂存、会被下一次提交包含的内容,和当前工作区的文件状态无关。
这里要注意一个很容易混淆的场景:同一个文件可以同时存在暂存修改和未暂存修改。比如你改了a.txt之后执行git add a.txt,此时index里a.txt的哈希和HEAD不一样,这部分修改是暂存状态;如果你之后又改了a.txt但没有再次执行add,工作区的a.txt哈希会和index里的哈希不一致,这时候git status就会把a.txt同时列在「暂存的变更」和「未暂存的变更」两个分类下——前者是HEAD和Index的差异,后者是Index和工作区的差异。
所有状态的对应关系
其实git status展示的所有文件状态,本质都是两次diff的结果组合,和index条目本身带不带状态标签无关:
- HEAD哈希 == Index哈希 == 工作区哈希:已提交,无变更
- HEAD哈希 != Index哈希,Index哈希 == 工作区哈希:修改已暂存,待提交
- HEAD哈希 == Index哈希,Index哈希 != 工作区哈希:修改未暂存
- HEAD哈希 != Index哈希,Index哈希 != 工作区哈希:文件同时存在已暂存和未暂存的修改
还有个特殊场景:合并冲突时,index里会给同一个冲突路径存最多3份不同阶段的条目(分别对应合并共同祖先版本、当前分支版本、合并入分支版本),这类条目本身就是未完成合并的标识,自然不属于已提交状态。
内容的提问来源于stack exchange,提问作者lorem1213

