Skip to main content

GitHub Actions: 変更されたファイルに対して処理をおこなう

GitHub Actions で、変更されたファイルに対して処理をおこなう方法です。

変更されたファイルを取得する

変更されたファイルを TARGETS 環境変数に改行区切りで取得します。

Push トリガーの場合

${{ github.event.before }} で push 前のコミットハッシュが取得できるため、それとの差分をチェックします。

on:
push:
branches:
- main

env:
BASE_DIR: "./"

jobs:
foo:
steps:
- name: checkout
uses: actions/checkout@v4

- name: check files
run: |
git fetch origin "${{ github.event.before }}" --depth=1
TARGETS=$(git diff -z --name-only --diff-filter=ACMR "${{ github.event.before }}.." "${{ env.BASE_DIR }}" | xargs -0 -I {} echo "{}")
printf "TARGETS<<EOF\n%s\nEOF\n" "${TARGETS}" >> $GITHUB_ENV
  1. git fetch で push 前のソースを取得
  2. git diff --name-only で、変更が加えられたファイル一覧を取得
    • -z オプションをつけて各ファイル名を NUL 文字で区切ります。これにより、スペースを含むファイル名も正しく区切ることができることに加え、日本語を含むファイル名がエスケープされず取得できます。
    • xargs -0 -I {} echo "{}" により、ファイル名を改行で区切り直します。
    • --diff-filter で対象を絞り込むことができます。(Added/Copied/Deleted/Modified/Renamed)
      • D を指定すると、削除されて存在しなくなったファイルも含みます。
    • 特定のファイル名だけ取得する場合は、さらに | grep "\.md$" || true をつけて絞り込むこともできます。(見つからない場合にエラーにしないよう注意)

Pull Request トリガーの場合

Pull Request イベントの場合、新規 Open 時点では github.event.before は設定されません(Open 後に追加 push した場合は設定される)。代わりにベースブランチ github.base_ref と比較することができます。この場合、push ごとに変更されたファイルでなく毎回 Pull Request 内で変更されたファイルすべてが処理対象となります。

on:
push:
pull_requests:
types:
- opened
- synchronize

env:
BASE_DIR: "./"

jobs:
foo:
steps:
- name: checkout
uses: actions/checkout@v4

- name: check files
run: |
git fetch origin "${{ github.base_ref }}" --depth=1
TARGETS=$(git diff -z --name-only --diff-filter=ACMR "origin/${{ github.base_ref }}.." "${{ env.BASE_DIR }}" | xargs -0 -I {} echo "{}")
printf "TARGETS<<EOF\n%s\nEOF\n" "${TARGETS}" >> $GITHUB_ENV

変更されたファイルに対して処理をおこなう

スクリプトでループする場合

ワークフロー内で TARGETS を改行で区切ってループするには、以下のように記述します。

      - name: process targets
run: |
while read -r target
do
echo "${target}"
done <<< "${TARGETS}"

カスタムアクションでループする場合

カスタムアクションに TARGETS を渡します。

      - name: process targets
if: ${{ env.TARGETS }}
uses: ./.github/actions/action-name
with:
targets: ${{ env.TARGETS }}

カスタムアクション (例: .github/actions/action-name/action.yml) で targets を受け取ります。

inputs:
targets:
description: target files
required: true
runs:
using: node20
main: index.js

カスタムアクションのスクリプト (例: .github/actions/action-name/index.js) 内で targets を改行で区切ってループします。

const process = require('node:process');

const targets = process.env.INPUT_TARGETS.split('\n');
for (const target of targets) {
console.log(target);
}