GitHub Actions を用いて Pull-Request で変更されたファイル一覧を取り出す方法

## はじめに

ベースブランチと Pull-Request の Diff をとるために [actions/checkout](https://github.com/actions/checkout) を **2 回実行して** 変更されたファイル一覧を取り出すシンプルな方法を紹介します。この方法は `fetch-depth: 0` オプションですべてのブランチ・タグを取得するよりも **3~4 倍高速です。**

## ワークフローのサンプル

``` .github/workflows/pr.yml
name: Check delta files
on: pull_request

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3 # まずこの PR のベースにチェックアウトしておくのがポイント
        with:
          ref: ${{ github.event.pull_request.base.sha }}
      - uses: actions/checkout@v3 # 次に Pull-Request の $GITHUB_SHA にチェックアウトする
      - name: delta files
        run: |
          git diff --diff-filter=AM --name-only \
            ${{ github.event.pull_request.base.sha }}..
```

## ポイントの紹介

[actions/checkout](https://github.com/actions/checkout) は [v2](https://github.com/actions/checkout/tree/v2) からはデフォルトで、そのイベントのトリガとなったコミット **1 つだけを** fetch するようになったため、ひと工夫しないとベースブランチと Pull-Request の HEAD 間の Diff がとれません。例えば [fetch-all-history-for-all-tags-and-branches](https://github.com/actions/checkout/blob/main/README.md#fetch-all-history-for-all-tags-and-branches) で紹介されている `fetch-depth: 0` ですべてのタグ・ブランチを取得して解決することは可能ですが、巨大なリポジトリの場合 `fetch-depth: 0` で fetch すると 10 分近くかかることがあります。

その代わりに [actions/checkout](https://github.com/actions/checkout) の `ref:` オプションを活用して 2 回チェックアウトする方法を思いつきました。

```
# The branch, tag or SHA to checkout. When checking out the repository that
# triggered a workflow, this defaults to the reference or SHA for that event.
# Otherwise, uses the default branch.
ref: ''
```

今回は Pull-Request で変更されたファイル一覧を得たいので、ベースブランチと Pull-Request の HEAD の 2 つの履歴を取得して比較する必要がありました。（以下図の B と E の比較）
ベースブランチの HEAD の SHA の取得については [webhook-events-and-payloads](https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#webhook-payload-example-35) を参考にしました。

![20230225-fig1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32736/ce3260e5-40f5-d028-e5a7-4c33a55a8abb.png)

## 実行時間の差

`fetch-depth: 0` オプションを使う場合と `ref:` を指定して 2 回チェックアウトを行う場合で、ワークフローの実行時間を比較しました。実行したリポジトリは巨大なカーネルリポジトリです。

ワークフローの差分は以下になります。

``` diff
    steps:
      - uses: actions/checkout@v3
-       with:
-         fetch-depth: 0
+       with:
+         ref: ${{ github.event.pull_request.base.sha }}
+     - uses: actions/checkout@v3 # Run again to checkout GITHUB_SHA
      - name: delta files
```

必要最小限なコミットしか取得しない後者の方が、実行時間が 3~4 倍早いことがわかりました。

| `fetch-depth:0` の場合 | `checkout` を 2 回行った場合 |
|:---|:---|
| 7m59s | 2m23s |

## 使用例

「変更されたファイル一覧」を活用する例を幾つか紹介します。
例えば変更されたファイル一つずつ対して Linter を実行する場合に利用できます。

https://qiita.com/osakiy/items/27d5382e41107de482a4

他には、特定の拡張子のファイルが変更されたら Bot コメントを送って目立たせることもできます。

``` .github/workflows/pr.yml
- name: sample comment
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    URL: ${{ github.event.pull_request.html_url }}
  run: |
    git diff --diff-filter=AM --name-only \
      ${{ github.event.pull_request.base.sha }}.. \
      -- '*.md' > ./updated
    test -s ./updated || exit 0
    sed -i '1i以下の .md ファイルが変更されました。\n' ./updated
    gh pr comment -F ./updated "${URL}"
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32736/cae1c27b-0412-babc-ede9-4ee1eadd605f.png)

https://qiita.com/osakiy/items/df80db383ce33b82c797

## おわり

`fetch-depth: 0` の実行時間が遅い巨大なリポジトリにおいて、どのように解消すればよいか模索していました。`run:` で煩雑なシェルスクリプトを書かなくてもシンプルに実現することができて良かったです。