git 靠 stat 判断文件有没有改,但如果文件恰好在 index 写出的同一时刻被改,mtime 一样怎么办?这是 racy git 问题。这篇用把 mtime 设到未来的办法造出 racy 条目,验证 git 不信任它们、改而比较内容,以及 racy 什么时候解除、代价有多大。
a) 正常情况:文件是之前写的,mtime 早于 index 写出的时刻
文件 mtime 1791729345.694569000 index 文件 mtime 1791729405.707702578 文件早于 index: True index 里记的 size = 6(文件真实大小 6)
b) racy:文件的 mtime 比 index 文件的修改时间还晚(这里用 os.utime 把 mtime 设到 60 秒后,等同于时钟不同步 / 解压出来的文件)
文件 mtime 1791729465.725666000 index 文件 mtime 1791729405.738596051 文件早于 index: False index 里记的 size = 6(文件真实大小 6)stat 缓存假设“文件内容变了,mtime(或 size、ctime)一定会变”。但如果文件在 index 写出的同一时刻之后被改,而 mtime 的精度又不足以区分,就会漏掉改动。这叫 racily clean。
a) 非 racy: git status --short → A a.txt 工作区 blob e427984d index blob ce013625 b) racy: git status --short → AM a.txt 工作区 blob e427984d index blob ce013625
git 的办法:index 条目里的 mtime 不早于 index 文件自己的修改时间时,这个条目就是 racy,不再信任 stat,直接比较文件内容。所以哪怕伪装了 mtime 和 size,racy 条目里的改动也会被发现。
建好仓库之后,离文件 mtime 还剩 12 秒(add 和 commit 本身就花了 8 秒) mtime 还没到:209 ms 209 ms 208 ms mtime 刚过去 0.3 秒之后连续 3 次:291 ms 210 ms 206 ms mtime 过去 3 秒之后连续 4 次:246 ms 32 ms 30 ms 29 ms
等 index 文件的修改时间越过文件的 mtime(粒度约 1 秒),下一次 git status 重写 index 后条目不再 racy,之后又走快速路径。本例里 mtime 刚过去 0.3 秒仍然 racy,过去 3 秒后第一次 status 之后恢复。
文件 mtime 在过去(正常):60 ms 30 ms 29 ms 29 ms 30 ms 文件 mtime 在 2 分钟后(racy):209 ms 207 ms 206 ms 207 ms 207 ms
racy 的条目每次都要读文件内容。文件 mtime 在未来(时钟不同步、网络文件系统、压缩包里带未来时间戳)时,1 万个文件的 git status 要 207 ms,正常只要 30 ms,而且在 mtime 到点之前不会好转。