当我们谈论版本控制系统的存储后端时,文件系统几乎是唯一的答案。Git 将对象存储在 .git 目录中,Subversion 使用 Berkeley DB 或 FSFS,而 Mercurial 则是对应的_bundle_格式。然而,一个名为 pgit 的开源项目正在挑战这一传统认知 —— 它将完整的 Git 仓库历史存储在 PostgreSQL 数据库中,使得版本控制历史变成了可被 SQL 直接查询的数据。

从 Delta 压缩到数据库存储

pgit 的诞生源于一个简单的问题:Delta 压缩技术能否应用于数据库层?开发者在构建 xpatch 库时意识到,绝大多数应用程序存储的版本化数据(文档编辑器、审计日志、配置历史)都与前一个版本存在 99% 的相似性,但数据库层面从未原生支持这种压缩能力。

为了验证这一想法,开发者首先尝试了 SQLite,但其扩展 API 限制较多,自定义存储的写入性能令人失望。PostgreSQL 则完全不同 —— 它的扩展系统足够强大,允许开发者编写自定义表访问方法(Table Access Method),这本质上相当于为数据库创建一个自定义存储引擎。最终产出的成果是 pg-xpatch,它被设计为 PostgreSQL 的一个扩展,能够对表数据进行透明的 Delta 压缩。

当数据写入时,pg-xpatch 自动只存储连续版本之间的差异;当你读取时,它透明地重建完整内容。这切断了传统数据库存储与压缩之间的鸿沟 —— 用户只需编写普通 SQL 语句,完全感知不到底层的压缩细节。

存储模型设计:面向查询优化的表结构

pgit 的核心创新在于其表结构设计,它将 Git 的对象模型映射为关系型表,同时充分利用 PostgreSQL 的查询能力。在 pgit 中,仓库的完整历史被拆解为多个关联表:提交记录表(存储哈希、作者、时间戳、消息)、路径表(所有文件路径的映射)、文件引用表(连接提交与具体文件路径),以及文件内容表(实际存储的 Delta 压缩数据)。

这种设计的精妙之处在于元数据与内容分离。路径、引用和哈希等元数据使用普通的堆表存储,查询速度极快;只有实际的文件内容才会触碰 Delta 压缩表。Primary Key 查找和顺序扫描性能优异,但涉及 Delta 链的 JOIN 和 COUNT 操作则需要谨慎处理。在 79,765 个提交的 git/git 仓库上,show 命令耗时 0.23 秒,diff 耗时 0.18 秒,blame 在缓存预热后仅需 0.7 秒 —— 这说明只要查询模式设计得当,压缩存储并不会成为性能瓶颈。

压缩率实测:超越 git gc 的极限

对于任何版本控制系统,存储效率都是核心指标。pgit 面临的最直接挑战是:与 Git 成熟的对象打包机制相比,PostgreSQL 方案能否在压缩率上具有竞争力?

在涵盖 6 种语言、共 273,703 个提交的 20 个真实仓库基准测试中,结果令人惊讶。pgit 在 12 个仓库上超越了 git gc --aggressive(Git 能做到的最佳压缩模式):serde(30% 优势)、ripgrep(10%)、fzf(14%)、flask(10%)。在其余 8 个落败的仓库中,差距通常较小,平均仅 2-7%—— 唯一的例外是 prettier(38% 差距)和 git/git 本身(23% 差距)。

这种差异背后的原因清晰可见。Git 的 Packfile 格式允许跨任意对象进行 Delta 压缩,无论文件路径如何;但 pgit 仅在同一文件的版本链内进行压缩,或者通过路径关联(重命名、复制)将相关文件编入共享链。pgit 在增量编辑为主的仓库表现优异,因为同一文件的连续版本天然具有高度相似性;而在跨文件相似度高的仓库(如 prettier 的格式化代码)中,Git 的全局 Delta 匹配策略占上风。

关键洞察是:仅靠目标明确的去重策略(将相关文件编入共享 Delta 链),就已在多数场景下击败 Git 的最优模式,同时额外获得了 SQL 可查询性这一重大优势。

SQL 可查询性:从工具到智能分析平台

将 Git 历史存入数据库的本质价值,远不止于压缩。当你的提交记录、文件版本和变更模式都可以用 SQL 表达时,一系列此前需要复杂脚本和外部工具链才能完成的分析任务,现在只需一条查询即可解决。

pgit 内置了最常见的代码库分析功能:churn(修改频率)找出最常变动的文件,coupling(耦合度)发现总是共同修改的文件对,hotspots(热点)按目录聚合 churn 数据,bus-factor(总线因子)识别只有单一贡献者的文件,activity(活跃度)追踪提交随时间的变化趋势。这些命令都支持 --json 输出,方便程序化调用,而 pgit sql 命令允许用户直接写原始 SQL。

一个典型的 coupling 查询会 JOIN 文件引用表两次来找出同一次提交中共同修改的文件对,然后按出现次数排序。结果可能揭示出隐藏的依赖关系 —— 例如 tenant.rstimeline.rs 在 289 次提交中共同修改,这远超其他文件对的频率,暗示两者之间存在强耦合,修改其中一个时几乎必然需要修改另一个。

AI Agent 的完美数据源

pgit 最有前景的应用场景或许在于 AI 编码 Agent。当前的 Agent 已经能读取、编写代码并执行测试,但对代码库历史的理解仍是短板 ——Agent 不知道某个文件在过去一个月被回滚了 5 次,不知道修改 tenant.rs 时通常需要同步更新 timeline.rs,也不了解某个函数在过去两年每个季度都在增长。

pgit 与 Agent 的契合点在于:模型天然能够编写 SQL,或者至少可以将自然语言需求转化为查询。当开发者给 Claude 一个简短提示,要求分析 Neon 数据库仓库并生成代码库健康报告时,Agent 在 9 分 36 秒内完成了完整工作:自动发现 pgit 命令、导入 8,471 个提交、编写优化的 SQL 查询(遵循官方性能指南)、并交付包含修改频率、文件耦合、规模趋势和当前最大文件等维度的报告。

这意味着代码库分析不再是昂贵的定制工具或第三方服务的专利,而变成了可以随时用自然语言提出的需求。内置的 pgit analyze 命令进一步降低了门槛 ——Agent 可以直接调用 pgit analyze churn --json 获取结构化数据,无需编写 SQL。

技术边界与适用场景

pgit 并不是要取代 Git 用于日常开发。GitHub、CI/CD、IDE 集成和合并工具的生态系统无可替代,pgit 无意与之竞争。它的定位是理解代码库历史的工具:将版本控制数据转化为可分析、可查询的数据库,使得代码库健康检查、趋势追踪和 Agent 驱动的洞察成为可能。

对于追求极致压缩且跨文件相似度高的项目,传统 Git 仍是更好的选择。但对于需要进行历史分析、构建自动化报告或让 AI Agent 理解代码演进的团队,pgit 提供了一种全新的数据层思路。其底层技术 pg-xpatch 的潜力更大 —— 它不局限于版本控制,可应用于任何需要存储版本化数据的场景,如文档编辑器、审计日志、配置快照和 CMS 内容历史。


参考资料