1074 文字
5 分

Github Actionsを利用してReviewer全員のApprovedを必須にする

指定したレビュワー全員に見てほしい場合、常に見てほしい人数が固定であるならば、Github のブランチプロテクションを利用すれば良い。 一方、PR によって見てほしい人数が異なる場合は、ブランチプロテクションでは制御出来ない。

「コメントで指摘した内容などが修正されていないにも関わらず他の人がマージしてしまう」という相談をされた際に考えた内容を元に、Github Actions を利用して Reviewer 全員の Approved を必須にする方法を紹介する。

GitHub Script を利用してレビューの状態を取得する#

Github Actions では、actions/github-scriptを利用することで octokit を利用して Github API を叩くことができる。 そこでまず手元で octokit を利用してレビュー状態を取得するスクリプトを書いた。

const { data: pull_request } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
});
if (pull_request.requested_reviewers.length > 0) {
throw new Error("There are reviewers who have not reviewed.");
}
const { data: reviews } = await github.rest.pulls.listReviews({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
});
console.log(reviews);

レビューをフィルタリングする#

レビューを一覧で取得すると、そのまま使うには次の 2 つの問題があると分かる。

  • 自分自身のレビューも含まれている。
  • 同一人物による複数回のレビューが全て含まれている。

そこで、これをフィルタリングする必要がある。

自分自身の除外#

次のようにして PR を作成した人物を除外できる。

const reviewsWithoutAuthor = reviews.filter((review) => {
return review.user.login !== context.payload.pull_request.user.login;
});

ステータスの確認#

レビューのステータスは、単純に最新の状態を取得するだけでは不十分である。次のことを考慮する必要がある。

  • CHANGE_REQUESTED と APPROVED は現在の状態を上書きする。
  • COMMENTED は、CHANGE_REQUESTED と APPROVED を上書きできない。

次のようにしてステータスを確認できる。

/**
* @type {{[reviewerName: string]: "APPROVED" | "CHANGES_REQUESTED" | "COMMENTED"}}
*/
const reviewStatus = reviewsWithoutAuthor.reduce((acc, review) => {
if (review.state === "CHANGES_REQUESTED" || review.state === "APPROVED") {
acc[review.user.login] = review.state;
}
if (review.state === "COMMENTED" && acc[review.user.login] === undefined) {
acc[review.user.login] = review.state;
}
return acc;
}, {});

全員が Approved しているかどうかは次のようにすればいい。

const allApproved = Object.values(reviewStatus).every((state) => {
return state === "APPROVED";
});

Resusable workflow として作る#

以上のスクリプトを利用して、レビューの状態を取得し、全員が APPROVED であるかを確認することができる。

全てをまとめて Resusable workflow として作成すると次のようになる。

name: Approved Checker
on: workflow_call
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Get pull request reviews
uses: actions/github-script@v6
with:
script: |
const { data: { requested_reviewers } } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number
});
if(requested_reviewers.length > 0) {
throw new Error('There are reviewers who have not reviewed.');
}
const { data: reviews } = await github.rest.pulls.listReviews({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number
});
const reviewsWithoutAuthor = reviews.filter((review) => {
return review.user.login !== context.payload.pull_request.user.login;
});
if(reviewsWithoutAuthor.length === 0) {
throw new Error('No pull request reviews have been submitted.');
}
const reviewStatus = reviewsWithoutAuthor.reduce((acc, review) => {
if (review.state === "CHANGES_REQUESTED" || review.state === "APPROVED") {
acc[review.user.login] = review.state;
}
if (review.state === "COMMENTED" && acc[review.user.login] === undefined) {
acc[review.user.login] = review.state;
}
return acc;
}, {});
const allApproved = Object.values(reviewStatus).every(state => {
return state === "APPROVED";
});
if (!allApproved) {
throw new Error('Not all pull request reviews have been approved.');
}

次のようにして利用すれば、PR を操作する度に走らせてレビューの状態を確認できる。

name: Approved Checker
on:
pull_request:
types:
[opened, reopened, synchronize, review_requested, review_request_removed]
pull_request_review:
types: [submitted, edited, dismissed]
jobs:
check_review_status:
uses: org_name/workflow_repository_name/.github/workflows/approved-checker.yml@main

誰か 1 人にだけ見てほしい場合#

例えば、ラベルなどを利用して次のように制御できる。

name: Approved Checker
on: workflow_call
jobs:
main:
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'or-review') == false
steps:
- name: Get pull request reviews
uses: actions/github-script@v6

Reusable Workflow を利用する側ではラベルの変更をトリガーにする。

on:
pull_request:
types:
[
opened,
reopened,
synchronize,
review_requested,
review_request_removed,
labeled,
unlabeled,
]

もっと簡単な解決策#

コメントを見てほしいなら Changed Request するべきだ。 Changed Request があれば、他の人が Approved していてもマージできない状態に簡単にできる。

Changed Request が拒絶しているような印象を与え相手を傷つけないようにしているのかもしれないし、ただレビュワーの怠惰かもしれないが、だからといって別の手段に頼るのはあまりにも不毛な上、健全で開発チームであるとはとても思えない。

Github Actionsを利用してReviewer全員のApprovedを必須にする
https://blog.ohirunewani.com/posts/github-reviewer-all-approved-required/
作者
hrdtbs
公開日
2023-04-22
ライセンス
CC BY-NC-SA 4.0