特定のファイルの変更を検知してPull RequestのDescriptionに加筆する

現在、GitHubでは複数のIssueテンプレートを作成することができますが、複数のPull Requestテンプレートを作ることはできません。
コメントアウトを活用して、うまく誤魔化す手法などが取られますがそうすると、テンプレートが肥大しがちです。
そこで、今回はRailsアプリケーションのmigrationを例に、GitHub Actionsを使ってdescriptionに加筆することで肥大化を解決してみましょう。


## 今回加筆したいもの

db/schema.rbが更新された時に、下記をdescriptionに追記するものとします。

```
## Migration list
- Migration time: s
- Record size: 
```

Migrationにかかる時間と、レコードサイズを記入する部分をDescriptionに追記するというわけですね。（レコードサイズは本来カラムやインデックスの追加・削除時だけで良さそうですが、今回はそこが本題ではないので考えないこととする）

## 作成したもの
下記のymlファイルです。

```yml:.github/workflows/description-migration.yml
name: Add Migration list

on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review]
    paths:
      - 'db/schema.rb'
env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:  
  add_migration_lists:
    name: Add migration lists to description of pull request
    runs-on: ubuntu-latest
    outputs:
      replaced_description1: ${{ steps.get_description.outputs.replaced_description }}
    steps:  
      - name: checkout
        uses: actions/checkout@v3
      - name: Get current pull request description
        id: get_description
        run: |
          DESCRIPTION=$(gh pr view ${{ github.event.pull_request.number }} --json body --jq .body)
          REPLACED_DESCRIPTION=$(echo $DESCRIPTION | sed 's/\n/;;;:::;;;/g')
          echo "replaced_description=$REPLACED_DESCRIPTION" >> $GITHUB_OUTPUT 
      - name: URL to Pull Request description
        if: contains(steps.get_description.outputs.replaced_description, '## Migration Lists') == false
        run: |
          DESCRIPTION=$(echo $REPLACED_DESCRIPTION | sed 's/;;;:::;;;/\n/g')
          gh pr edit ${{ github.event.pull_request.number }} --body "$DESCRIPTION
          ## Migration Lists
          - Migration time: s
          - Record size: "
        env:
          REPLACED_DESCRIPTION: ${{ steps.get_description.outputs.replaced_description }}
```

### 特定のファイル変更が起きたときにだけワークフローが走るようにする
まず、`db/schema.rb`に変更が起きたときだけ走るようにします。
下記のような記述で、PRをOpenしたとき、Pull Requestが追跡しているブランチがPull Requestのソースブランチと同期されたとき、再度Openしたとき、Draftから変更した時をトリガーとしています。

```yml
name: Add Migration list

on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review]
    paths:
      - 'db/schema.rb'
```

### Pull Requestのdescriptionを取得して一時保存するr。
まず、現在のPull Requestのdescriptionに追記する形を取るので、現在のPull Requestの情報を取得しましょう。
checkoutでリポジトリのソースコードを引っ張ってきた後に、[ghコマンド](https://github.com/cli/cli)を利用してpull requestの情報を取得します。

```yml
DESCRIPTION=$(gh pr view ${{ github.event.pull_request.number }} --json body --jq .body)
```

`--json body`でdescriptionを取得し、`--jq .body`でjson形式になっているのを平文に直しています。

また、このテキストは次のステップで使うので一時保存しましょう。
```yml
    outputs:
      replaced_description1: ${{ steps.get_description.outputs.replaced_description }}
```

この記述で、idが`get_description`のステップで、replaced_descriptionとして保存したものを別ステップで `${{ steps.get_description.outputs.replaced_description }}` のような記述で取り出せます。
格納は`echo "outputs.の後に指定した文字列=格納したい文字列" >> $GITHUB_OUTPUT`の形式で行います。
格納する際、sedで置換していますが、これは`outputs`を使うためには一行にする必要があるためです。


### containsを使って何度もMigration listsを作らないようにする

さて、更新する前にまずPull RequestにMigration listsが既に作られてないかチェックします。

```yml
- name: URL to Pull Request description
        if: contains(steps.get_description.outputs.replaced_description, '## Migration Lists') == false
```

runなどの前にifを置いて条件式を書くわけですが、今回は`## Migration Lists`という文字列がなかったらとします。
否定はcontainsの場合は `contains(捜索する文字列, 含まれてるか確認したい文字列) == false`形式で書きましょう。 `!contains`のような形式で書いたりしても普通にエラーになります。

### descriptionの更新をする

それでは、実際に更新をしていきましょう。まずはenvから先ほど格納した値を呼べるようにします。

```yml
        env:
          REPLACED_DESCRIPTION: ${{ steps.get_description.outputs.replaced_description }}
```

まず、再度sedで改行が置換された文字列を再度置換しなおします。
その後、ghコマンドで、今度はPRを更新するためのコマンドを打ちます。 --bodyオプションでdescriptionを更新することができます。

```yml
        run: |
          DESCRIPTION=$(echo $REPLACED_DESCRIPTION | sed 's/;;;:::;;;/\n/g')
          gh pr edit ${{ github.event.pull_request.number }} --body "$DESCRIPTION
          ## Migration Lists
          - Migration time: s
          - Record size: "
```

## まとめ
今回は特定条件でPull RequestのDescriptionを更新するというスクリプトを書いてみました。
GitHub Actionsではghコマンドを組み合わせることでPRを作成したり、編集したりかなり色々とできるので、是非何かやりたいことがあれば自作してみてください。