读一百条提交能读出什么:CC Switch 的版本体检
想知道一个项目的新版本到底改了什么,最省事的做法是读发布说明。但发布说明是作者挑出来讲给你听的,它天然回答不了两类问题:改动的重心到底落在哪一层,以及哪些地方作者一个字都没碰。后一类问题往往更要紧——你依赖的那块基础设施如果这一版没动,你的升级风险就小得多。
这两类问题只能自己去提交历史里数。CC Switch 从 v3.19.2 到 v3.20.1 正好是一个不大不小的区间,适合拿来演示一次完整的体检:怎么定口径、怎么分桶、怎么用「diff 为空」当证据。
关于这篇文章的依据
本文只做了一件事:把 CC Switch 的两个版本快照拉到本地,用 git log、git diff 和 grep 静态地数。新快照是 v3.20.1,对应仓库提交 3217f725;旧快照是 v3.19.2 这条线上的 c39c9032,两者放在两个本地目录里互相比对,核对日 2026-08-31。
我们没有编译、没有运行、也从来没有安装过这个桌面应用。下面出现的所有数字,要么来自一条可以贴出来的命令,要么来自某个文件的某一行;凡是我们没法自己重算的,都会点名说清楚。
第一步:先把「一百条」的口径钉死
这个区间常被说成「一百个提交」。但同一件事至少有三种数法,三个数字都不一样:
| 口径 | 命令 | 结果 |
|---|---|---|
| tag 到 tag 的全区间 | git log --oneline v3.19.2..v3.20.1 | 100 |
| 旧快照的 HEAD 到新 tag | git log --oneline c39c9032..v3.20.1 | 98 |
| 拆成两个发行版 | v3.19.2..v3.20.0 / v3.20.0..v3.20.1 | 72 / 28 |
差的那两条不是数错了,是端点选得不一样:我们手上那份旧快照的 HEAD 是 c39c9032,它比 v3.19.2 这个 tag 晚了两个提交。也就是说「旧快照」和「上一个正式版」并不是同一个点。
这件事看着琐碎,实际影响很大。你在两篇文章里分别写「98 个提交」和「100 个提交」,读者会以为你在自相矛盾;而真相只是端点不同。所以体检的第一条纪律是:报提交数必须连口径一起报,不能只给数字。 本文之后的所有统计,一律用 tag 到 tag 的全区间,也就是那个 100。
同样的道理适用于整段 diffstat。git diff --shortstat v3.19.2 v3.20.1 给出的是 309 个文件、62517 行新增、7445 行删除。这个数字不能当作「有人手写了六万行代码」来读——它把 Cargo.lock、pnpm-lock.yaml 这类由工具生成的锁文件,以及成块的图标数据文件都算了进去。diffstat 适合用来感知量级,不适合用来估算工作量。
按类型分桶:这是一次收敛,不是一次扩张
这个项目的提交标题基本遵循 conventional commits,于是类型可以直接切出来:
git log --format='%s' v3.19.2..v3.20.1 \
| sed -E 's/^([a-zA-Z]+)(\(.*\))?!?:.*/\1/' | sort | uniq -c | sort -rn
结果是 fix 56 条、feat 22 条,refactor / docs / chore 各 5 条,ci 3 条,test / style / perf 各 1 条,另有 1 条不带类型前缀。
这张表怎么读?修复占了一多半,新功能只有两成出头,这是典型的「收口」形态而不是「铺面」形态。它不一定说明质量好坏——一个新子系统刚落地,随后大量提交都在补它的边角,是很正常的节奏。真正有用的是把这个比例当成基线:如果下一个区间 fix 依然占一多半,说明还没收敛完;如果 feat 反超,说明又进新一轮扩张了。
需要提醒的是,这类统计只在区间封闭之后才是稳定的。v3.19.2 到 v3.20.1 之间的提交已经写死在历史里,谁去数都是这些数;但换成任何一个包含未来提交的区间,数字就得重跑。
唯一一条不守规范的提交
一百条里有一条不符合 conventional commits,标题是 Add managed OAuth account selection for providers (#3879)。用反向筛选很容易把它挑出来:
git log --format='%h %s' v3.19.2..v3.20.1 | grep -vE '^[0-9a-f]+ [a-z]+\('
要注意这条筛选不是只捞出它一条:输出里还跟着 3 条没写 scope 的 ci: 提交。那 3 条是有类型前缀的,不算违规,只是括号里的 scope 空着才被这个正则连带捞了出来,肉眼一扫就能分开。
那条真正不带类型前缀的提交,恰好是这个区间里体量最大的功能改动之一。同一区间里还有另一处命名上的出入:scope 写成 grokbuild 的有 4 条,写成 grokBuild 的有 1 条,指的是同一个模块。
这两处只是记录事实:仓库里既有 100 条里 99 条守规范的提交,也有 1 条没带类型前缀;scope 名同时存在两种大小写写法。我们不去推断作者为什么这样、也不据此评价这个项目——说完差异就停。对读者有用的信息只有一条:这份规范目前没有机器校验,所以你写脚本按提交标题做统计时,必须给这类漏网之鱼留一个兜底分支,否则一条大功能就会从你的分桶表里凭空消失。
按 scope 与文件分桶:改动压在哪几个面上
类型告诉你「在做什么性质的事」,scope 和文件告诉你「事情发生在哪」。
按 scope 数,最集中的三个是 codex(21 条)、presets(12 条)、usage(9 条),后面依次是 provider、proxy 各几条。把 codex 与 codex-oauth 合起来看,占了整个区间的将近四分之一,且其中绝大多数是 fix——这条线是在追一个被管理客户端的上游兼容性,不是在加新东西。
按文件数就更直观了:
git log --name-only --format='' v3.19.2..v3.20.1 \
| grep -v '^$' | sort | uniq -c | sort -rn | head -40
排在最前面的是四个界面文案文件(src/i18n/locales/ 下的 en / ja / zh / zh-TW),每一个都被 20 个提交碰过。换句话说,这个区间里平均每五个提交就有一个要动翻译。紧随其后的是 src/config/ 下按应用分组存放的供应商预设文件,以及后端的 src-tauri/src/codex_config.rs。
这里有个容易误读的地方:预设文件被改得勤,不等于「开发量大」。这批文件本质上是配置数据——往数组里加一条、改一条已有条目指向的地址,都算一个提交。它是常态化的维护面,跟逻辑代码的改动不是一个量级的事。把配置数据的提交和功能开发的提交混在一起算「开发活跃度」,得到的结论会明显失真。
时间与作者:两段冲刺夹一个低谷
把提交按日期铺开(git log --format='%ad' --date=short,再 uniq -c),形状很清楚:8 月 10 日到 18 日是连续的密集提交,这九天的日计数加起来正好 72;19 日到 24 日整整六天只有 2 条;25 日到 28 日又是一段密集提交,这四天合计 25 条。中间还有个零头:整个区间最早的一条落在 8 月 6 日。发版、喘口气、再开一轮——曲线本身就是节奏。
这里要按前面那条纪律再提醒一次:日期分桶和发行版分桶不是一回事,别顺手画等号。 两段冲刺的形状确实分别对着 v3.20.0 与 v3.20.1,但「10 日到 18 日」这个日期区间和「v3.19.2..v3.20.0」这个 tag 区间不是同一个集合——前者不含 6 日那条,两边的成员构成不同,只是日和恰好也是 72。第二段更明显:25 日到 28 日是 25 条,而 v3.20.0..v3.20.1 是 28 条,差的 3 条散在更早的日子里。读者拿你给的命令重跑,第一眼看的就是这种对不上。
作者维度上,这一百条来自 14 位作者,维护者一人占了 53 条。这个比例的用处是提醒你一件事:「外部贡献者数量」和「外部贡献的行数占比」是两码事,别把前者当后者用——十几个人的名字出现在作者栏里,不代表代码量也是这么分布的。
反过来问:哪些东西一行都没动
体检做到这里,还差最重要的一半。升级风险不在改了什么,而在你依赖的那块有没有被改。 这个问题只有一种可靠的答法:直接对文件做 diff,输出为空就是证据。
在 CC Switch 这两版之间,本地网关那一层的三块基础设施逐字未变:
熔断器 src-tauri/src/proxy/circuit_breaker.rs 新旧两份文件 diff 完全为空。里面的三个状态(Closed / Open / HalfOpen,定义在 circuit_breaker.rs:16-23)、五个阈值字段与它们的默认值(CircuitBreakerConfig 在 :38-49,Default 实现在 :63-73:失败阈值 4、成功阈值 2、超时 60 秒、错误率阈值 0.6、最小请求数 10)一个都没动。半开状态下同时只放行 1 个探测请求这件事也没变——那个 1 是 allow_half_open_probe() 函数体内的局部变量,不在配置结构体里。以上都是 v3.20.1 的代码默认配置,其中五个阈值是可配的、会随版本变;半开限额那一个当前不可配。这些值只描述代码写了什么,不构成对你实际会不会被熔断的任何预测。
日志错误码表 src-tauri/src/proxy/log_codes.rs 同样 diff 为空,62 行、27 个 pub const、6 个模块前缀,逐字相同。文件头的注释写了格式约定「格式: [模块-编号] 消息」,并且整个文件带着 #——允许存在暂时没有调用方的码,属于先占号后启用的写法。我们早先按 v3.19.2 快照整理过这张码表(见那 27 个日志错误码与 HTTP 状态映射),到 v3.20.1 它一个字都不用改。
连通性探测与测速的超时常量也没变。src-tauri/src/services/stream_check.rs 的 Default 实现给出单次探测超时 8 秒、超时类失败最多重试 1 次、降级阈值 6000 毫秒;src-tauri/src/services/speedtest.rs:8-10 的三个常量分别是默认 8 秒、最大 30 秒、最小 2 秒(这三个数的用法我们单独写过一篇:测速超时的 2 / 8 / 30 三个数怎么用)。两版一致。
还有两条结构性的「没变」值得记下来。第一条:整个区间里没有任何文件被删除。这个结论是把新旧两份快照的目录清单分别 ls 出来再 diff 得到的——src/config/、src-tauri/src/、src-tauri/src/commands/、src-tauri/src/services/ 四处都只有新增行、没有删除行。第二条:src-tauri/src/proxy/providers/ 这个目录的文件清单一个都没增减,ls 出来两份完全一致——注意这说的是目录结构,那里面个别文件的内容改动其实相当大,不要把两件事混为一谈。
把这些串起来,结论就落地了:309 个文件的改动里,代理层的熔断、探测、日志码三块基础设施一行未动,改动集中在接入层与兼容性修复上。这句话对使用者的意义远比「本版新增了什么」更具体——如果你的排查流程依赖那 27 个错误码,或者你按熔断阈值调过参,这次升级不会推翻你的既有认知。至于再往后的版本会不会动,得重新 diff 一次,没有别的办法。
这套体检下次怎么跑
四步,每一步都能落到一条命令上:
- 先定端点,再报数字。 明确写清是 tag 到 tag 还是快照到快照,两个数一起给也行,就是不能只给一个光秃秃的数。
- 按类型分桶看性质,按 scope 和文件分桶看位置。 分桶脚本必须给不合规范的提交标题留兜底分支。
- 把配置数据的改动单独拎出来。 翻译文件、预设数据这类高频但轻量的改动,混进「开发活跃度」会让结论失真。
- 最后对你依赖的模块逐个
diff。 输出为空是最便宜也最硬的证据,比读十遍发布说明都可靠。
前三步告诉你这一版在忙什么,第四步才告诉你要不要担心。发布说明只负责第一件事。
本文依据 CC Switch 官方仓库(github.com/farion1231/cc-switch)的 README、docs/ 下的用户手册、
路由指南与发布说明,以及 src/、src-tauri/、tests/ 的源码整理,
核对日 2026-08-31,对应仓库快照 3217f725(仓库内版本号 3.20.1)。
本文内容为仓库源码与文档口径,我们没有安装或运行过这个桌面应用,
因此不涉及界面外观、操作手感与切换速度的任何描述。
文中出现的阈值与默认值均为源码中的默认配置,不构成对实际运行结果的保证。
该项目仍在快速迭代,版本与默认值随时可能变动,请以仓库最新内容为准。
留言讨论
评论发布后会被人工复核,违规内容将被删除。
如果发表没有反应,可以前往联系我们告诉我们。