Kōhei Yamamoto

テンプレートファイルからプルリクエストを作るGitHub Actionsワークフロー

やりたいこと

次のような操作でプルリクエスト (PR) を作りたい。

たとえば次のようなファイル

# ${PR_TITLE}

<!-- ここに本文を書いてね -->

がリポジトリ内にテンプレートとして用意されているとする。このとき、Web UIから${PR_TITLE}に与える値として「サンプル記事」という文字列を渡すと、

# サンプル記事

<!-- ここに本文を書いてね -->

というファイルが新しいブランチにコミットされ、PRが作られるようにしたい。

実現方法

次の手順でやりたいことを実現できる。

この手順は、PR作成ロジックとワークフロー実行インタフェースの分離を意識している。

「github-actions[bot]」アカウントとしてPRを作るreusable workflowを定義する

「github-actions[bot]」アカウントとして、テンプレートから生成したファイルをコミットしてPRを作るワークフロー。reusable workflowとして定義することで、Web UIからのPR作成とは別のユースケースでも再利用できる。

name: reusable-create-pr

on:
  workflow_call:
    inputs:
      title:
        type: string
        default: "title"
        required: false
        description: PR title
      template:
        type: string
        default: "path/to/template.md"
        required: false
        description: |
          Path to the template file. Use $PR_TITLE in the template to replace it with the actual PR title.
    secrets:
      token:
        required: true
        description: GitHub token
    outputs:
      pr_number:
        description: PR number
        value: ${{ jobs.create-pr.outputs.pr_number }}

jobs:
  create-pr:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    env:
      BRANCH: ${{ github.actor }}-${{ github.run_id }}
      TITLE: ${{ inputs.title }}
      ASSIGNEE: ${{ github.actor }}
      GITHUB_TOKEN: ${{ secrets.token }}
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup-git # 後述のsetup-gitの説明を参照のこと
      - name: Create a pull request
        id: create_pr
        run: |
          # PRを作るために、ブランチを作って空コミットをプッシュする
          git switch -c "${BRANCH}"
          git commit --allow-empty -m "Prepare for ${BRANCH}"
          git push origin "${BRANCH}"

          # gh CLIでPRを作り、出力としてPRのURLを得る
          PR_URL=$( \
            gh pr create \
            --head "${BRANCH}" \
            --title "${TITLE}" \
            --draft \
            --assignee "${ASSIGNEE}" \
          )
          echo "pr_url=${PR_URL}" >> "${GITHUB_OUTPUT}"
      - name: Commit the template
        id: commit-template
        env:
          PR_URL: ${{ steps.create_pr.outputs.pr_url }}
          TEMPLATE: ${{ inputs.template }}
          PR_TITLE: ${{ inputs.title }}
        run: |
          # 例として、<PR番号.md>というファイル名でテンプレートをコピーしてくる
          echo "::group::Copy template"
          export PR_NUMBER
          PR_NUMBER=$(echo "${PR_URL}" | grep -oE "[0-9]+$")
          FILE_PATH="path/to/${PR_NUMBER}.md"
          cp "${TEMPLATE}" "${FILE_PATH}"
          echo "::endgroup::"

          # envsubstを使って、テンプレート内のプレースホルダーを入力値で置換する
          echo "::group::Replace variables in template"
          envsubst '${PR_TITLE}' < "${FILE_PATH}" > "${FILE_PATH}.tmp"
          mv "${FILE_PATH}.tmp" "${FILE_PATH}"
          echo "::endgroup::"

          # 変更をコミットしてPRにプッシュする
          echo "::group::Commit and push template"
          git add "${FILE_PATH}"
          git commit -m "Set up a file"
          git push origin "${BRANCH}"
          echo "::endgroup::"

          echo "pr_number=${PR_NUMBER}" >> "${GITHUB_OUTPUT}"
    outputs:
      pr_number: ${{ steps.commit-template.outputs.pr_number }}

テンプレートからファイルを作るときに、プレースホルダー${PR_TITLE}だけを置換対象にするため、envsubstにプレースホルダー名を含む文字列'${PR_TITLE}'を引数として渡している1

ワークフローの見通しをよくするために、「github-actions[bot]」アカウントになるためのGitのセットアップはアクションに切り出す。このアクションの置き場は .github/actions/setup-git/action.yaml を想定している。description記載のリンク先のページにもあるとおり、「github-actions[bot]」としてコミットを作るための公式(?)ハックを使っている。

name: Set up Git as github-actions[bot]
description: |
  cf. <https://github.com/actions/checkout/blob/v4/README.md#push-a-commit-using-the-built-in-token>

runs:
  using: composite
  steps:
    - name: Set up Git
      shell: bash
      env:
        USERNAME: github-actions[bot]
        EMAIL: 41898282+github-actions[bot]@users.noreply.github.com
      run: |
        git config --global user.name "${{ env.USERNAME }}"
        git config --global user.email "${{ env.EMAIL }}"

Web UIから実行できるワークフローを定義し、その中でPRを作るreusable workflowを呼ぶ

GHAのWeb UI(リポジトリ配下の/actions/workflows/<ワークフローファイル名>のパスでアクセスできる画面)からPRを作るreusable workflowを実行するために、手動実行可能なワークフローを次のように定義する。

name: Create pull request

on:
  workflow_dispatch:
    inputs:
      title:
        description: PR title
        required: true
        default: "[WIP]"

jobs:
  create-pr:
    uses: ./.github/workflows/reusable-create-pr.yaml
    with:
      title: ${{ github.event.inputs.title }}
    secrets:
      token: ${{ secrets.GITHUB_TOKEN }}
    permissions:
      contents: write
      pull-requests: write

これで、Web UIから、テンプレートをもとに生成したファイルを含むPRが作れるようになる。

脚注

  1. envsubstがこのような仕様になっている経緯については、次の記事を参照のこと: envsubstの本来の使い方はテンプレートエンジンではなくシェルスクリプト用の国際化機能 #ShellScript - Qiita