CircleCIでGitHub上のPRを自動コードレビュー（master, develop以外任意の統合先ブランチにも対応）

GitHubにホストしているプロジェクトのPRに対して、正しい統合先ブランチを判定し、CircleCIからProntoを使って自動的にコードレビューできるようにした。

## 具体的にPRのどこが悪い？

とあるRoRプロジェクトで、Rubocopの違反数増をCircleCIでガードしてGitHubでマージさせないようにしていた。これで確かにある程度はブロックできている。しかし、開発者としては具体的に、自分が変更したコードのどこに問題があるのかPR画面ですぐ分からないため対応しようとしても面倒だ。できたら、Rubocopの指摘をGitHubのPRのコメントを載せて上げられないものかと考えた。

## 自動コードレビューのツール

調べてみたら、SaddlerとProntoというgemsがこの用途にあると分かり、先にSaddlerを試してみたがローカルでも実行が上手くいかなかったり、使い勝手が悪いと感じて断念した。Prontoにしたら、ローカルでは比較的スムーズに実行できた。Prontoの必要なチェッカーをGemfileに指定してインストールしたら、pronto-run.shのように、コマンドラインで比較対象ブランチ(master、releaseやdevelopなど）を指定すると、現在のブランチ（マージしたいブランチ）によって持たされる差分に対して、Rubocopチェックを実施できた。

```ruby:Gemfile
# ProntoのチェッカーをGemfileに指定してインストール
group :development, :test do
  # Pronto: auto code review
  gem 'pronto-rubocop', require: false
  gem 'pronto-reek', require: false
  gem 'pronto-flay', require: false
end
```

```shell:pronto-run.sh
# コマンドラインで現在のブランチとマージ先のブランチの差分に対してコードチェック
# Check the diff between current branch and the branch specified
# PR `TO_BRANCH` ← `FROM_BRANCH`における変更箇所のチェックを行うには

git checkout FROM_BRANCH
bin/pronto run -c origin/TO_BRANCH
```

## 統合先ブランチ多数の難

CIで実行する場合、比較対象ブランチ（統合先のブランチ）をどう取得できるのか。前述のRoRプロジェクトでは、統合ブランチがmaster一本だけではなく複数存在する。だから、PRもmasterに対するとは限らず、release、developや親featureブランチに対するPRが大多数。いくつか参考させていただいたところ、統合先ブランチが複数存在する場合の対応方法は見当たらなかったので、模索と工夫が必要だった。

## CircleCIで工夫して統合先ブランチ名を特定

CircleCIなら、PRのURLsは環境変数CI_PULL_REQUESTSにあり、URLからPRのIDを取り出し、GitHub API (GraphQL)を通してPRの情報が取得でき、その中には欲しいbase branch （統合先ブランチ）情報が含まれている。GraphQLのQueryにはレポジトリの owner とnameを指定する必要もあるが、それらもCircleCIの環境変数にある。一連の作業があるので、pr.rakeに記述し、CircleCIではcode-review.shスクリプトで環境変数を受け取り実行する。

```ruby:CI_PULL_REQUESTSから関連PRのIDを（複数）取り出す
# CI_PULL_REQUESTSから関連PRのIDを（複数）取り出す
def pr_ids(pr_id = nil)
  ids = ENV.fetch('CI_PULL_REQUESTS', '').split(',').map { |url| pr_id url }
  ids.push pr_id if pr_id
  ids
end

# PRのURLから、最後の数値列を取り出しPRのIDとする
def pr_id(pr_url)
  pr_url[/\d+$/].to_i
end
```

```graphql:GraphQLでPRのIDから統合先ブランチ名を特定
query {
  repository(owner: "#{owner}", name: "#{name}") {
    pullRequest(number: #{pr_id}) {
      title
      url
      baseRefName
      headRefName
    }
  }
}
```

```shell:code-review.sh
#!/usr/bin/env bash
# CircleCIから環境変数を受け取り、コードレビューのrakeタスクに渡す
set -eu

export GITHUB_OWNER=$CIRCLE_PROJECT_USERNAME
export GITHUB_REPO=$CIRCLE_PROJECT_REPONAME
export PRONTO_GITHUB_ACCESS_TOKEN=$GITHUB_ACCESS_TOKEN
set -x

bin/rake pr:code_review

echo DONE
```

## まとめ

- CirleCIの環境変数からPRのIDを取得
- GitHub GraphQLでPRのIDから統合先ブランチ名を取得
- Prontoで統合先ブランチとの差分をチェックしPRにコメントを挿入

## その他

- コード自動レビューツールのProntoはローカルでの利用やGitHub連携以外にGitLabやBitbucketにも対応している。
- CircleCI以外、JenkinsやTravisCIなどでもGitHubと連携していたらPRのIDや統合先ブランチ名を取得できるはずだ。
- Rubocopで自動修正できる部分は自動修正していますが、Rubocopで自動修正できない部分やReekやFlayの指摘点はやはり開発者が確認する必要があります。

## 参考

- [特定のラベルのついたPRを拾ってくる](http://whitech0c0late.hatenablog.com/entry/2017/11/14/023102)
- [Rubocopを無理なく既存プロジェクトに導入する](https://medium.com/finc-engineering/rubocop%E3%82%92%E7%84%A1%E7%90%86%E3%81%AA%E3%81%8F%E6%97%A2%E5%AD%98%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AB%E5%B0%8E%E5%85%A5%E3%81%99%E3%82%8B-bea6bfbbc4b1)
- [RuboCopは自動修正のみ使うと捗る](https://qiita.com/fujimura/items/b67c720046c88b29125c)
- [CircleCI + Rubocop](https://medium.com/@MaximAbramchuk/circleci-github-pr-commenting-ruby-scss-coffeescript-javascript-git-and-etc-fbcbe2a378a5)
- [Pronto](https://github.com/prontolabs/pronto)
- [Octokit: How do you make a GraphQL query?](https://github.com/octokit/octokit.rb/issues/918)
- [GitHub GraphQL API v4 PullRequest](https://developer.github.com/v4/reference/object/pullrequest/#fields)