GitHub Actions でプルリクエストの自動アサインをする

これは [ゆめみ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/yumemi) の１０日目の投稿です。

皆さんはもう GitHub Actions を使っていますでしょうか？便利ですよね。

CI（継続的インテグレーション）でテストしたりビルドしたりするのに利用することが多いかと思いますが、GitHub 上の作業を自動化するのにも使えたりします。

今回は、プルリクエストを作成したら自動で **Assignees** と **Reviewers** を設定するワークフローを紹介します。
（毎回これらが決まりきっている場合、プルリクエストを作る毎に設定するのは面倒ですよね。）

#### 参考

- [GitHub Actions 公式ドキュメント(日本語)](https://help.github.com/ja/actions/automating-your-workflow-with-github-actions)
- [GitHub API v3](https://developer.github.com/v3/)

## Assignees の自動設定

assignees は基本的には当該プルリクエストの対応（レビューの対応）をする人になると思うので、プルリクエストを作成した本人とします。
ワークフローの設定は次のようになります。

```yaml:.github/workflows/assign-auto.yml

name: Assign auto

on:
  pull_request:
    types: opened # プルリクを作成したタイミングだけで動かす

jobs:
  assign:
    name: Set assignees
    runs-on: ubuntu-18.04
    steps:
      - name: Set assignees
        env:
          ASSIGNEES: "${{ github.actor }}" # 変えたければここを変える
        run: |
          assignee_count=$(cat ${{ github.event_path }} | jq '.pull_request.assignees | length')
          if [[ 0 == $assignee_count ]]; then
            assignees=$(echo "\"${ASSIGNEES// /}\"" | jq 'split(",")')
            curl -X POST \
                 -H "Content-Type: application/json" \
                 -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
                 -d "{ \"assignees\": $assignees }" \
                 https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/assignees
          fi
```

`${{ github.XXX }}` は公式ドキュメントを見てもらえば分かりますが、GitHub 上の情報（今回はプルリクエスト）を参照する構文です。勘違いしやすいのですが、環境変数ではありません。「実行前に静的に文字列置換される」という感覚が近いです。`${{ secrets.GITHUB_TOKEN }}` も標準で利用できる構文で、GitHub API アクセス用のトークンに置き換えられます。

`assignee_count` はプルリク作成と同時に設定された assignees の数です。これがゼロの場合に curl コマンドで GitHub API を叩いて assignees を設定します。

`assignees=$(echo "\"${ASSIGNEES// /}\"" | jq 'split(",")')` の箇所が少し分かりづらいので補足します。仮に環境変数 `ASSIGNEES` に設定されたアカウントが `hoge, fuga` とすると、

1. `${ASSIGNEES// /}` でスペースを削除 .. `hoge,fura`
2. `"\"${ASSIGNEES// /}\""` でダブルクォートを付加しJSONの文字列とする(後続の`jq`コマンドで扱えるようにする) .. `"hoge,fuga"`
3. `jq 'split(",")'` で `jq`コマンドにより文字列をJSONの配列にする .. `[ "hoge", "fuga" ]`

といったことをしています。

## Reviewers の自動設定

ワークフローの設定は次のようになります。

```yaml:.github/workflows/assign-auto.yml
name: Assign auto

on:
  pull_request:
    types: opened

jobs:
  assign:
    name: Set reviewers
    runs-on: ubuntu-18.04
    steps:
      - name: Set reviewers
        env:
          REVIEWERS: "hoge, fuga, piyo" # edit here
        run: |
          reviewer_count=$(cat ${{ github.event_path }} | jq '.pull_request.requested_reviewers | length')
          if [[ 0 == $reviewer_count ]]; then
            reviewers=$(echo "\"${REVIEWERS// /}\"" | jq 'split(",") | .-["${{ github.actor }}"]')
            curl -X POST \
                 -H "Content-Type: application/json" \
                 -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
                 -d "{ \"reviewers\": $reviewers }" \
                 https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/requested_reviewers
          fi
```

assignee の設定とあまり変わらないので説明は割愛しますが、プルリクを作成した本人は reviewers から除外している（レビュアに設定する必要がないし、GitHub API でもエラーになる）ことだけ注意です。

## まとめると

上記２つのワークフローをまとめると次のようになります。

```yaml:.github/workflows/assign-auto.yml
name: Assign auto

on:
  pull_request:
    types: opened

jobs:
  assign:
    name: Set assignees and reviewers
    runs-on: ubuntu-18.04
    steps:
      - name: Set assignees
        env:
          ASSIGNEES: "${{ github.actor }}"
        run: |
          assignee_count=$(cat ${{ github.event_path }} | jq '.pull_request.assignees | length')
          if [[ 0 == $assignee_count ]]; then
            assignees=$(echo "\"${ASSIGNEES// /}\"" | jq 'split(",")')
            curl -X POST \
                 -H "Content-Type: application/json" \
                 -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
                 -d "{ \"assignees\": $assignees }" \
                 https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/assignees
          fi
      - name: Set reviewers
        env:
          REVIEWERS: "hoge, fuga, piyo" # edit here
        run: |
          reviewer_count=$(cat ${{ github.event_path }} | jq '.pull_request.requested_reviewers | length')
          if [[ 0 == $reviewer_count ]]; then
            reviewers=$(echo "\"${REVIEWERS// /}\"" | jq 'split(",") | .-["${{ github.actor }}"]')
            curl -X POST \
                 -H "Content-Type: application/json" \
                 -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
                 -d "{ \"reviewers\": $reviewers }" \
                 https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/requested_reviewers
          fi
```

もしブランチ毎に reviewers が異なる、という場合は、ワークフローの実行条件のところでブランチを指定するか（yamlファイルはブランチ分用意する）、

```yaml
on:
  pull_request:
    branch:
      - master
```

もしくは `steps` 内で

```yaml
- name: Set reviewers
  if: github.base_ref == 'master'
```
```yaml
- name: Set reviewers
  if: startsWith(github.base_ref, 'develop') # 前方一致
```

のようにして各ステップを実行するか否かを制御すればいいでしょう。

今回は固定メンバーを全員 reviewrs として設定していますが、スクリプトを工夫すればランダムに選択して設定、等もできると思います。

## おわりに

いかがだったでしょうか。もし他に良い利用事例があれば、紹介いただけると幸いです。
それではよい GitHub Actions ライフを！

#### 2020年10月26日 追記

上記の機能+αを、独立した GitHub の Action として切り出しました。

https://github.com/marketplace/actions/review-assign-action

この Action を利用すると、次のようにシンプルに書けます :smile: 

```yaml:.github/workflows/review-assign.yml
name: Review Assign

on:
  pull_request:
    types: [opened, ready_for_review]

jobs:
  assign:
    runs-on: ubuntu-latest
    steps:
      - uses: hkusu/review-assign-action@v0.1.0
        with:
          assignees: ${{ github.actor }}
          reviewers: hoge, fuga, piyo
```