复杂项目的 git submodules 管理之道

2026-02-06 pv

在之前的 文章🔗 文章提到,Chromium 这样的一个庞大的项目,为了管理复杂的依赖,动用了诸多奇技淫巧。

借助工程化,对冲项目惯性增长的复杂性,几乎是一种常识。

Chromium 找到了它的解决方案,但对于普通项目呢?

其实也有,而且得到 Git 的原生支持:Git Submodules。

1. 背景

正常项目,只有一个 module。

但是保不齐,项目会越来越复杂,总不能所有代码都自己写吧,因此免不了有外部依赖。

有依赖,就需要管理。

很多编程语言都有成熟的包管理方案,Python 有 pip,Node.js 有 npm。抛开这些不谈,如果我想实现源码层面的依赖管理呢?

比如,在一个 Git 项目中,添加另一个 Git 项目作为依赖。

直接将源代码拷贝到主工程目录下,当然是一种手段,不过太粗糙,而且存在几点问题:

  1. 拷贝繁琐,易错
  2. 代码更新不及时
  3. 版本无法精准控制

于是诞生了 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 命令切换,tagcommit 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 里的真实文件放置在主工程某个版本下,而只是存储一个版本指针

Terminal window
路径 + commit hash

而且 Submodule 也不是直接放置在 .git 目录下,而是放在 .git/modules。不仅在逻辑上,在物理上也是完全切割。

切换主工程版本时,对应的 Submodule 版本会被检索到,.git/modules 也会做相应地切换。

小小一个指针,便搭建起强版本锁定和父子模块松散耦合的桥梁。

可见,巧妙地增加一个间接层,通常是一个好办法。

4. 总结

和第一次使用 Git 时受到的触动一样,Submodules 无疑是 Git 思想的再一次呈现:

管理版本,而非文件。

文件尽管繁多,但对应的版本永远只有一个。

管理好版本,就能管理好文件。正所谓四两拨千斤

尽管有些反直觉,因为对于普通用户,文件是显而易见的。

但是,真正的高手从来不会被表象迷惑,因为他们知道,有时候真正的答案,恰好藏在反直觉里。

(完)

参考

  1. Working with submodules - The GitHub Blog🔗
  2. Git - Submodules🔗
在 GitHub 上编辑本页面

最后更新于: 2026-02-06T01:46:09+08:00