composer不自动克隆git子模块,需手动或通过post-install-cmd钩子执行git submodule update --init --recursive,或改用path/package类型仓库、私有composer仓库统一管理依赖。

Composer require 时 Git 子模块不自动 clone 怎么办
Composer 本身不解析或递归 clone Git 仓库里的 .gitmodules,它只读取目标仓库根目录下的 composer.json,然后按需下载该仓库——子模块完全被忽略。这不是 bug,是设计如此。
常见现象:你用 vcs 类型配置了一个私有 Git 仓库 A,A 里通过 git submodule add 引入了另一个私有仓库 B;执行 composer install 后,A 被拉下来了,但 B 的目录空着,git status 显示未跟踪的子模块路径。
- 必须手动运行
git submodule update --init --recursive(在 A 的本地工作目录下) - 如果 A 是作为依赖被安装到
vendor/下的,它的.git目录默认被 Composer 删除(除非显式设置"preferred-install": "source"),此时子模块根本无法初始化 - 解决方案只有两个:要么把 B 打包成独立 Composer 包并发布到私有仓库,让 A 在
require中声明它;要么放弃子模块,改用path或package类型仓库手动定义 B
vcs 仓库中含子模块时认证失败的典型原因
Git 子模块地址如果是 git@xxx:org/repo.git 这类 SSH 地址,而主仓库用的是 HTTPS + token 方式认证,就会出问题:Composer 拉下主仓库后,git submodule update 会单独发起 SSH 连接,但系统没配好对应密钥,报错 Permission denied (publickey)。
更隐蔽的情况是子模块用了相对 URL(如 ../repo-b.git),而父仓库 clone 到 vendor/ 后,相对路径指向错误位置,导致 git submodule sync 失败。
- 统一用 HTTPS + token 地址:把子模块的 URL 改成
https://<token>@gitlab.internal/org/repo-b.git</token>,并在父仓库的.gitmodules中硬编码该 URL - 避免相对路径:子模块 URL 必须是绝对可解析的,不能依赖 clone 时的上下文
- 不要指望 Composer 自动透传认证:它只对主仓库请求带 auth header,子模块的 git 操作完全走本地 git 配置
想让子模块也走 Composer 管理,得绕开 vcs 类型
如果你真正想要的是“用 Composer 统一管理多个私有 Git 仓库间的依赖关系”,vcs 类型不是正解。它只支持单点命中,不支持依赖图展开。
正确路径是把每个子模块都注册为独立 Composer 包,并统一托管在私有 Composer 仓库(如 Satis、Private Packagist 或 Artifactory)中。这样 A 的 composer.json 就能正常写 "require": {"org/repo-b": "^1.0"},Composer 会自己去私有源查、下、装,完全绕过子模块机制。
- 每个子模块必须有合法的
name字段(格式vendor/name),且不能和主项目重名 - 私有 Composer 仓库必须已索引这些包(比如 Satis 要跑
build,Private Packagist 要连通 Git webhook) - 主项目的
repositories数组里,私有 Composer 仓库必须排在packagist.org前面,否则公共包可能被错误 fallback 到私有源
调试子模块相关问题必用的两个命令
光看 composer install -vvv 不够,它不会打印子模块操作日志。真要定位问题,得进 vendor 里手动触发:
- 先确认主仓库是否以 source 方式安装:
composer config --list | grep preferred-install,如果不是source,加一行"preferred-install": {"*": "source"}到composer.json - 进入
vendor/org/package-a目录,运行git submodule status—— 如果输出为空或显示-开头,说明子模块没初始化 - 再跑
git submodule update --init --recursive --verbose,观察具体哪一步卡住:是认证失败?URL 解析失败?还是权限不足?
子模块不是 Composer 的一部分,它是 Git 的功能。想靠 Composer “自动搞定”嵌套 Git 结构,本质上是在要求两个不同工具链强行耦合——容易踩坑的地方,往往就藏在这层边界上。











