Github で PR の APPROVED 数に応じてラベル(releasable) をつけたり外したりする

f:id:kyagi:20220131212856p:plain
Dismiss stale pull request approvals when new commits are pushed

Github の Pull Request のレビュー後、APPROVED の数に応じてラベルをつける運用を自動化します。APPROVED 後にコミットがあった場合、自動的に APPROVED が取り消される運用(画像) での運用を想定しています。*1

f:id:kyagi:20220131220133p:plain
shamshir

任意の数の APPROVED を取得した PR の集合を A とし、releasable ラベルが付与されている PR の集合を B とした場合、動作は以下になります。

  • 差集合 A - B に対して releasable ラベルを付ける
  • 差集合 B - A に対して releasable ラベルを剥がす

差集合 B -A は主に APPROVED 後にコミットすることで発生します ((Github のブランチ設定で Dismiss stale pull request approvals when new commits are pushed 設定をしている場合)))

レポジトリ

https://github.com/kyagi/shamshir

実行

$ node shamshir.js --owner kyagi --repo awesome-project --label releasable --quorum 2

ログ

$ cat combined.log
{"level":"info","message":"Shamshir started.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:22"}
{"level":"info","message":"Shamshir got pulls: 2602,2598,2596,2575,2573,2557,2553,2551,2540,2539,2481,2478,2295,2281,1981,1951,1685","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:22"}
{"level":"info","message":"Shamshir added releasable label to pull/2598.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:24"}
{"level":"info","message":"Shamshir added releasable label to pull/2596.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:24"}
{"level":"info","message":"Shamshir added releasable label to pull/2575.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:25"}
{"level":"info","message":"Shamshir removed releasable label from pull/2573.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:26"}
{"level":"info","message":"Shamshir added releasable label to pull/2551.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:28"}
{"level":"info","message":"Shamshir added releasable label to pull/2540.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:29"}
{"level":"info","message":"Shamshir added releasable label to pull/2539.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:30"}
{"level":"info","message":"Shamshir added releasable label to pull/2478.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:31"}
{"level":"info","message":"Shamshir added releasable label to pull/2295.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:32"}
{"level":"info","message":"Shamshir added releasable label to pull/1951.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:34"}
{"level":"info","message":"Shamshir finished.","mode":"live","owner":"kyagi","repo":"awesome-project","service":"shamshir","timestamp":"2022-01-31 00:54:34"}

Github API (REST API) について

Github API では Issue と Pull Request でエンドポイントを共有しています("shared" actions for both features)。API Reference の Issue ページにはラベル処理がありますが Pulls ページにはラベル処理がありません。だからといって Pull Request にラベルをつけられないというわけではなく、Issue API のエンドポイントを使い key として pull_request を使用することで(shared) Pull Request に対してもラベル処理が可能です。

Note: GitHub's REST API v3 considers every pull request an issue, but not every issue is a pull request. For this reason, "Issues" endpoints may return both issues and pull requests in the response. You can identify pull requests by the pull_request key. Be aware that the id of a pull request returned from "Issues" endpoints will be an issue id. To find out the pull request id, use the "List pull requests" endpoint.

https://docs.github.com/en/rest/reference/issues

Every pull request is an issue, but not every issue is a pull request. For this reason, "shared" actions for both features, like manipulating assignees, labels and milestones, are provided within the Issues API. https://docs.github.com/en/rest/reference/pulls

トップの画像では集合の図を載せていますが、実装で集合演算は使用していません。JavaScript に組み込みの集合演算がないのと、1 ページ 30 個 程度の Pull Request が処理対象の場合、集合演算よりループで処理した方が簡単だからです。

運用方法について

EC2 や ECS で動かすスタンドアローン的な運用と Github Actions で動かす運用の 2 つを考えています(Github Actions は将来サポート予定)。それぞれの長所/短所を以下に挙げます。

スタンドアローン Github Actions
設計/実装 制約がなく自由度が高い Github Actions の方法に則る必要があり workflow/action 内でできることに制約がある
コスト 走らせるためのリソース(EC2 or ECS) にコストがかかる 条件内の実行時間とストレージ容量であればコストは無料
認証/認可 Personal Access Token が必要 入力として GITHUB_TOKEN が使える

参考情報

*1 Dismiss stale pull request approvals when new commits are pushed 設定をしている場合