post-update-cmd备份脚本重复写入的根本原因是未做变更感知,必须通过Git commit hash比对或JSON哈希摘要判断composer.lock真实变动,再结合分支隔离路径实现幂等备份。

为什么 post-update-cmd 里的备份脚本容易重复写入
常见现象是每次 composer update 都追加一份元数据快照到 metadata-backup/ 目录,导致文件堆积、路径冲突甚至覆盖失败。根本原因在于脚本没判断「当前变更是否真有元数据变动」——哪怕只是 composer.json 注释改了一行,post-update-cmd 也会无差别触发。
Composer 不会自动帮你比对前后 composer.lock 差异,它只管执行钩子。所以备份逻辑必须自己做「变更感知」,否则就不是备份,是日志轮转。
用 diff + git hash 判断真实依赖变更
在脚本里直接读取上一次 lock 文件的 Git commit hash,并和当前比对,是最轻量且可靠的幂等依据:
- 先确保
composer.lock已提交:未git add composer.lock的变更不纳入备份范围 - 用
git log -n1 --format=%H -- composer.lock获取上次修改该文件的 commit ID - 对比当前工作区和该 commit 的
composer.lock内容差异:git diff --quiet $PREV_COMMIT -- composer.lock - 只有
diff返回非零(即有真实变更)时,才执行备份并生成新时间戳文件,例如metadata-backup/202611031827.json
PHP 类封装比 shell 脚本更适合做幂等判断
直接在 composer.json 的 scripts 里写一长串 shell 命令,很难处理 Git 异常、路径不存在、JSON 解析失败等边界情况。换成 PHP 类调用更可控:
- 用
file_get_contents('composer.lock')+json_decode()提取packages和packages-dev的哈希摘要,跳过时间戳等浮动字段 - 把摘要存成
.composer-metadata-hash,每次运行前比对,一致则exit(0) - 避免依赖
git命令:CI 环境可能没配置 Git 用户信息,或使用 shallow clone - 示例调用:
"post-update-cmd": "App\Scripts\MetadataBackup::run",类内可自由抛异常、打日志、控制退出码
备份目标路径必须隔离环境与时间维度
多个分支或 CI job 并发执行时,若都往 metadata-backup/latest.json 写,必然冲突。正确做法是:
- 路径包含 Git 分支名:
metadata-backup/{branch}/202611031827.json - 生产部署用
composer install --no-dev时,应额外标记环境:metadata-backup/prod-{branch}/... - 禁止硬编码绝对路径,全部基于
__DIR__或getcwd() - 备份前先
mkdir -p目录,但不要rm -rf metadata-backup/*—— 历史快照就是用来回溯的
真正的难点不在“怎么备份”,而在“怎么确认这次值得备份”。一旦漏掉哈希比对或分支隔离,再严谨的压缩格式、再完整的字段提取,都只是制造了更难排查的垃圾文件。


















