资料库 · Git 规模化、工作流与安全
第 ③ 卷,条目 41–60。绝大多数来自 github.blog,作者以 Derrick Stolee(大仓库性能)和 Taylor Blau(发布与安全)为主。
这一卷分四段:
- 41–48 规模化 —— 仓库大到
git clone要半小时的时候怎么办 - 49–52 工作流 —— worktree、三角工作流、凭据管理这些日常但少有人系统读过的东西
- 53–58 安全 —— Git 自身的历次漏洞公告,以及源码归档 hash 稳定性那次事故
- 59–60 社区 —— 二十周年的两篇回顾
第一段是给"仓库已经很大"的人的。如果你的仓库只有几万行,这四条读了也用不上——别提前优化,git clone 三秒钟的仓库不需要 partial clone。
规模化:仓库大了怎么办
41. Get up to speed with partial clone and shallow clone
来源:Derrick Stolee,GitHub Blog,2020 年 12 月 21 日。
读它解决什么:--depth=1 和 partial clone 的区别,各自该在什么场景用。
要点:
- 对比 shallow clone(浅克隆)与 partial clone(部分克隆)两种机制
- 说明各自省掉的是什么数据
我的补充:这两个机制经常被混为一谈,实际上省的是完全不同的东西:
- shallow clone(
--depth=N)省的是历史。 只要最近 N 个提交,更早的不要。 - partial clone(
--filter=blob:none)省的是文件内容。 提交和树全都要(所以完整历史在),但文件的实际内容按需拉取。
这个区别决定了它们的适用场景,而搞错了会很难受:
CI 用 shallow。 CI 只要一个快照来构建,不需要历史。git clone --depth=1 是对的。但注意:git describe、git blame、SonarQube 的增量分析、任何依赖 tag 或历史的步骤在浅克隆里会失败或给错结果。这类 CI 要么加大 depth,要么改用 partial clone。
人用 partial clone。 开发者需要能 git log、能 git blame、能翻历史,只是不需要每个历史版本的文件内容都在本地。git clone --filter=blob:none <url> 之后,你 checkout 到哪个提交,Git 才去拉那个提交需要的 blob。
partial clone 的代价是它把"离线可用"换掉了:断网时 git checkout 一个老提交会失败,因为要去服务端拉 blob。这一点在飞机上或者网络差的地方很难受,值得事先知道。
42. Bring your monorepo down to size with sparse-checkout
来源:Derrick Stolee,GitHub Blog,2020 年 1 月 17 日。
读它解决什么:怎么让工作区里只出现你关心的那几个目录。
要点:
- 介绍 sparse-checkout 的用法与设计意图
- 面向 monorepo 场景
我的补充:sparse-checkout 省的是第三种东西:工作区里的文件数。配合上一条:
| 机制 | 省掉什么 |
|---|---|
| shallow clone | 历史提交 |
| partial clone | 历史文件内容 |
| sparse-checkout | 工作区文件 |
三个可以叠加,Scalar(第 46 条)干的就是把三个一起配好。
用法(cone 模式,推荐):
git sparse-checkout init --cone
git sparse-checkout set apps/web libs/sharedcone 模式是关键,别用非 cone 的 pattern 模式。 非 cone 模式支持任意 gitignore 风格的通配,代价是 Git 要对每个路径做一次模式匹配——在几十万文件的仓库上这本身就成了瓶颈,反而更慢。cone 模式只支持"整个目录要或不要",但可以用前缀树快速判断,这才是让 sparse index(下一条)成为可能的前提。
一个真实的坑:sparse-checkout 之后 git status 里不会显示范围外的文件,但它们仍然在 index 里、仍然会被提交带走。如果你在范围外的文件上做了什么(比如某个脚本改了它),你不会看到。所以别把 sparse-checkout 当作"隔离"手段用。
43. Make your monorepo feel small with Git's sparse index
来源:Derrick Stolee,GitHub Blog,2021 年 11 月 10 日。
读它解决什么:sparse-checkout 之后 git status 还是慢,为什么。
要点:
- sparse index 是对 index 文件本身的稀疏化
- 与 sparse-checkout 配合,解决"工作区小了但 index 还是全量"的问题
我的补充:这一篇解决的问题非常典型:你配了 sparse-checkout,工作区从 50 万文件降到 2 万,但 git status 只快了一点。 原因是 .git/index 里仍然记着全部 50 万个条目——sparse-checkout 只是不把它们写到磁盘上,index 该有的还有。而 git status 要遍历整个 index。
sparse index 的做法是:范围外的整个目录在 index 里只留一个条目(指向那棵子树),不展开。50 万条目可能压到几万,git status 才真的快下来。
开启方式:
git sparse-checkout init --cone --sparse-index
# 已有的稀疏检出可以直接切
git config index.sparse true注意它依赖 cone 模式(见上一条),这就是为什么 cone 模式重要。另外这个特性刚出时有一批命令还不支持稀疏 index,会静默"展开"回全量(然后你的优化就没了)。判断办法是跑完命令后 git ls-files --sparse | wc -l 看条目数有没有暴涨——涨了说明刚才那个命令把 index 展开了。到 2024 年之后主流命令基本都支持了,但自定义脚本和第三方工具仍可能触发展开。
44. Scaling monorepo maintenance
来源:Taylor Blau,GitHub Blog,2021 年 4 月 29 日。
读它解决什么:GitHub 侧是怎么维护超大仓库的,服务端要做哪些事。
要点:
- 从服务端视角讲大仓库的维护策略
- 涉及 gc、repack、引用管理等运维层面的工作
我的补充:这一篇的视角和前三条不同——前三条是客户端怎么少拿数据,这一条是服务端怎么把数据组织好。
对自建 Git 服务的人,这一篇里最该抄的是分层 repack 策略(geometric repacking)。朴素的做法是定期 git repack -a,把所有对象重打成一个 packfile——问题是大仓库上这个操作要几十分钟、吃满 CPU 和内存,还得拿锁。几何式重打包的思路是维护一组大小成几何级数的 packfile,新对象打进小包,小包积累到一定程度合进大包,大包很少动。总代价摊薄了。
命令层面 Git 2.32 之后有 git repack --geometric=<factor>,配合 git maintenance 用。自建服务上值得配:
git repack --geometric=2 --write-midx -d--write-midx(multi-pack index)是配套的另一半:多个 packfile 存在时,查一个对象要逐个包找,midx 给它们建一个统一索引。有了 midx,"多个包"就不再有查询代价,几何式重打包才划算。
45. Git clone: a data-driven study on cloning behaviors
来源:Solmaz Abbaspoursani,GitHub Blog,2020 年 12 月 22 日。
读它解决什么:不同 clone 方式(全量、浅、部分、稀疏)在真实数据下的效果对比。
要点:
- 基于实际数据比较各种克隆策略
- 与第 41、42 条同期,是那两篇的量化补充
我的补充:这一篇的用处是给你一个决策依据,而不是让你凭感觉选。
我自己的经验规则,按仓库规模分(git count-objects -vH 看 size-pack):
- < 100 MB —— 什么都别配,全量 clone。优化的收益小于配置的复杂度成本。
- 100 MB – 1 GB —— CI 用
--depth=1(前提是没有依赖历史的步骤),人用全量。 - 1 GB – 10 GB —— 人用
--filter=blob:none+ sparse-checkout;CI 视情况。 - > 10 GB —— 直接上
scalar clone(第 46 条),别自己一个一个配。
另外有个容易忽略的点:很多"仓库太大"的问题根源不是代码多,是历史里有不该进来的二进制文件。在做任何 clone 优化之前,先花十分钟确认这一点:
# 找出历史里最大的对象
git rev-list --objects --all |
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' |
awk '$1=="blob" {print $3, $4}' | sort -rn | head -20如果前几名是几百 MB 的压缩包、视频、编译产物,那真正的解法是清理历史(git filter-repo)加上 LFS,不是 partial clone。前者一次性解决,后者是每天都在付的复杂度。注意清理历史会改写所有提交 hash,是个需要全团队协调的操作。
46. The Story of Scalar
来源:Derrick Stolee & Victoria Dye,GitHub Blog,2022 年 10 月 13 日。
读它解决什么:Scalar 是什么、从哪来,以及为什么它被收进了 Git 本体。
要点:
- Scalar 的来历与设计目标
- 2022 年 10 月发表,同期 Git 2.38 把 Scalar 收入本体(见第二卷第 24 条)
我的补充:Scalar 的血统值得知道,因为它解释了这个工具为什么可信:它源自微软为了把 Windows 源码仓库(几百万文件,全球最大的 Git 仓库之一)搬上 Git 而做的一系列工作。早期是 VFS for Git(一个虚拟文件系统,激进但侵入性太强),后来收敛成 Scalar——不改文件系统,只是把 Git 已有的优化选项一次性配对。
所以 Scalar 里没有任何"魔法",它做的每一件事你都能手动做(第 41–44 条那些)。它的价值纯粹是把正确的组合固化下来,省掉你逐个研究和踩坑。
用法:
scalar clone https://github.com/org/big-repo
# 已有仓库也能接管
scalar registerscalar register 这个用法知道的人少但很实用:把一个已经 clone 好的仓库交给 Scalar 管理(注册后台维护、写 commit-graph、开 fsmonitor),不用重新 clone。我在几个中等规模的老仓库上跑过,git status 从两三秒降到几百毫秒。
47. Git's database internals IV: distributed synchronization
来源:Derrick Stolee,GitHub Blog,2022 年 9 月 1 日。
读它解决什么:fetch/push 在协议层实际交换了什么,为什么有时候会传很多数据。
要点:
- 五篇系列的第四篇(前三篇在第一卷第 18–20 条)
- 讲分布式同步,即 Git 的传输协议与协商过程
我的补充:这一篇讲的是 Git 里最容易产生"为什么这么慢"疑问的环节。fetch 的过程分两步:协商(双方交换 have/want,确定对方缺什么)和传输(服务端现场打一个 packfile 发过来)。
两个实用的推论:
一、git fetch 慢常常慢在协商,不是带宽。 如果你有几万个本地引用,协商阶段要交换的 have 列表本身就很大。征兆是 git fetch 卡在 "Enumerating objects" 前面很久。解法是清理引用:git remote prune origin、fetch.prune true、别 fetch 所有远端分支(用 refspec 限定)。
二、服务端要现场算 delta,所以并发大的时候 CPU 是瓶颈。 这就是为什么 CI 里几百个 job 同时 clone 会把自建 Git 服务打爆,而单个 clone 明明很快。解法:CI 侧加缓存(actions/cache 缓存 .git,或者用 bundle URI 让首次 clone 走静态文件),服务端侧预生成 packfile。
protocol.version=2 值得确认一下有没有开(Git 2.26 之后默认开)——协议 v2 的核心改进就是让引用发现变成按需的,而不是服务端上来就把所有引用列一遍。仓库引用多的话差别很大。
48. Git's database internals V: scalability
来源:Derrick Stolee,GitHub Blog,2022 年 9 月 2 日。
读它解决什么:Git 的扩展性瓶颈分别在哪个维度上。
要点:
- 系列第五篇,也是收尾篇
- 从数据库视角总结 Git 的扩展性问题
我的补充:这一篇给出了一个很有用的框架:Git 的"大"有四个互相独立的维度,而每个维度的解法完全不同。这个框架让"仓库太大"这个模糊抱怨变成可诊断的问题:
| 维度 | 症状 | 解法 |
|---|---|---|
| 文件多(宽) | git status 慢 | sparse-checkout + sparse index + fsmonitor |
| 历史长(深) | git log、git blame 慢 | commit-graph、changed-path Bloom filter |
| 单文件大 | clone 慢、.git 巨大 | LFS,或清理历史 |
| 引用多 | fetch/push 慢 | prune、pack-refs、reftable |
诊断顺序:先 git count-objects -vH 看总体积和松散对象数,再 git ls-files | wc -l 看文件数,git rev-list --count HEAD 看提交数,git for-each-ref | wc -l 看引用数。四个数一出来,你就知道自己"大"在哪个维度上,然后只做对应那一列的优化。
最常见的错误是在错误的维度上优化——比如仓库慢在引用太多,却去配 sparse-checkout,配完发现没用。
这五篇系列(第一卷 18–20 条 + 本卷 47–48 条)如果只读一篇,读这一篇。 它是整个系列的总纲。
工作流:worktree、三角工作流、凭据
49. What are git worktrees, and why should I use them?
来源:Cassidy Williams,GitHub Blog,2026 年 6 月 16 日。
读它解决什么:git worktree 是什么,比来回 git switch 好在哪。
要点:
- 介绍 worktree 的概念与使用场景
- 2026 年 6 月发表,是本库收录的较新一篇
我的补充:worktree 是我认为投入产出比最高、但用的人最少的 Git 特性(第一卷第 16 条提到几乎没人用)。
它解决的问题很具体:你正在 feature 分支上改到一半,工作区脏着,这时候要去看一眼 main 上的某个文件、或者紧急修个 bug。常规做法是 git stash,切分支,弄完再切回来 git stash pop——中间任何一步出错都可能丢东西,而且 stash 栈一深就没人搞得清哪个是哪个。
worktree 的做法是同一个仓库,多个工作目录:
git worktree add ../myrepo-hotfix main
cd ../myrepo-hotfix # 一个干净的 main 工作区,对象库和原来共享
# 修完
git worktree remove ../myrepo-hotfix
git worktree list # 看当前有哪些关键点是对象库只有一份(.git 是共享的),所以加一个 worktree 几乎不占额外空间(只有工作区那份文件),也不需要重新 clone。
三个实际的注意点:
- 同一个分支不能在两个 worktree 里同时 checkout,Git 会拒绝。这是保护你的,不是限制。
.env、node_modules、构建缓存这些不在版本控制里的东西不会跟过去,新 worktree 里要重新装依赖。这是 worktree 最大的实际摩擦,前端项目尤其明显。- 删的时候用
git worktree remove,别直接rm -rf。直接删会留下悬空的元数据,之后要git worktree prune清。
顺带说,worktree 在 AI 辅助开发的场景下用处更大了——让多个任务在各自的工作目录里并行进行,互不干扰,比反复切分支干净得多。
50. How the GitHub CLI can now enable triangular workflows
来源:Tyler McGoffin,GitHub Blog,2025 年 4 月 25 日。
读它解决什么:"从 upstream 拉、往自己 fork 推"这种工作流怎么配才顺。
要点:
ghCLI 对三角工作流的支持- 三角工作流指 fetch 和 push 指向不同远端
我的补充:三角工作流是给开源项目提 PR 的标准形态:你从 upstream(原仓库)拉最新代码,往 origin(你的 fork)推分支,然后开 PR。之所以叫三角,是因为数据流走了 upstream → 本地 → fork → upstream 这一圈。
手动配的话是这样:
git remote add upstream https://github.com/原作者/项目.git
git config remote.pushDefault origin # push 默认去 fork
git config branch.main.remote upstream # main 的上游是原仓库remote.pushDefault 这个配置是关键,知道的人不多。没有它你就得每次 git push origin HEAD,而一旦手滑打成 git push upstream,你就在往别人的仓库推分支(如果有权限的话,那更糟)。
gh 的价值在于把这几步自动化了:gh repo fork --remote 会一次性配好 fork、remote 和上游关系。配合 gh pr create 直接从命令行开 PR,整个流程不用碰浏览器。
另外一个和三角工作流配套的实用命令:gh pr checkout <编号>。它会把别人的 PR 分支拉到本地并正确设置追踪关系——手动做这事要写一串 git fetch upstream pull/123/head:pr-123,很少有人记得住。
51. Git Credential Manager Core: Building a universal authentication experience
来源:Matthew John Cheetham,GitHub Blog,2020 年 7 月 2 日。
读它解决什么:Git 的凭据到底存在哪、怎么存才安全。
要点:
- Git Credential Manager Core 的设计说明
- 目标是跨平台统一的认证体验
我的补充:凭据管理是 Git 里最容易被凑合过去、然后埋下真实风险的一块。几种存法的安全性差得很远:
store—— 明文存在~/.git-credentials,任何能读你 home 的进程都拿得到。别用。 我见过它在共享的构建机上把公司 token 泄给所有人。cache—— 存在内存里,默认 15 分钟。安全但每天要重新输。manager(GCM) —— 走操作系统的凭据存储(Windows Credential Manager、macOS Keychain、Linux 上的 libsecret),并且支持 OAuth 设备流。这是应该用的。
检查自己现在用的是哪个:
git config --get credential.helper如果输出是 store,去换掉。Windows 上装 Git for Windows 默认就是 manager;macOS 上 osxkeychain 也可以,但 GCM 支持 OAuth(拿到的是有过期时间的 token,而不是长期有效的 PAT),更好。
还有一个相关的实践:用 SSH key 而不是 HTTPS + token,能绕过整个凭据管理问题。SSH key 有 passphrase 保护,加载到 ssh-agent 里一次,之后不用再输。给 key 加 passphrase 这一步别省——没有 passphrase 的私钥被偷了就是直接的仓库写权限。
52. Mounting git commits as folders with NFS
来源:Julia Evans,jvns.ca,2023 年 12 月。
读它解决什么:一个把 Git 提交挂成文件系统的实验,顺带展示对象库的可用性。
要点:
- 作者用 NFS 把 Git 提交暴露成目录的实验记录
- 属于探索性项目而不是生产方案
我的补充:这一条收进来是因为它示范了一件比结论更有用的事:Git 的对象库是个通用的内容寻址存储,你可以在它上面盖任何东西。挂成文件系统只是其中一种。
同类思路的实际产物不少:VFS for Git(微软早期的方案,见第 46 条)走的就是这条路;git cat-file --batch 让你能把 Git 当一个 KV 服务用;有些工具直接把配置或数据存成 Git 对象,白拿了版本历史和完整性校验。
对日常工作最有用的衍生技巧是 git archive:
# 把某个提交的内容导出成 tar,不需要 checkout
git archive --format=tar HEAD~5 | tar -x -C /tmp/old-version
# 只导出某个子目录
git archive HEAD:src/ | tar -t这比"clone 一份再 checkout 到老提交"快得多,而且不动你当前的工作区。部署脚本里从仓库里取一份干净的源码,用这个而不是 cp -r(后者会把 .git 和一堆本地垃圾带过去)。
安全:Git 自身的漏洞
先说清这一段的立场:下面这些是 Git 项目自己发布的安全公告,目的是让使用者知道该升到哪个版本。我写的补充一律是"怎么防",不写利用方法。
53. Git security vulnerabilities announced(2025 年 7 月)
来源:Taylor Blau,GitHub Blog,2025 年 7 月 8 日。
读它解决什么:确认 2025 年中这批漏洞影响哪些版本、修在哪个版本。
要点:
- Git 项目 2025 年 7 月的安全公告
- 是本库收录的最新一次 Git 安全公告
我的补充:Git 的漏洞有一个反复出现的模式,知道这个模式比记住任何单个 CVE 都有用:绝大多数 Git 漏洞的触发条件是"克隆或拉取一个恶意构造的仓库",而危害是"在你机器上执行代码"。
路径通常是这几类之一:
.gitmodules里的路径或 URL 含特殊字符(换行、回车、--开头),被 Git 解析后逃出预期范围,最终往.git/hooks/里写文件——而 hooks 里的脚本会在下一次 Git 操作时自动执行- 符号链接 + 大小写不敏感的文件系统(macOS、Windows),让
.git/目录本身可写 - 凭据 helper 的协议里注入控制字符,骗它把你的 token 发到别的主机
所以防御措施是通用的,不用逐个 CVE 去理解:
- 及时升级 Git。 这是唯一真正的修复。
- 不要对不信任的仓库用
--recurse-submodules。 这是绝大多数子模块类漏洞的必要条件。先 clone 不带子模块,看一眼.gitmodules内容,再决定要不要拉。 - CI 里跑不信任的代码要隔离。 PR 触发的 CI 如果 clone 了外部贡献者的分支,那台机器就该当成不可信环境(这也是为什么
pull_request_target这类触发器很危险)。 core.hooksPath值得检查一下是不是被改成了意外的位置。
54. Git security vulnerabilities announced(2025 年 1 月)
来源:Taylor Blau,GitHub Blog,2025 年 1 月 14 日。
读它解决什么:确认 2025 年初这批漏洞的影响范围。
要点:
- Git 项目 2025 年 1 月的安全公告
- 与 Git 2.48 同期发布(见第二卷第 34 条)
我的补充:这一批里有和凭据 helper 协议相关的问题。这类漏洞值得单独说,因为它的危害形态和"代码执行"不同:它泄露的是你的 token,而且泄露的时候你什么都看不到。
原理层面(不涉及具体利用):Git 和凭据 helper 之间用一个基于换行的文本协议通信,helper 根据 host 字段决定返回哪个凭据。如果 URL 里的某些字段能塞进控制字符,就有可能让 helper 对 host 的理解和你的理解不一致——你以为在访问 A,凭据发给了 B。
防御上除了升级,有两条能显著缩小影响面:
一、用范围最小的 token。 GitHub 的 fine-grained PAT 可以限定到单个仓库、单个权限。泄露一个"只能读某个仓库"的 token 和泄露一个 repo 全权限的经典 PAT,后果差好几个数量级。尤其别再用没有过期时间的 classic PAT。
二、优先用 SSH key。 SSH 的认证是挑战-应答,私钥不会离开你的机器,不存在"把凭据发给错误的主机"这个失败模式(发过去的是对随机挑战的签名,对别的主机没用)。
顺带一提,这也是为什么定期轮换凭据有意义:你无法知道自己有没有被这类漏洞影响过(没有日志会记下来)。轮换是唯一能收敛风险窗口的手段。
55. Securing Git: Addressing 5 new vulnerabilities
来源:Johannes Schindelin,GitHub Blog,2024 年 5 月 14 日。
读它解决什么:2024 年 5 月那批(含一个高危 RCE)的说明与修复版本。
要点:
- 一次公布 5 个漏洞的修复
- 作者是 Git for Windows 的维护者,这批漏洞里有多个与 Windows / 大小写不敏感文件系统相关
我的补充:这一批里最受关注的是一个克隆即执行代码的问题(CVE-2024-32002),条件是递归克隆 + 大小写不敏感的文件系统(macOS 和 Windows 的默认配置)。恶意仓库能利用子模块路径与符号链接的组合,让写入落到 .git/hooks/ 里,于是克隆过程中就有钩子被执行。
这个漏洞值得记住,因为它打破了一个很多人默认的假设:"我只是 clone 看一下代码,还没运行"是安全的。实际上 git clone --recursive 一个不认识的仓库,本身就是一个执行外部代码的动作。
具体建议:
# 不信任的仓库这样克隆
git clone --no-recurse-submodules <url>
cat .gitmodules # 先看子模块指向哪、路径长什么样
# 确认没问题再拉
git submodule update --init还有个防御纵深值得配上:全局禁用符号链接(git config --global core.symlinks false,Windows 上通常本来就是关的)。代价是仓库里的符号链接会变成普通文本文件,对一部分项目有影响,所以这个要按需开关,别无脑设。
对企业环境,更有效的是在网关侧拦住:内部只允许从镜像仓库 clone,镜像同步时在隔离环境里做。
56. Git security vulnerabilities announced(2023 年 4 月)
来源:Taylor Blau,GitHub Blog,2023 年 4 月 25 日。
读它解决什么:确认 2023 年 4 月那批的影响范围。
要点:
- Git 项目 2023 年 4 月的安全公告
- 与 Git 2.40.1 等补丁版本同期
我的补充:这一批里有 git apply 相关的问题。借这条说一件常被忽略的事:git apply 和 git am 处理的是外部输入,而很多人对它们的警惕性远低于对 git clone。
场景很常见:有人在 issue 里贴了个 patch,或者你从邮件列表拿了个 .patch 文件,直接 git am < xxx.patch。这个 patch 是别人写的任意内容,理论上可以试图往工作区之外写文件。
安全一点的做法:
# 先看它要改什么,别直接应用
git apply --stat xxx.patch # 改了哪些文件、多少行
git apply --check xxx.patch # 能不能干净应用,不实际写入
# 确认路径都在预期范围内再应用
git apply xxx.patch--stat 那一步的重点是看路径:出现 ../、绝对路径、或者 .git/ 下的路径,就该停下来。
顺带说,这个"先 stat 再 check 再 apply"的三步在日常也有用——很多 patch 应用失败是因为基线版本不对,--check 能在不留下半应用状态的情况下告诉你。
57. Update on the future stability of source code archives and hashes
来源:Matt Cooper,GitHub Blog,2023 年 2 月 21 日。
读它解决什么:GitHub 自动生成的源码归档包(tarball)的 hash 会不会变,能不能拿它做校验。
要点:
- 说明 GitHub 自动生成的源码归档的稳定性承诺
- 起因是一次 hash 变化引发的大范围构建失败
我的补充:这一条不是 Git 的漏洞,但它是这一段里对日常影响最大的一条,而且教训非常具体。
背景:GitHub 的 release 页面上有自动生成的 "Source code (tar.gz)"。大量构建系统(Homebrew、各种 Linux 发行版的打包、Nix、Bazel 的 http_archive)会下载这个包并校验 sha256。问题是这个包是 git archive 现场生成的,而生成结果依赖 Git 的压缩实现——Git 一升级,压缩输出的字节可能变,hash 就变了,于是全世界的构建同时失败。2023 年初真实发生过一次。
结论和实践:
- 别拿自动生成的归档做 hash 校验。 要校验就用维护者手动上传的 release 资产(那是静态文件,字节永远不变)。
- 要可复现,就 pin commit hash 而不是 tag。 tag 是可以被移动的(
git tag -f),commit hash 不能。CI 里uses: actions/checkout@v4这种写法在供应链安全上就弱于uses: actions/checkout@<40位sha>。 - 自己做 release 的话,手动上传构建产物并附 checksum 文件。 别让下游依赖自动生成的那个。
这件事的通用教训是:"看起来是静态文件的东西可能是动态生成的"。CDN 上的、平台自动产生的、带 latest 字样的,都要先确认它的稳定性承诺是什么,再决定能不能把它写进校验。
社区:二十年
58. Git turns 20: A Q&A with Linus Torvalds
来源:Taylor Blau,GitHub Blog,2025 年 4 月 7 日。
读它解决什么:Git 的作者本人回顾当初的设计决策。
要点:
- Git 二十周年之际对 Linus Torvalds 的问答
- 2025 年 4 月发表,对应 Git 的首次提交(2005 年 4 月)
我的补充:这一篇的价值不在怀旧,在于它印证了一个正确的核心模型能撑多久。Git 最初是 Linus 花了大约两周做出来的应急工具(BitKeeper 授权出问题,Linux 内核开发急需替代品),设计目标只有三条:快、分布式、不能悄悄损坏数据。
第三条尤其值得注意:"完整性"是 Git 从第一天就写进数据结构的东西,不是后来加的功能。每个对象的名字就是它内容的 hash,任何篡改都会导致 hash 不匹配。这个设计让 Git 顺手解决了一个当时不是主要目标的问题——你能验证拿到的代码就是作者写的代码。二十年后供应链攻击成为主要威胁,这个当初的副产品变成了核心价值。
对做技术决策的人,这里的可迁移经验是:在数据模型上多花时间,在界面上少花时间。Git 的界面(第一卷第 13 条)是公认的糟糕,但这没妨碍它赢;如果它的数据模型有妥协,再好的界面也救不回来。界面可以由第三方补(GUI、gh、IDE 集成,现在还有 AI 助手),数据模型没人能替你补。
59. What's next for Git? 20 years in, the community is still pushing forward
来源:Lee Reilly,GitHub Blog,2025 年 9 月 22 日。
读它解决什么:Git 接下来在往哪个方向走。
要点:
- 讨论 Git 社区当前的工作方向
- 与二十周年系列内容相关
我的补充:从这几年发布说明的走向看(第二卷那 20 条),Git 的演进有四条能看清的主线:
- 性能与规模化 —— reftable、sparse index、几何式重打包、multi-pack index。目标是让超大仓库可用。
- 界面现代化 ——
switch/restore拆分checkout、git config子命令化。方向是把历史包袱包起来而不是删掉(老命令永远保留)。 - 默认值的谨慎修正 —— 每次都是"先加配置项,几年后再考虑改默认"。这个节奏慢得让人着急,但它是 Git 二十年零重大破坏的原因。
- 供应链与完整性 —— SHA-256(第二卷第 36 条)、签名验证、归档稳定性(第 57 条)。
四条里最影响你的是第二条和第三条:新界面要主动用(老的不会消失,但你的肌肉记忆值得更新),新配置要主动开(升级不会自动给你,见本卷开头)。
至于 SHA-256 那条主线,我的判断是短期内不会有实质推进——互操作性问题(两种 hash 的仓库无法互推)没有解,而这是个纯工程量问题,需要有人投入很久。除非出现针对 Git 的实际 SHA-1 攻击,否则动力不足。
60. 20 Years of Git, 2 days at GitHub HQ: Git Merge 2025 highlights
来源:Lee Reilly,GitHub Blog,2025 年 10 月 9 日。
读它解决什么:Git Merge 2025 上讲了什么,社区当前在关心什么。
要点:
- Git Merge 2025 会议纪要,2025 年 10 月
- 二十周年主题,在 GitHub 总部举办
我的补充:Git Merge 是 Git 的主要社区会议,不定期办。这类会议纪要的实际用法是看方向而不是看细节:哪些议题被反复提,就是接下来几个版本会落地的东西。
想更深入跟进 Git 的话,按信息密度排序有三个来源:
- 邮件列表
git@vger.kernel.org(归档在public-inbox.org/git)—— 唯一的一手来源,每个特性的设计讨论都在这。缺点是流量极大、按主题检索困难。 - Git Rev News —— 社区维护的月度摘要,把邮件列表里值得注意的讨论挑出来。这是我认为性价比最高的跟进方式,一个月读一次十分钟。
- 本卷和第二卷收的 GitHub Blog —— 加工程度最高、可读性最好,但只覆盖 GitHub 团队关注的部分。
本卷小结:41–48 条是规模化(先按第 48 条的四维度框架诊断,再选对应手段,别在错误的维度上优化);49–52 条是工作流(worktree 和 remote.pushDefault 是这一段里最该立刻用起来的两个);53–57 条是安全(记住"不信任的仓库不要 --recurse-submodules"和"不要拿自动生成的归档做 hash 校验"这两条就够本了);58–60 条是社区回顾。
这一段没收进来的安全公告:2023 年 2 月、2023 年 1 月、2022 年 10 月、2022 年 4 月(safe.directory 那次,"dubious ownership" 提示就是它引入的)、2021 年 3 月、2020 年 4 月、2019 年 12 月。这些都在 github.blog/open-source/git/ 的第 2–4 页。它们的模式和已收的这几条一致,防御建议也相同,所以我没有逐条重复。另外 2020 年 4 月的 Junio Hamano 十五周年访谈、2022 年的 Git Merge 纪要也在那几页。
上一卷: ② Git 版本演进 · 下一卷: ④ 终端与 Shell 机制 →
