复杂项目的 git submodules 管理之道
2026-02-06
在之前的 文章🔗 文章提到,Chromium 这样的一个庞大的项目,为了管理复杂的依赖,动用了诸多奇技淫巧。
借助工程化,对冲项目惯性增长的复杂性,几乎是一种常识。
Chromium 找到了它的解决方案,但对于普通项目呢?
其实也有,而且得到 Git 的原生支持:Git Submodules。
1. 背景
正常项目,只有一个 module。
但是保不齐,项目会越来越复杂,总不能所有代码都自己写吧,因此免不了有外部依赖。
有依赖,就需要管理。
很多编程语言都有成熟的包管理方案,Python 有 pip,Node.js 有 npm。抛开这些不谈,如果我想实现源码层面的依赖管理呢?
比如,在一个 Git 项目中,添加另一个 Git 项目作为依赖。
直接将源代码拷贝到主工程目录下,当然是一种手段,不过太粗糙,而且存在几点问题:
- 拷贝繁琐,易错
- 代码更新不及时
- 版本无法精准控制
于是诞生了 Git Submodules,专门用来管理不同 Git 项目间的依赖关系。
2. 最佳实践
先看一个例子。
我有一个 A 项目,需要依赖另一个 B 项目。
通过 git submodule add https://github.com/<user>/B B ,可将 B 添加到 A 项目中。
此时目录中出现 .gitmodules ,记录这次新增依赖。
[submodule "B"] path = B url = https://github.com/<user>/B执行 git submodule update --init --recursive,便可在 A 目录下看到 B 项目最新的代码(新版本 Git 会在上一个命令后默认执行)。
更新也有办法, git submodule update --remote,另外记得 add & commit,否则这次更新不会记录在版本库中。
刚才提到的两个问题,拷贝和更新,几个 git submodule 命令,轻松解决。
如果我想固定某个版本怎么办?
其实也简单。进入到 B 目录,用常规的 Git 命令切换,tag 或 commit id 皆可。另外别忘了 add & commit。
如果想查看某个主工程版本对应的 Submodule 版本,用 git ls-tree <commit id> B。
至此,你获得了,对于项目 A 的每一个版本,都有 B 的版本对应。你可以在任意版本间自由游走,不再需要考虑依赖和版本对齐的问题。
从使用上看,Git Submodule 似乎并不难用。
3. 原理
知道了 Know-How,更重要的,是要知道 Know-Why。
Git Submodule 保证了:主工程在某一次 commit 上,必须配套某一个确定的依赖 commit。
熟悉 Git 的人都知道,对于主工程,Git 借助指针文件访问不同版本(这里涉及到更深层次的 Git 领域知识,即 .git 文件夹里的组织结构,后续有时间会详细聊聊)。
对于 Submodule,同样如此。
有点反直觉,实际上 Git 并不会将 Submodule 里的真实文件放置在主工程某个版本下,而只是存储一个版本指针:
路径 + commit hash而且 Submodule 也不是直接放置在 .git 目录下,而是放在 .git/modules。不仅在逻辑上,在物理上也是完全切割。
切换主工程版本时,对应的 Submodule 版本会被检索到,.git/modules 也会做相应地切换。
小小一个指针,便搭建起强版本锁定和父子模块松散耦合的桥梁。
可见,巧妙地增加一个间接层,通常是一个好办法。
4. 总结
和第一次使用 Git 时受到的触动一样,Submodules 无疑是 Git 思想的再一次呈现:
管理版本,而非文件。
文件尽管繁多,但对应的版本永远只有一个。
管理好版本,就能管理好文件。正所谓四两拨千斤。
尽管有些反直觉,因为对于普通用户,文件是显而易见的。
但是,真正的高手从来不会被表象迷惑,因为他们知道,有时候真正的答案,恰好藏在反直觉里。
(完)
参考
- 本文作者:Plantree
- 本文链接:https://plantree.me/blog/2026/git-submodules/
- 版权声明:所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
最后更新于: 2026-02-06T01:46:09+08:00