以下の内容はhttps://toranoana-lab.hatenablog.com/entry/2025/09/09/120000より取得しました。


GitHub Actions: developからfeatureブランチへ自動マージする仕組みを作ってみた

こんにちは!虎の穴ラボの鷺山です。

Gitで複数の機能を並行開発するときは、developブランチから各機能のfeatureブランチを派生させて、開発が終わったらdevelopにマージするといったフローが一般的だと思います。

このとき、大規模な機能や複数の機能がマージされるとdevelopに多くの変更が入り、開発中のfeatureブランチに影響を与えたりコンフリクトを発生させたりすることがあります。定期的にdevelopをfeatureに取り込めばその影響を少なくできますが、その作業を毎回人手でやるのは少し面倒です。

そこで今回はこの課題を解決するため、GitHub Actionsを使ってdevelopからfeatureブランチへのマージを自動的に行う仕組みを作ってみたのでご紹介します。

解決したい課題

今回解決したい課題をもう少し具体的に解説します。

具体的なシチュエーションとして、機能ブランチA・B・Cからdevelopへのプルリクエスト (PR#101~#103) が出ている場面を想定します。

  • PR#101 — 機能ブランチA … feature/aからdevelopへのマージ
  • PR#102 — 機能ブランチB … feature/bからdevelopへのマージ
  • PR#103 — 機能ブランチC … feature/cからdevelopへのマージ

このとき、まず最初に「機能ブランチA」がdevelopにマージされたとします。
すると「機能ブランチB」と「機能ブランチC」には、developに入った「機能ブランチA」の変更を取り込む必要が出てきます。

この作業を手動で行うこともできますが、毎回手動で行っているとつい後回しになったり、忘れたりしがちです。
その結果、機能間で変更の影響による不具合が発生していた場合、その発覚が遅れてしまうことになります。
さらに、差分が大きくなるとコンフリクトの規模も大きくなり、解消作業がどんどん大変になってしまいます。

解決方法

GitHub Actionsを使ってdevelopからfeatureブランチへ自動でマージする仕組みを作ることで、この課題を解決します。

この記事では、「featureブランチにdevelopの変更を取り込むこと」をdevelop追従と呼ぶことにします。

どのブランチをdevelop追従させるかを個別に指定できるように、対象ブランチのプルリクエストに「develop追従」ラベルをあらかじめ付けておくという仕様にします。

対象プルリクエストに「develop追従」ラベルを付与
対象プルリクエストに「develop追従」ラベルを付与

実際のワークフロー

実際のワークフローファイルは次のようになります:

# .github/workflows/auto-merge-develop-into-feature.yml
name: developからfeatureへの自動マージ

permissions:
  contents: write
  pull-requests: read

on:
  schedule:
    - cron: "0 0 * * MON-FRI" # 平日のJST09:00に実行
  workflow_dispatch:

jobs:
  prepare-matrix:
    runs-on: ubuntu-latest
    outputs:
      branches: ${{ steps.prepare-branches.outputs.branches }}
    steps:
      - name: ラベル「develop追従」付きのPRのブランチを抽出
        id: prepare-branches
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const pullRequests = await github.paginate(
              github.rest.pulls.list,
              {
                owner: context.repo.owner,
                repo: context.repo.repo,
                state: 'open'
              }
            );
            const targetBranches = pullRequests
              .filter(pr => pr.labels.some(label => label.name === 'develop追従'))
              .map(pr => pr.head.ref);
            core.setOutput('branches', JSON.stringify(targetBranches));

  auto-merge:
    needs: prepare-matrix
    runs-on: ubuntu-latest
    if: ${{ needs.prepare-matrix.outputs.branches != '[]' }}
    strategy:
      fail-fast: false
      matrix:
        branch: ${{ fromJson(needs.prepare-matrix.outputs.branches) }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Gitユーザーを設定
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
      - name: マージ要否を確認
        id: check-diff
        run: |
          git checkout ${{ matrix.branch }}
          BASE_SHA=$(git merge-base origin/develop HEAD)
          DEVELOP_SHA=$(git rev-parse origin/develop)
          if [ "$BASE_SHA" != "$DEVELOP_SHA" ]; then
            echo "is_merge_needed=true" >> $GITHUB_OUTPUT
          fi
      - name: マージ実行
        if: steps.check-diff.outputs.is_merge_needed == 'true'
        run: |
          git merge origin/develop
      - name: プッシュ実行
        if: steps.check-diff.outputs.is_merge_needed == 'true'
        run: |
          git push origin ${{ matrix.branch }}

それぞれのポイントについて解説します。

on:セクション

on:
  schedule:
    - cron: "0 0 * * MON-FRI" # 平日のJST09:00に実行
  workflow_dispatch:

平日の朝9時 (日本時間) に自動実行されるように、cronでスケジューリング設定をしています。チームのマージ頻度などに応じて柔軟に設定してください。
また任意のタイミングで手動実行できるようにworkflow_dispatchも設定しています。

もし「developが更新された場合に随時実行させたい」場合は次のように設定します:

on:
  push:
    branches: [develop]

jobs:セクション

jobs:
  prepare-matrix:
    ... # develop追従のラベルが付いたプルリクエストのブランチを抽出

  auto-merge:
    ... # それぞれのブランチについてマージ処理を実行

このワークフローは大きく2つのジョブで構成されています:

  1. prepare-matrixで、develop追従ラベルが付いたプルリクエストのブランチを抽出します。
  2. auto-mergeで、そのブランチごとに実際のマージ処理を実行します。

prepare-matrixジョブ

jobs:
  prepare-matrix:
    ...
    steps:
      - name: ラベル「develop追従」付きのPRのブランチを抽出
        ...
          script: |
            const pullRequests = await github.paginate(
              github.rest.pulls.list,
              {
                owner: context.repo.owner,
                repo: context.repo.repo,
                state: 'open'
              }
            );

まず、プルリクエスト一覧 github.rest.pulls.list からOpen状態 state: 'open' のものを取得します。

            const targetBranches = pullRequests
              .filter(pr => pr.labels.some(label => label.name === 'develop追従'))
              .map(pr => pr.head.ref);
            core.setOutput('branches', JSON.stringify(targetBranches));

そこから、ラベルにdevelop追従が付いているプルリクエストだけを抽出して、そのブランチ名を次のジョブに渡すように core.setOutput(...) で出力します。

auto-mergeジョブ

  auto-merge:
    ...
    steps:
      ...
      - name: マージ要否を確認
        id: check-diff
        run: |
          git checkout ${{ matrix.branch }}
          BASE_SHA=$(git merge-base origin/develop HEAD)
          DEVELOP_SHA=$(git rev-parse origin/develop)
          if [ "$BASE_SHA" != "$DEVELOP_SHA" ]; then
            echo "is_merge_needed=true" >> $GITHUB_OUTPUT
          fi

ここでは、マージが必要かどうかを事前にチェックしています:

  • git merge-base origin/develop HEADで、developとHEAD (対象ブランチ) の共通祖先のSHAを取得します。
  • git rev-parse origin/developで、最新のdevelopのSHAを取得します。
  • 2つのSHAが違う場合は、対象ブランチがまだ最新のdevelopに追従できていない (マージが必要) と判断します。
      - name: マージ実行
        if: steps.check-diff.outputs.is_merge_needed == 'true'
        run: |
          git merge origin/develop
      - name: プッシュ実行
        if: steps.check-diff.outputs.is_merge_needed == 'true'
        run: |
          git push origin ${{ matrix.branch }}

そしてマージが必要 (is_merge_needed=true) な場合のみ、マージやプッシュを実行します。

以上で、developからfeatureブランチへ自動マージする仕組みが完成しました🎉

自動マージの実行結果
自動マージの実行結果

ここからは、この仕組みをさらに便利にするための工夫についてご紹介します。

自動マージ後にCIをトリガーする

上記の仕組みのままでは、自動マージによりプッシュが実行されてもCIなどの別ワークフローは自動で起動されません。 これは、「GITHUB_TOKENによるPushでは別のワークフローが自動起動されない」というGitHub Actionsの制約によるものです。

この制約を回避するには、ワークフローを次のように変更します。

# .github/workflows/auto-merge-develop-into-feature.yml
name: developからfeatureへの自動マージ
...

permissions:
  ...
  actions: write # 👈 Actionsの実行権限を追加

...
  auto-merge:
    ...
    steps:
      ...

      # 👇 CIをトリガーするステップを追加
      - name: CIをトリガー
        if: steps.check-diff.outputs.is_merge_needed == 'true'
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            await github.rest.actions.createWorkflowDispatch({
              owner: context.repo.owner,
              repo: context.repo.repo,
              workflow_id: 'ci.yml', // 起動したいワークフローのファイル名
              ref: '${{ matrix.branch }}'
            })

💡ポイント

  • permissions:actions: writeを追加し、別のワークフローを起動できるようにします。
  • auto-mergeジョブに「CIをトリガーするステップ」を追加します。
    • このステップでcreateWorkflowDispatch() API を使い、ワークフローを明示的に起動します。
      • 明示的なワークフロー起動であれば、GITHUB_TOKENであっても制約を受けずに起動できます。
      • workflow_id:に、起動したいワークフローのファイル名(例: ci.yml)を指定します。

また、CI側のワークフローを次のように変更します。

# .github/workflows/ci.yml
...

on:
  ...
  workflow_dispatch: # 👈 ここを追加

permissions:
  ...
  statuses: write # 👈 ステータスの変更権限を追加

jobs:
  ...
    steps:
      ...

      # 👇 ステータスを更新するステップを追加
      - name: コミットステータス更新 (workflow_dispatch 用)
        if: always() && github.event_name == 'workflow_dispatch'
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const state = '${{ job.status }}' === 'success' ? 'success' : 'failure';

            await github.rest.repos.createCommitStatus({
              owner: context.repo.owner,
              repo: context.repo.repo,
              sha: context.sha,
              state: state,
              target_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
              context: '${{ github.workflow }} / ${{ github.job }} (${{ github.event_name }})',
              description: state.charAt(0).toUpperCase() + state.slice(1),
            });

💡ポイント

  • on:workflow_dispatchを追加し、 外部からのワークフロー起動を受け付けるようにします。
  • permissions:statuses: writeを追加し、コミットステータスを更新できるようにします。
  • CIのジョブに「コミットステータスを更新するステップ」を追加します。
    • このステップでcreateCommitStatus() APIを使い、コミットステータスを更新します。
      • workflow_dispacthではコミットステータスが自動更新されないため、APIを使って明示的に更新します。
      • ビルドの成功・失敗に応じた結果がコミットステータスstateに反映されるように設定します。
    • トリガーイベントがworkflow_dispatchのときだけ実行されるようにif:で制御します。

以上で、自動マージによるプッシュ後にCIをトリガーし、コミットステータスを更新させることができるようになります。

自動マージ後にコミットステータスを更新
自動マージ後にコミットステータスを更新

自動マージ失敗時の通知

「develop」と「自動マージ対象ブランチ」の間にコンフリクトが発生していると、ワークフローはマージに失敗してエラーになります。自動マージが失敗したことに気付けるように、ワークフローのエラー時にチャットツールに通知するステップを追加します。

今回は、SlackのチャンネルにWebhook経由でメッセージ通知する例をご紹介します:

# .github/workflows/auto-merge-develop-into-feature.yml
name: developからfeatureへの自動マージ

...
  auto-merge:
    ...
    steps:
      ...

      # 👇 エラー発生を通知するステップを追加
      - name: エラー発生時にSlackに通知
        if: failure()
        run: |
          JOB_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
          curl -H 'Content-Type: application/json' --data "{\"text\":\"develop から ${{ matrix.branch }} への自動マージが失敗しました: ${JOB_URL} \"}" ${{ vars.AUTOMERGE_SLACK_WEBHOOK_URL }}
  • マージの失敗時はステータスがfailure()になるので、そのときに実行されるようにif:で制御します。
  • Slackからワークフローの実行結果にアクセスできるように、JOB_URLにそのURLを設定します。
  • SlackのWebhook URLは、リポジトリ変数AUTOMERGE_SLACK_WEBHOOK_URLにあらかじめ登録しておきます。
    • ワークフローで${{ vars.AUTOMERGE_SLACK_WEBHOOK_URL }}と書くことで読み出せます。

Webhook URLをリポジトリ変数に登録
Webhook URLをリポジトリ変数に登録

実行されると以下のようなメッセージが投稿されます。

Slackへ自動マージ失敗を通知
Slackへ自動マージ失敗を通知

まとめ

この記事では、developからfeatureブランチへの自動マージを実現するためのGitHub Actionsワークフローの作成方法をご紹介しました。

最新のdevelopをこまめに各featureブランチへ反映することで、一度にマージすると発生しがちな「大規模なコンフリクト」を未然に防ぐことが期待できます。また自動マージ後にCIを走らせることで、統合後のビルドエラーや不整合などの早期発見につながります。

複数の機能を並行開発しているチームの効率改善に、この記事の内容が少しでも役立てば幸いです!

採用情報

虎の穴ラボでは一緒に働く仲間を募集中です!
この記事を読んで、興味を持っていただけた方はぜひ弊社の採用情報をご覧ください。
toranoana-lab.co.jp




以上の内容はhttps://toranoana-lab.hatenablog.com/entry/2025/09/09/120000より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14