git本身没有pre-merge钩子,merge阶段自动检查应挂pre-push端——它能覆盖本地merge、rebase、cherry-pick后的推送行为,精准校验head与远程分支差异,且不依赖服务端权限;pre-commit仅校验当前提交,易漏检未暂存的merge变更。

Git钩子该挂在哪一端:pre-merge还是post-merge?
Git本身没有pre-merge钩子,所谓“Merge阶段自动检查”,实际只能靠pre-commit(对本地合并后暂存的变更)或pre-push(更推荐)来拦截。CI/CD流程中则常用pre-receive(服务端钩子),但需要Git服务器权限。多数团队选pre-push——它能覆盖本地merge、rebase、cherry-pick后的推送行为,且不依赖服务端配置。
-
pre-commit只校验当前提交,若merge产生新冲突但未add,会漏检 -
pre-push检查HEAD与远程目标分支差异,更能反映“即将上线的代码状态” - 服务端
pre-receive最可靠,但需运维介入,且PHP项目常跑在共享主机上无法部署
怎么用Composer验证lock文件是否过期?
核心是composer validate --no-check-publish + composer install --dry-run组合。前者只校验composer.json和composer.lock语法与结构一致性;后者真正模拟安装过程,若lock里记录的哈希与当前仓库不匹配(比如有人手动改了json但没更新lock),就会报错Lock file is not up to date。
-
--dry-run不写入vendor,仅输出差异,适合钩子场景 - 必须加
--no-dev参数(如果CI环境不装dev依赖),否则可能因缺少phpunit等包而误报 - 若项目用了
platform配置(如"platform": {"php": "8.1"}),确保钩子执行环境PHP版本一致,否则--dry-run会因平台约束失败
钩子脚本里如何避免误伤CI流水线?
CI系统(如GitHub Actions、GitLab CI)通常设置CI=true环境变量。钩子脚本第一行应判断:if [ -n "$CI" ]; then exit 0; fi。否则CI机器执行git push时也会触发本地钩子,而CI环境往往没装Composer或PHP,直接崩溃。
- 不要依赖
which composer——有些CI用Docker镜像,路径可能是/usr/bin/composer或vendor/bin/composer - 推荐用
php $(git rev-parse --show-toplevel)/composer.phar install --dry-run,把composer.phar纳入版本控制,彻底摆脱全局安装依赖 - 钩子返回非0码才会中断推送,所以检查命令后必须显式
exit 1,不能只靠composer install --dry-run的退出码(它失败时确实是1,但保险起见仍建议封装判断)
为什么有时lock文件明明更新了,钩子还报错?
常见于Windows换行符(CRLF)与Linux(LF)混用:composer.lock是JSON,Git默认对文本文件做换行符转换,导致哈希不一致。解决方案只有两个:
- 全局关闭Git自动换行:
git config --global core.autocrlf false(团队需统一) - 或在项目根目录加
.gitattributes,强制composer.lock按二进制处理:composer.lock -text
另一个隐形坑是composer install默认读取COMPOSER_HOME下的配置,如果某人本地设了cache-dir或github-oauth,可能导致--dry-run行为不一致。钩子中应临时重置:COMPOSER_HOME=/dev/null composer install --dry-run。
锁文件一致性不是“有没有改”的问题,而是“改得对不对”的问题。只要涉及composer.json变更,就必须由composer update或composer require生成新lock,手工编辑lock几乎必然出错。











