这些数字可信吗
这个站点上的一切都建立在一个输入之上:你的 git 历史。而这段历史比看上去脏得多,污染带来的影响比任何公式的精度都大。本页讲的是什么会出错,以及 dowsing 对每种情况做了什么。
这也是对「我该相信这些数字吗」的诚实回答。如果你的仓库存在 dowsing 无法纠正的问题,那就不该相信。
1. 纠缠提交
问题。 一个提交同时做了重构、修 bug 和重命名。里面的每个文件从此都显得「一起改动」,凭空造出并不存在的耦合。
dowsing 的做法。 触及超过 50 个文件的提交,从耦合计算中排除。它们仍计入 churn,因为那些文件确实改了——只是它们对耦合没有任何说明力。
留给你的部分。 一个 10 个文件的纠缠提交,和一个正当的 10 文件提交无法区分。没有工具能事后修复这一点,把提交拆小是唯一的办法。
2. 格式化、生成与 vendored 提交
问题。 对整个仓库跑一次 Prettier 看起来像是巨大的改动,会撑大它触及的每个文件的 churn。生成代码——openapi-types.ts、protobuf 输出——在无人思考的情况下持续变化。
dowsing 的做法。
- 只改空白的提交用
git log -w检出并排除。 - 生成目录(
generated/、__generated__/)、生成文件模式(*.gen.ts、*.generated.ts)、锁文件、压缩文件与vendor/默认不在范围内。 - 加上
--verify-churn会比对每个修订的 AST,把行 diff 无法区分的重命名与格式化从真实修改中分离出来。
这一条是靠自用发现的
在一个真实仓库里,加上这些排除之前,生成的 openapi-types.ts 排在 hotspot 第一位。纯粹的噪声,分数却很高。
3. 重命名与移动
问题。 重命名一个文件,它的历史就从零开始。在目录重组是家常便饭的 monorepo 里,这是破坏力最大的一项。
dowsing 的做法。 读取日志时启用重命名与复制检测,因此会跟随移动。加上 --verify-churn,移动后内容未变的文件会被判定为 identical,不计入 churn。
4. 包边界
问题。 在 monorepo 里,改 packages/ui 和改 apps/web 不是同一类事件。把仓库当成一堆平铺的文件,就会把两者混在一起。
dowsing 的做法。 先读取 workspace 配置,把每个文件归属到包。指标按文件计算,再用 LoC 加权平均聚合。耦合在文件与包两种粒度上都计算。
5. squash merge 与 rebase
问题。 使用 squash merge 时,一个分支的 30 个提交变成 1 个。改动频率下降,贡献者数量被低估。
dowsing 的做法。 没有自动处理——信息已经被销毁了。它能做的是报告实际使用的提交数,于是你能看出「一年 200 个提交、40 个开发者」的仓库正在用 squash merge。
留给你的部分。 请在同一仓库内跨时间窗口比较,而不是跨合并策略不同的仓库比较。
6. 开发者身份
问题。 同一个人以 alice@corp.com、alice@personal.com 和 Alice 提交。他们被算作三个人,ownership 被稀释,bus factor 被高估——包看起来比实际更安全。
dowsing 的做法。 通过 git log %aN/%aE 读取作者名与邮箱,这会应用 .mailmap。
留给你的部分。 如果没有 .mailmap,请补上。这种失效是无声的:数字看起来正常,只是朝乐观方向错了。
7. bot 提交
问题。 dependabot、renovate 与 CI bot 会产生大量提交,同时扭曲 churn 与 ownership。
dowsing 的做法。 作者带 [bot] 后缀、属于已知 bot 名、或使用 noreply 地址的提交,会在计算 churn 与 ownership 之前被剔除。剔除数量会被打印。
8. 浅克隆
问题。 fetch-depth: 1——CI 的默认值——几乎不留历史。churn、耦合与 age 于是全部失去意义。
dowsing 的做法。 启动时检测浅克隆,明确警告,并在输出中记录 shallow: true。
得到错误数字最常见的方式
它安静地失败:你会得到一份看起来完整、实则基于三个提交的报告。在 CI 中请始终设置 fetch-depth: 0。
9. 源码中的裸 NUL 字节
问题。 如果源文件中含有裸 NUL 字节,git 会把它当作二进制文件,--numstat 返回 - 而不是行数。那个文件的 churn 会悄悄消失。
dowsing 的做法。 把 - 解析为「未知」而不是零,因此不会把该文件默默当成未改动。
留给你的部分。 请用 \0 转义而不是嵌入该字节。dowsing 自己的代码库里就有三个文件犯过这个错。
贯穿这一切的原则
上述所有排除都会出现在输出里。
excluded: 312 merge, 47 bot, 18 format-only, 0 ignore-revs (51 large changesets flagged)一个悄悄丢弃数据的分析,会诱使你把结果读成「考虑了全部之后的结论」。一个你看不出范围的数字,是无法据以行动的——所以 dowsing 总是展示它的范围,哪怕答案并不好看。