Git Submodules 为何总让人头疼?

Git Submodules as a Package Manager

Git Submodules 为何总让人头疼?

上周我在使用 Git Worktree 时,发现它和 Git Submodules 的冲突导致无法移除工作树。这让我重新审视 Submodules 作为包管理器的可行性。虽然它拥有精确的 Commit SHA 锁定和类似 Manifest 的 .gitmodules 文件,但在依赖解析、存储隔离和更新机制上远不如现代包管理器完善。从 Rust 项目弃用 Submodules 的经历,到多次远程代码执行漏洞的爆发,Submodules 暴露了 Git 底层细节的复杂性。尽管社区正在尝试通过硬链接共享存储来优化,但缺乏统一的版本范围约束和安全的解析机制,使其体验在每一步都显得笨拙且充满风险。

Submodules 直接暴露了 Git 的内部细节:以对象 ID 作为锁定、更新后处于分离 HEAD 状态、特殊的目录布局以及 Manifest 中的传输 URL,而包管理器则是将这些机制封装在格式、解析器和本地缓存之后。
  1. unqueued

    顺便提一下,子模块(submodule)不一定非要用 gitfile 以及对应的 `$GIT_DIR/modules/<name>` 结构,不这么用其实有充分的理由。只要子模块里有个 `.git`,它既可以是符号链接,也可以是自包含的嵌入式仓库的普通 `.git` 目录。你依然可以在父仓库中使用相同的 gitlink 来代表它的 commit id,git 照样能管理它。

    如果我有 20GB 大小的子模块,我通常会直接执行 `git clone <url> <path> && git submodule add <path>`,git 对待它的方式是一样的。但现在我可以直接删除它,它就被彻底清除了。而且这种方式在某些方面更便携、更健壮,因为它不需要去解析 gitfile。我更喜欢我的仓库“底部更重”,而不是把 modules 文件夹搞得乱七八糟。

    我手头有个粗略的脚本,算是 `git submodule absorbgitdirs` 的逆向操作,不过有点脆弱。

    如果有一些底层机制(plumbing)能更好地暴露这种功能,那就太酷了。

  2. dissent

    如果你能把包管理器指向一个 git 仓库并像使用包一样使用它,那这只是一种偶然的便利。源代码仓库应该被设计为源代码,并通过构建步骤将其转换为包。对于某些语言,这意味着编译;对于其他语言,这意味着转译、压缩,或者仅仅是复制文件。生成的产物形态不同,专为消费而设计。

    如果你用 git 来做包管理,那么你的仓库就变成了包的边界。你就不再拥有从一个仓库生成多个包,甚至从多个仓库生成一个包的选项了。

  3. AlphaSite

    我知道这听起来像个玩具,但如果真有人突发奇想这么做:请从 npm 吸取教训,不要为项目的每次克隆都重复复制包。像所有理智的包管理器那样,在多个项目间共享它们。

  4. Myrmornis

    围绕子模块的许多困惑和批评,源于人们没有意识到:你不需要学习一大堆新命令就能使用它们:你只需要 cd 进入子模块,把它当作一个普通的 git 仓库来用。然后 cd 出来,如果你想的话,提交子模块的 hash 变更即可。

    有些东西确实让人恼火,比如删除它们。但总体而言,它们极其有用,而且设计得基本完全符合你的预期:它极其一致,因为大致来说,一旦你进入子模块内部,它的行为就和普通的 git 仓库一样。

  5. orliesaurus

    我就是有种感觉,开发者的体验(developer-experience)不会一样——但这也许只是个暂时的问题?

同日更多故事

2026-09-05