この記事はZOZO AdventCalender 2024シリーズ5の5日目の記事です。
GitHub Actions Tips
本記事では筆者がGitHub Actionsでworkflowを実装した際に得た知見を共有します。
主にactions/checkoutに関連した高速化についてのTipsを紹介したいと思います。
大規模リポジトリで使うactions/checkout
GitHub Actionsでworkflowを実装する際には、まずリポジトリをチェックアウトするためにactions/checkout
を使うことが多いと思います。
actions/checkout
はGitHub Actionsの公式アクションで、リポジトリをチェックアウトするためのアクションです。
しかし、大規模なリポジトリでデフォルト設定のまま使うと、チェックアウトに時間がかかりすぎてしまうことがあります。
なお、デフォルト設定でもfetch-depth
は1に設定されているため、最新コミットのみを取得するShallowClone自体は有効になっています。
そのworkflow、本当にリポジトリのファイル全部必要ですか?
workflowの処理がリポジトリのファイル全てを必要としない場合、sparse-checkout
が有効です。
sparse-checkout
は、リポジトリ内の特定のディレクトリやファイルのみを指定してチェックアウトすることができる機能です。
例えば.github
ディレクトリとsrc
ディレクトリ以下のファイルしか使わない場合、以下のように設定します。
※ この設定自体はREADMEにも記載されています。
- name: Checkout
uses: actions/checkout@v4
with:
sparse-checkout: |
.github
src
この設定を有効にすることで、workflowで必要なファイルに絞ってチェックアウトすることができるため、チェックアウトにかかる時間を短縮することができます。
大量のファイルがコミットされているリポジトリで特に有効です。
筆者の試したリポジトリでは、数分かかっていた処理が数秒で終わるようになりました。
そのworkflow、そもそもチェックアウト自体必要ですか?
workflowの処理が
- リポジトリ内のファイルは使わない
- 履歴が必要(diffやPRの差分を見るなど)で
fetch:depth: 0
でフルクローンしてる
というケースだと、チェックアウトしているけど時間をかけてダウンロードしたファイルを使っていないという無駄が発生しています。
こういったケースでは、actions/checkout
自体使うのをやめましょう。
ではどうするか?
actions/checkout
を使わずにdiffやPRの差分を取得したりするには、GitHub APIを使う方法があります。
GitHub Actionsのデフォルトのランナーには、GitHub CLI
があらかじめインストールされているため、GitHub CLIを使ってGitHub APIを叩くことができます。
例えばPullRequestの差分を取得する場合、Pull Request Eventをトリガーとしたworkflowで以下のように差分を取得することができます。
on:
pull_request:
types: [opened, synchronize, closed]
jobs:
diff:
runs-on: ubuntu-latest
steps:
- name: Get diff
env:
GH_TOKEN: ${{ github.token }}
run: |
gh api /repos/${{ github.repository }}/pulls/${{ github.event.number }}/files --jq '.[] | .filename'
このようにGitHub APIを使うことで、リポジトリをチェックアウトすることなく、リポジトリの情報を取得することができます。
workflowの処理がリポジトリのファイル自体を必要としない場合、actions/checkout
を使わずにGitHub APIを使うことで、チェックアウト自体を省略できるので大幅に処理時間を短縮することができます。
なお、gh pr diff
コマンドでPullRequestの差分を取得することもできますが、gh pr
を使う場合、.git
ディレクトリが必要なため、actions/checkout
を使って.git
ディレクトリだけ先にチェックアウトする必要があります。
応用
ここまでで一部のディレクトリ・ファイルだけ必要なケース、ファイルは必要ないけどリポジトリの情報は必要なケースを紹介しましたが、一部のディレクトリ・ファイルだけ必要だけど必要なものはPullRequestの差分を取得しないとわからず、事前にsparse-checkoutの設定をするのが難しいというケースもあるかと思います。
そういった場合は、はじめにGitHub APIで差分を取得して、sparse-checkoutの設定を動的に行うという方法を紹介したいと思います。
サンプルコードは以下の通りです。
on:
pull_request:
branches:
- main
types: [opened, synchronize, closed]
jobs:
get-diff:
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{github.token}}
steps:
- name: Get diff
id: get-diff
run: |
diff=$(gh api /repos/${{github.repository}}/pulls/${{github.event.number}}/files --jq '.[] | .filename')
{
echo 'diff<<EOF'
echo "${diff}"
echo EOF
} >> "${GITHUB_OUTPUT}"
echo "${diff}" >> "${GITHUB_STEP_SUMMARY}"
- name: checkout
uses: actions/checkout@v4
with:
sparse-checkout: |
${{ steps.get-diff.outputs.diff }}
- name: tree
run: tree >> "${GITHUB_STEP_SUMMARY}"
stepごとに解説していきたいと思います。
-
Get diff
GitHub APIを使ってPullRequestの差分を取得します。--jq '.[] | .filename'
でファイル名のみを取得しています。
また、GITHUB_OUTPUT
はそのままでは改行を含んだ文字を出力できませんが、区切り文字で囲って出力することで改行を含んだ文字を出力することができます。
参考: https://docs.github.com/ja/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#multiline-strings -
checkout
Get diff
で取得した差分をsparse-checkoutの設定としています。
本記事の例では、差分のファイルパスをそのままsparse-checkoutの設定としていますが、Get diff
のステップでファイルが含まれるディレクトリを取得して出力することで、ディレクトリを指定してsparse-checkoutの設定をすることも可能です。 -
tree
実際にどういうファイルがチェックアウトされたかを確認するために、tree
コマンドを実行しています。
出力結果は以下の通りです。
A/1/test.txt
とA/2/test.txt
の2ファイルに修正を加えたPullRequestを作成し、このworkflowを実行した結果です。比較用に、このworkflowを実行したリポジトリのディレクトリ構成を記載します。A/1/test.txt A/2/test.txt . ├── A │ ├── 1 │ │ └── test.txt │ └── 2 │ └── test.txt ├── README.md └── test.txt 3 directories, 4 files
PullRequestに含まれていない. ├── A │ ├── 1 │ │ └── test.txt │ ├── 2 │ │ └── test.txt │ └── 3 │ └── test.txt ├── README.md └── test.txt 5 directories, 5 files
A/3
ディレクトリがチェックアウトされていないことが確認できます。
まとめ
本記事では、主にactions/checkout
に関連した高速化についてのTipsを紹介しました。
actions/checkout
はよく使われるactonですが、大規模リポジトリで使う場合はworkflowのボトルネックになりがちです。
workflowを見直してみると、案外使う必要がなかったりするかもしれません。