composer如何在CI中仅当特定路径变更时才运行composer install?(changed-files过滤)

ci中应通过git diff检查composer.json或composer.lock是否被修改来决定是否执行composer install,需确保git历史深度足够(如fetch-depth: 2),并用命令替换判断输出非空以避免退出码误判。

composer如何在CI中仅当特定路径变更时才运行composer install?(changed-files过滤)

CI里怎么用git diff找出被修改的PHP依赖文件

直接靠 composer install 自身没法判断路径变更,得靠 Git 提前筛出是否动了 composer.jsoncomposer.lock。CI 脚本里最稳妥的方式是检查这两个文件是否在本次提交中被修改过。

  • 推荐用 git diff --name-only HEAD^ HEAD | grep -E '^(composer.json|composer.lock)$',注意要确保 CI 环境有足够 git 历史(比如 fetch-depth: 2
  • 如果用 GitHub Actions,actions/checkout@v4 默认只 fetch 当前 commit,必须显式加 fetch-depth: 2,否则 HEAD^ 会失败
  • GitLab CI 可用 $CI_COMMIT_BEFORE_SHA 替代 HEAD^,更可靠: git diff --name-only $CI_COMMIT_BEFORE_SHA $CI_COMMIT_SHA | grep -E '^(composer.json|composer.lock)$'

Shell 判断逻辑写法别漏掉空输出和退出码

很多人用 grep 后直接跟 && composer install,但 grep 没匹配时默认退出码是 1,会导致整个命令链中断或误判——尤其在 set -e 环境下容易静默跳过安装。

  • 正确做法是用命令替换捕获输出,再判断非空:if [ -n "$(git diff --name-only HEAD^ HEAD | grep -E '^(composer.json|composer.lock)$')" ]; then composer install; fi
  • 别用 grep -q 单独判断,它不输出内容,但退出码逻辑在管道里容易受上一个命令影响(比如 git diff 本身失败时)
  • 如果 CI 使用缓存(如 Composer cache in GitHub Actions),记得 composer install --no-interaction --prefer-dist,避免交互卡住或本地配置干扰

为什么不能只看 changed-files 列表就跳过 install

有些团队试图解析 PR 中所有变更文件,只要没改 PHP 文件就跳过 composer install,这是危险的。依赖变更的影响不局限于代码路径,而在于锁文件是否生效。

  • composer.json 里改了 require-devconfig.platform,不改业务代码但会影响测试环境行为
  • composer.lock 被 rebase 或手动编辑过,即使没 diff 出差异,也可能导致依赖版本实际不一致
  • 某些 CI 场景(如矩阵构建)需要干净 vendor,哪怕没改依赖文件,也建议加个开关控制是否强制 install,比如用 if [[ "$FORCE_COMPOSER_INSTALL" == "true" ]]; then ...

GitHub Actions 示例片段要注意 job-level 权限和缓存键

steps 里嵌入判断时,别把 composer install 放进 if 条件的 run 字段里拼接长字符串,可读性差还难调试。

  • 推荐拆成两个 step:Check dependency changes 输出 env 变量,再用 if: env.HAS_COMPOSER_CHANGE == 'true' 控制下一步
  • 缓存 key 别只用 composer.lock hash,应包含 PHP 版本和平台配置,例如:php-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
  • 如果项目用了 platform.config 或自定义 repositories,这些变化不会反映在 lock 文件里,需额外监控相关配置项

实际跑起来最常被忽略的是 Git 历史深度和 lock 文件的二进制变更识别——比如 Windows 换行符改动或 lock 文件里时间戳字段微调,都可能让 git diff --name-only 漏掉真实变更。