bigywater.tech
Git · 36

racy git:时间戳相同时怎么避免误判

git 靠 stat 判断文件有没有改,但如果文件恰好在 index 写出的同一时刻被改,mtime 一样怎么办?这是 racy git 问题。这篇用把 mtime 设到未来的办法造出 racy 条目,验证 git 不信任它们、改而比较内容,以及 racy 什么时候解除、代价有多大。

AM vs A
同样的伪装改写,racy 条目的 git status 多出 M(被发现),非 racy 的没有
≈ 1 秒
racy 判断的粒度:mtime 刚过去 0.3 秒,git status 仍然每次重读内容
207 vs 30 ms
1 万个文件,mtime 在 2 分钟后(racy)与在过去(正常)时 git status 的耗时

问题

实测 · 本机执行输出
  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 到点之前不会好转。

分步演示

racy git
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。racy 条目用 os.utime 把 mtime 设到未来来造;“恢复 mtime 的原地改写”沿用上一篇的方法;耗时每组连续运行几次,含进程启动;本机 git 的 racy 比较粒度是从行为推断的(0.3 秒后仍 racy),没有读源码
    • 没有验证:git 在写出 index 时把 racy 条目的 size 记成 0(“smudge”)的传统机制——我在这台机器的 git 上没有观察到 index 里的 size 被改成 0,所以这里只描述了读取时的比较行为;Linux 上 mtime 精度为纳秒时 racy 判断的粒度;fsmonitor 打开时 racy 的处理;网络文件系统的情形
    京ICP备2026067502号-1京公网安备11010502063487号