👏

特定のディレクトリの変更があった場合のみ、github actionsを動かそう

2025/01/20に公開

初めに

初めまして!
皆様どうもこんばんわ、こんにちわ、おはようございます。
エンジニアの榎本です。

最近よくgithub actionsをよく触る機会があり、効率化のために色々と試行錯誤しています。
本記事では、そうした経験から得たTipsを共有します。
皆さんのCI/CD環境の改善や時間短縮に役立てれば幸いです。

想定ユースケース

この記事が役立つのは、以下のようなケースです:

  • モノレポでのコード管理
    フロントエンド、バックエンド、インフラ(IaC)のコードをモノレポで管理しており、GitHub Actionsを使用してCI/CDを行っている場合。

  • 特定ディレクトリの変更に応じたジョブ実行
    特定のディレクトリが変更された場合のみ特定のジョブを実行したい。

例として:

  • フロントエンドの変更だけ確認したいのに、バックエンドのCIまで動いてしまう。
  • フロントエンドのコードだけデプロイしたいのに、バックエンドも一緒にデプロイされてしまう。
  • アプリケーションの動作とは関係ないコードエディターの設定ファイルの変更だけなのに、CIが動いてしまう。

これらは、現状でも大きな問題ではないかもしれませんが、改善すれば開発効率をさらに高めることができます。

path-filterでいいのでは?

元々githhub actionsはpath-filter という機能があり、ymlファイル全体で、特定のディレクトリに変更があった場合だけ動かすという風なことができます。

しかし、以下の制約があります:

  1. ジョブやステップ単位での制御が難しい
    path-filterはワークフロー全体に適用されるため、細かな制御ができません。

  2. ファイル分割の手間
    ワークフローをファイルごとに分割する方法も考えられますが、checkoutやパッケージのインストール処理を繰り返し記述する必要があり、コードが冗長になります。

github actionsのサンプルコード

今回のサンプルコードはpull requestがstaging branchにマージされた時に、merge commit間の差分ファイルを検出し、その内容に応じて特定のJobを動かすという感じにしています。
(よくあるstaging環境へのデプロイフローという感じ)

deploy.yml
name: Deploy For Staging

on:
  pull_request:
    types: [closed]
    branches:
      - staging

jobs:
  git_difference:
    runs-on: ubuntu-latest
    outputs:
      diff_files: ${{ steps.diff_files.outputs.diff_files }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Get diff files
        id: diff_files
        run: |
          latest_merge_sha=$(git log --merges -n 1 --pretty=format:"%H")
          previous_merge_sha=$(git log --merges -n 2 --pretty=format:"%H" | tail -n 1)
          diff_files=$(git diff --name-only $latest_merge_sha $previous_merge_sha | tr "\n" " ")
          echo "diff_files=${diff_files}" >> "$GITHUB_OUTPUT"

....
....
...

  
  deploy_service:
    if: github.event.pull_request.merged == true && (contains(needs.git_difference.outputs.diff_files, 'apps/backend/')
    name: Deploy api service
    runs-on: ubuntu-latest
    needs: [git_difference]
    steps:
      - name: Deploy API
        run: echo "Deploying API..."
...
...

解説

一つ一つ解説を書いていきます。

on:
  pull_request:
    types: [closed]
    branches:
      - staging

これはこのymlファイル全体の制御を担うコードです。
注意しないといけないのは、マージ先 がstaging branchに場合のpull requestがclosedになった場合です。

jobs:
  git_difference:
    runs-on: ubuntu-latest
    outputs:
      diff_files: ${{ steps.diff_files.outputs.diff_files }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

ここではcheckoutを行っています
actions/checkout@v4では、デフォルトでfetch-depth が 1 に設定されていて最新のコミットのみを取得するようになっています。
しかし、今回はmerge commit間の差分の取得が目的なので、fetch-depth を 0 に設定し、すべての履歴を取得してきています。

- name: Get  diff_files
        id: diff_files
        run: |
          latest_merge_sha=$(git log --merges -n 1 --pretty=format:"%H")
          previous_merge_sha=$(git log --merges -n 2 --pretty=format:"%H" | tail -n 1)
          diff_files=$(git diff --name-only $latest_merge_sha $previous_merge_sha | tr "\n" " ")
          echo "diff_files=${diff_files}" >> "$GITHUB_OUTPUT"

ここでは主にgitの操作で差分を取得しています。

latest_merge_sha=$(git log --merges -n 1 --pretty=format:"%H")
git logには--mergesというオプションがあり、ここでmerge commitだけを抜き出してきます。
その中で最新のものを一つだけ抜き出し、commit hashだけとってくるような感じのformatを指定しています。

previous_merge_sha=$(git log --merges -n 2 --pretty=format:"%H" | tail -n 1)
今度は次にmerge commit間の差分を取得するために、最新から2つ分までのmerge commitを取得して、commit hashだけをとってくるようにします。

そしてそれらの結果に対して、最後から1行目だけ取得するという形にして、最新から2番目のcommit hashを取得します。

diff_files=$(git diff --name-only $latest_merge_sha $previous_merge_sha | tr "\n" " ")

最後にこれらのcommit間の差分ファイルを取得します。
--name-only指定したコミット間の変更ファイル名を取得し、改行をスペースに置換して1行の文字列にして、diff_filesという変数に格納し、それをoutputとして渡します。

deploy_service:
    if: github.event.pull_request.merged == true && (contains(needs.git_difference.outputs.diff_files, 'apps/backend/')
...

最後に以下の条件式でJobの実行の制御を先ほどの変数を使ってやります。
・pull requestがmergeされたか?(close時にgithub actionsが動くので、merge時のみ動くように)
・先ほどの変更のファイルの差分に特定のディレクトリのファイル名が含まれているか?

これを応用すると特定のディレクトリが変更された時のみ、Jobを実行させることができます。

ストレスのない開発をするためにちょっとした努力をしよう

いかがだったでしょうか?

こういった「CIの実行時間を減らす」、「効率よく適切なデプロイを行う」という行為は結構後回しにされがちなものです。
実際今問題なく動作していて、壊れているわけではないので。

ただしこのような改善は開発が続く限りお釣りが一生続くものです。

早く改善すればするほど良い!

開発するエンジニアとしてもこのような待ち時間はできるだけ少ないほうがノンストレスな開発ができます。

快適なCI/CDライフを送りましょう!!

余談

この記事を書いてる途中で自分が書こうとしていることをすでに作ってくれている人がいたので、そちらも参考にしてもらえればと思います(涙目)

https://github.com/dorny/paths-filter

We're hiring!

https://teamdelta.jp/recruit/

最後までお読みいただきありがとうございます。
開発環境でお悩みのことがあればぜひDELTAにお問い合わせください。

そして現在DELTA では一緒に働いてくださる仲間を大募集中です!
ご興味をお持ちいただけましたら、お気軽にフォームからご連絡ください。

https://docs.google.com/forms/d/e/1FAIpQLSfQuWNU1il5lq2rVdICM0tSK_jTsjqwc52LYEwUxBq7_ImtrQ/viewform

DELTAテックブログ

Discussion