vendor 目录下出现 .git 文件夹是 Composer 在 prefer-source 模式或上游打包不规范时的正常现象;安全清理需同步删除 .git、更新 autoload 映射并刷新 OPcache/ APCu 缓存,否则将复发或导致类加载失败。

vendor 目录下出现 .git 文件夹不是“异常”,而是 Composer 在特定安装模式或上游打包不规范时的必然产物;清理它不能靠一次 rm -rf,必须同步处理锁文件、autoload 映射和缓存,否则很快复发或引发类加载失败。
为什么中文项目里 vendor/.git 更容易出问题
中文开发环境常因以下原因放大 .git 问题:
- 本地调试时习惯用
composer install --prefer-source查看源码,但网络不稳定(尤其访问 GitHub)导致git clone中断,留下残缺.git(有目录无HEAD或objects) - 团队成员在
vendor/内直接改代码、提交到自己 fork 的分支,却没同步更新composer.lock,下次composer install强制git reset --hard覆盖修改 - 部分国内镜像源(如腾讯云、阿里云)同步 dist 包时未严格过滤
.git,或上游包作者用git archive打包漏了--no-git-dir - IDE(如 PHPStorm)默认开启 Git 集成,一打开项目就扫描整个
vendor/,遇到残缺.git直接卡住或报错 “fatal: not a git repository”
定位哪个包的 .git 在捣鬼
别猜,直接看 Composer 的 verbose 输出或用系统命令定位:
- 运行
composer install -v,错误行末尾通常带路径,例如:Cloning into 'vendor/monolog/monolog'→ 问题就在monolog/monolog - 执行
find vendor -name ".git" -type d -exec ls -A {}/HEAD \; 2>/dev/null | grep -v "No such file"—— 只列出那些有HEAD的完整.git,其余都是残缺的,优先清理 - 检查
composer.lock中对应包的source字段:如果"type": "git"且"reference"是 commit hash(不是 tag),说明这个包是 source 模式安装的,.git是它的一部分,删了会破坏一致性
安全清理 .git 的三步闭环
单删目录只是治标,真正安全要同时做三件事:
- 删文件:
find vendor -name ".git" -type d -exec rm -rf {} +(仅限你确认没在vendor/里做过本地 commit 的项目) - 清映射:删完立刻运行
composer dump-autoload --optimize --classmap-authoritative,确保vendor/composer/autoload_classmap.php里不再引用任何.git下的路径(比如某些包把测试工具放在tests/bootstrap.php并写进了 autoload) - 刷缓存:如果用了 OPcache,必须调用
opcache_reset();若用 APCu,执行apcu_clear_cache(),否则旧的 autoload 映射还在内存里,删了文件也照样报Class not found
永久避免 .git 回归的配置组合
靠手动清理永远被动,关键在让 Composer 从源头不放 .git 进来:
- 全局禁用 source:
composer config --global preferred-install "dist"(影响所有新项目) - 当前项目锁定 dist:
composer config preferred-install "dist"(写入本地composer.json的config字段) - 部署时强制兜底:
composer install --no-dev --prefer-dist --remove-vcs --optimize-autoloader --classmap-authoritative,其中--remove-vcs是最可靠的内置开关,比archive.excludes更底层,能确保解压后、写入前就把.git剔干净 - 注意例外:
path类型仓库不受以上控制——只要你的repositories里写了"type": "path"且路径含.git,它就会被原样复制进来,这种场景只能靠 CI 脚本用rsync --exclude=.git同步
真正麻烦的从来不是 .git 文件夹本身,而是它在 composer.lock 里留下的 commit hash、在 autoload 映射里注册的测试路径、以及 OPcache 里缓存的旧类名——三者不联动清理,删了也白删。


















