具体而言, 在执行git status的时候, git到底做了什么?
目前在网上找到的疑似正确答案: git会调用系统函数lstat来读取文件属性, 从而判断文件的大小和修改时间是否有所改变; 于是, 假设某个文件修改后的大小正好和修改前一致, 然后在保存时又强制使用了和之前相同的修改时间来保存, 那么此时git是感知不到这一文件已经发生改变的。
经过个人测试, 发现确实如此, 所以该答案似乎是对的, 但随之产生一些疑惑, 不妨以一个具体的例子说明:
1, 对一个初始化的空库, 新建文档t1.txt, 执行git status, 此时git能将之识别为未追踪文件. 问: git调用lstat具体读取的是t1.txt的哪一些属性, 从而判断出来t1.txt是一个新文件? 个人猜测是检测创建时间是否等于修改时间, 不知是否正确, 但反正不可能是上述答案中的看文件大小和修改时间是否有变.
2, 将t1.txt暂存, 修改其内容后, 再次git status, 此时git能将之识别为已修改文件. 问: git调用lstat具体读取的是t1.txt的哪一些属性, 从而判断出来t1.txt是一个有新内容的旧文件? 按照上述答案, 读取的是其当前的文件大小与修改时间, 然后就要与它以往的文件大小与修改时间做对比, 看是否有改变. 但是问题来了, t1.txt之前的文件大小与修改时间现在已经被新值覆盖掉了, 那还怎么比对? 最直观的想法肯定是存进了版本里, 但发现起码对于修改时间, index和blob里都是没有的, 所以目前猜测是由操作系统负责保存的? 比如大小与修改时间等文件属性其实都存在一个列表里, 所以git才有历史数值可供比对当前数值是否改变.
3, 将t1.txt改名为t2.txt, 再次git status, 此时git能同时识别到t1.txt的删除与t2.txt的新增. 问: 对前者t1.txt的删除, 这应该是比对了index与工作区的结果, 但对于后者t2.txt的新增, git又是怎么调用lstat识别的? 这回是重命名而非新建文件, 所以它的文件大小与修改日期均是未改变的, 亦即其修改日期现在并不等于创建日期, 那git又是怎么识别它是一个新文件的?
4, 再将t2.txt暂存, 会发现相应blob文件的修改日期变了, 亦即改一次文件名就要重新把相同的内容备份完了覆盖一遍, 这个设计不合理吧, 尤其当文件特别巨大时, 不该是这样才对. 对此该怎么理解?
贴一下复现的测试结果: 文件内容从1改到2, git检测不到.
初始化时,对象库中为空
创建一个 t1.txt 文件,在第一行输入 123456进行 git add操作后在 46 目录下发现新增的 blob 对象,所谓的 SHA 算法也就是 Git 对象中的对象 ID,拆分成“2 位文件目录名+38 位对象名”用于快速查找 Git 中的文件
查看 t1.txt 在 Index 树下如何存储,在 Git 中可以看到其内容:git write-tree是使用当前索引 Index 创建树对象,同时会创建一个树目录,也就是 aa,我们可以忽略它
如果将 t1.txt 文件内容修改,增加一行 123456 并操作 git add,查看当前对象库:
发现创建了一个新的目录,再查看其内部文件:
实际上 t1.txt 的内容修改,Git 会再创建一个新 blob 对象存放整个内容,而不是在原 blob 对象下增量存储
修改 t1.txt 为 t2.txt,再次 git add,新增了 8c 目录和里面的文件实际上 Git 并没有在对象库中删除之前的 t1.txt,创建了一个新对象,新对象里面的内容指向原来的 t1.txt:
最后再将 t2.txt 修改为 t1.txt 并操作 git add 查看 Index 文件修改时间,我们得知 Index 树变化了,但是其他文件目录没有发生变化:
查看当前的 Index 索引树,发现最后结果还是指向了和上面相同的那个 t1.txt 文件: