Github Actionsを使ってリリース用のPRを自動で作成してみる

この記事は、[SmartHR Advent Calendar 2024](https://qiita.com/advent-calendar/2024/smarthr) シリーズ1の5日目の記事です。

こんにちは！最近はSmartHRでスキル管理というプロダクトを開発しているs.miyoshiデス。

今回は日々の運用がちょっとだけ楽になるかもしれないスクリプトをご紹介したいと思います。

## はじめに

スキル管理プロダクトでは通常以下のようなステップで開発を進めています。

1. それぞれのブランチで開発
2. PRを出して、レビューokならstagingブランチにマージ
3. stagingブランチにマージされると、自動でstaging環境にデプロイ
4. staging環境でのチェックが問題なければ、PRを作成してmainブランチにマージ
5. 自動で本番環境にデプロイ

いわゆるGitHub Flowにstagingブランチが追加されている感じです。

今回はこの手順4でstagingブランチからmainブランチへマージするためのPRを自動作成する試みです。

## 成果物

![スクリーンショット 2024-12-04 17.48.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152318/272aacfe-b597-ba6e-e894-0ac546454db8.png)

このようなPRを自動で作成するようにしました。
`[release] 2024-12-05`というPRが自動で作成され、そこにリリースされる内容が一覧で見えるようになっています。

リリースされる内容が表示されていると、いつどんな内容がリリースされたのか一目でわかるので、後から振り返りやすかったり不具合発生時の原因特定がやりやすかったりと色々メリットがあります。

では、実際にどのようなコードなのかを解説していきたいと思います。

## コード全体像

```yml
name: Create a pull request for release.

on:
  push:
    branches: [staging]

jobs:
  create-release-pr:
    runs-on: ubuntu-latest

    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      PR_TITLE: "[release]"
      BASE_BRANCH: main

    steps:
      - uses: actions/checkout@v4

      # リリース用PRが既に存在するかどうかをチェック
      - name: Check if pr exists
        id: check_pr
        run: |
          echo "count=$(gh pr list -S ${PR_TITLE}' in:title' -B $BASE_BRANCH | wc -l)" >> $GITHUB_OUTPUT
      # リリース用PRを作成
      - name: Create release pr
        if: ${{ steps.check_pr.outputs.count == 0 }}
        run: |
          today=`TZ=JST date +"%Y-%m-%d"`
          gh pr create -B ${BASE_BRANCH} -t "${PR_TITLE} ${today}" -b "" -d
      - name: Set description
        run: |
          today=`TZ=JST date +"%Y-%m-%d"`
          pr_number=`gh pr list -S ${PR_TITLE}' in:title' -B $BASE_BRANCH --json number --jq '.[0].number'`
          pr=`gh api /repos/kufu/sample-product/pulls/${pr_number}/commits --paginate | jq '.[] | select(.commit.message | contains("Merge pull request")) | .commit.message' | awk '{print "- "$4}'`
          tmpfile=$(mktemp)
          cat << EOF > $tmpfile
          ## 関連リンク
          $pr
          EOF
          gh pr edit staging --title "${PR_TITLE} ${today}"
          gh pr edit staging --body-file $tmpfile
          rm -f $tmpfile
```

## コードの解説

このコードがどのように動くかもうちょっと詳しく解説していきたいと思います。

まず、Github Actionはstagingブランチにpushされた時に発火するようにします。
つまりstagingブランチにpushされるごとにcreate-release-pr jobが走るようになっています。

```yml
on:
  push:
    branches: [staging]
```

create-release-pr jobでは最終的に`[release] 2024-12-05`みたいなPRを作成します。
既存の同タイトルのPRがあるかをチェックして、あればその内容を編集、なければ作るといった作戦です。

### 各ステップの解説

Check if pr existsステップでまずはすでにPRがあるかをチェックしています。

```bash
echo "count=$(gh pr list -S ${PR_TITLE}' in:title' -B $BASE_BRANCH | wc -l)" >> $GITHUB_OUTPUT
```

ghコマンドでPRを見ていって`[release]`がタイトルに含まれているPRが存在するかをチェックします。
最後に書き出しているのは次のstepのif分岐させるためですね。

Create release prのstepは単にghコマンドでPRを作成しているだけです
-dでdraft PRにし、-b ""で空のPRを作成してます。
このあたりは`gh pr create --help`で調べると詳しくチェックできます。(ghコマンドはヘルプが充実しててほんと便利)

そしてその後Set description stepで中身を埋めていきます。
descriptionにはこのPRに含まれるすべてのPRの内容を表示するようにしたいのでその取得部分がこれです。

```bash
gh api /repos/kufu/sample-product/pulls/${pr_number}/commits --paginate | jq '.[] | select(.commit.message | contains("Merge pull request")) | .commit.message' | awk '{print "- "$4}'
```

GithubでPRをマージすると以下のようなマージコミットが作成されます。
`Merge pull request #88 from sh-miyoshi/split-damage-type`
つまりリリースPRに含まれる全てのcommitを確認して、そのコミットメッセージに`Merge pull request`が含まれるPRを取得していけばいいのです。

実はcommitを全て取得するには`gh pr list --json commits`とやれば取得できます。
しかし、このコマンドの場合commitsが100件を超えているとページネーションの関係で取得できないという問題がありました。
そこで`gh api /repos/kufu/sample-product/pulls/${pr_number}/commits --paginate`コマンドによりページネーションを考慮して取得するようにしています。
(基本的にcommitsが100件を超えることはないのですが、たまに大きい機能のリリースやライブラリのバージョンアップなどが重なると超えたことがありました)

取得したcommitsはjqコマンドによって`Merge pull request`がコミットメッセージに含まれるcommitだけにフィルタリングし、awkコマンドでPR番号を取得しています。

ちなみにGithubはPR番号を記載すると成果物のスクショのようにPRのタイトルを表示してくれるので便利ですね。

## おわりに

自動化は正義！
あと何かゴニョゴニョやりたいときにjqコマンドは便利すぎる・・・