应该跑,而且必须加 --strict 和 --no-check-publish:--strict 强制校验 psr-4 路径存在、版本语法正确等语义错误,--no-check-publish 避免网络超时卡住钩子。

pre-commit 里该跑 composer validate --strict --no-check-publish 吗?
应该跑,而且必须加 --strict 和 --no-check-publish。默认的 composer validate 会放过 autoload 路径不存在、license 字段缺失等语义错误,起不到真正拦截作用。--strict 才强制校验 PSR-4 映射路径是否真实存在、require 包名是否合法、版本约束语法是否正确(比如 "^1.2" 写成 "^1,2" 就会被捕获)。--no-check-publish 是关键——它跳过对 Packagist 的连通性检查,避免网络抖动导致钩子卡住或误报失败。
常见错误现象:
• 钩子执行时卡在 “Checking packagist.org…” 几秒后超时退出
• composer.json 中 "autoload": {"psr-4": {"App\": "src/"} },但 src/ 目录根本不存在,却没报错
• 提交成功了,但 CI 上 composer install 失败,提示 “The lock file does not contain required package xyz”
- 务必加
--no-interaction,否则遇到 warning(如 license 缺失)会等待输入,阻塞 commit - 只在
composer.json被修改时才运行:用git diff --cached --quiet -- composer.json做前置判断,避免每次提交都无谓执行 - 脚本开头加
cd $(git rev-parse --show-toplevel),确保命令在项目根目录执行,否则composer.json可能被找不到
为什么 git diff --quiet composer.lock 不能可靠检测未暂存变更?
它只比对暂存区和 HEAD,完全看不到工作区里“已修改但没 git add”的状态。也就是说,开发者改了 composer.json,跑了 composer install 生成新 composer.lock,却忘了 git add composer.lock —— 这个场景下 git diff --quiet composer.lock 返回 0(静默成功),钩子放行,但 push 后 CI 构建必然失败。
真正可靠的判断是:git status --porcelain | grep '^[M?] composer.lock$'
• M 表示已修改未暂存
• ?? 表示文件新增且未跟踪(比如第一次生成 composer.lock)
二者都该中止提交
- Windows 下 Git Bash 的
grep对换行符敏感,建议用git status --porcelain -z | grep -z '^M composer.lock$'更健壮 - 别把检查逻辑写在
pre-commit里就完事——这个检查更适合放在pre-push钩子,作为最后一道防线 - 如果用了
composer install --no-dev自动更新composer.lock,钩子必须在 install 后立刻执行上述检查,否则无法捕获刚生成的变更
.git/hooks/pre-commit 权限和路径问题怎么避坑?
Linux/macOS 下,.git/hooks/pre-commit 必须有可执行权限(-rwxr-xr-x),否则 Git 直接忽略它;Windows 下 Git Bash 默认不执行 .sh 脚本,而 PowerShell 或 cmd.exe 又难处理命令链和错误码。最稳妥的做法不是硬扛 shell,而是统一用项目内可管理的方式。
- 手动修复权限:
chmod +x .git/hooks/pre-commit(Mac/Linux) - 避免全局
composer命令:钩子脚本里优先用php composer.phar或./vendor/bin/composer,防止本地环境 Composer 版本与项目要求不一致 - 不要依赖
cd切目录:Git hooks 默认在工作区根目录执行,但某些 IDE 内置终端或 Windows 环境 cwd 不稳定,所有路径用./vendor/bin/composer这种相对写法更安全 - 如果团队混用 Windows/Mac/Linux,放弃 shell 脚本,改用
captainhook或brainmaestro/composer-git-hooks,它们生成的钩子会自动处理路径和权限
CI 和本地 pre-commit 校验逻辑要保持一致吗?
必须一致,否则会出现“本地能过,CI 报错”的割裂体验。但注意:CI 环境通常禁用交互、跳过耗时操作,而本地钩子可以更激进些。
- CI 中
composer validate可以省略--no-interaction(本来就不支持交互),但--strict和--no-check-publish仍需保留 - 本地
pre-commit不适合跑composer update --dry-run:它要遍历所有依赖,慢且非必要;git status --porcelain检查composer.lock更快更准 - 如果项目用了自定义 Composer 插件,CI 和本地都要确认插件版本一致,否则
validate输出可能不同(比如某个插件重写了 autoload 校验逻辑) - 最关键的同步点是:
composer.lock是否被提交、是否与composer.json同步——这个判断逻辑(git status --porcelain)在 CI 的pre-push模拟环节和本地钩子里应完全一致
最容易被忽略的是:钩子脚本里没显式指定 composer.phar 路径,导致不同机器上用了不同版本的 Composer,校验结果不一致。直接在项目根目录放一个 composer.phar,并在钩子里调用它,是最可控的做法。











