GitHubにおけるPull RequestのAssign/Mergeを自動化して開発を加速させる

皆さんこんにちは. 現在はfluctにてfluct DRという広告配信システムの開発を行っております, 大関です.

GitHub上でのチーム開発では, レビューの依頼や, CIが通ったことを確認した上でのPull Requestのマージといった複数の作業が発生しますが, これらはGitHubのUIを複数回クリックする必要があり, 非常にストレスフルな作業です.

本稿では, こうした定形作業を自動化するbotとしてpopukoを開発・導入することで, 我々開発者のストレスを軽減するとともに, より堅牢かつフィードバックの多い開発が実施できるようになった事例を紹介します.

GitHubでの開発はとてもクリック操作が多い

前段でも述べたように, GitHubを用いたチーム開発においては, 数多くの定形作業が存在します. コードレビューの可能な人を探してレビューを依頼する, 依頼の度に対象者をAssigneeに追加する, コードをレビューする, upstream(多くの場合においてmasterブランチ)とのコンフリクトが発生していないかを確認する, reviewerによる許可が出た後にマージボタンを押す, Pull Requestをupstreamにマージした後もCIがgreenのままでいるかを確認する, などなど. GitHubのデフォルトのUIだけでは, これらは手動で実施する必要があり, 非常にストレスフルな作業です.

また, それぞれのPull Requestの状態についてはラベルで管理していない限りは一覧した場合に判断しにくく, かといってラベル管理している場合はそれはそれでラベルを付け替える手間が発生してしまいます.

我々はソフトウェア開発者ですので, このような鬱屈とした作業に関しては直ちに自動化を行い, クリック作業を減らし, ストレスの多い生活から開放されなければなりません.

自動化したい

このようなGitHubにおけるPull Request作業の自動化という分野については, homuと呼ばれる高機能かつ素晴らしいbotが存在しています. homuはGitHubにおけるPull Requestのマージ処理の自動化やTravisCIとの連携など数多くの機能を持つbotです. かつてMozillaにてRust Languageのtech leadを務めていたGraydon Hoareの語った

The Not Rocket Science Rule Of Software Engineering: automatically maintain a repository of code that always passes all the tests

の思想に基づき開発されたborsの流れを組んでおり, Rust ProjectならびにServo Projectで使われています.

ですが, 本家側の更新は止まって久しく, MozillaにてforkされたバージョンはMozillaのビルドインフラやServoプロジェクトに合わせた変更がなされており, 他のプロジェクトが自前でホストする形式での利用を積極的に薦める状態にはありません.

自前でホストするのを諦めるのであれば, homu.ioを使用するという選択肢もあります. しかしながら, 使用するに際して, 運営元の不明瞭な外部サービスにプライベートリポジトリへのアクセスを認めることとなり, セキュリティ上の観点からは決して望ましいものではありません.

また, homuは各リポジトリごとのreviewerの設定を, 中央集権されたhomu向けの設定ファイルに記述することで管理しています. ですが, 私達は, このような設定に関しては各リポジトリごとに管理可能にし, それぞれのリポジトリ内でreviewerやbotの機能の設定を完結させたいという意志がありました.

これらの理由から私達は, 自分達の要求に足るだけの最小機能だけをpopukoとして再実装し, 私達のプロダクト開発に導入することとしました.

popuko

popukoは以下のモデルでPull Request駆動による開発を補助します

  • webhookを起点にして動作を行う
    • コメントの書き込みややpushイベントに反応して動作する
  • ラベルを用いた状態の可視化を行う
    • rebase必須, レビュー待ち, マージ待ち, など
  • 各リポジトリのトップレベルディレクトリに配置されたOWNERS.jsonファイルに書かれた設定に基づき動作する

この原則に基づきに, 現在, 以下の機能を実装しています.

  • Pull Requestに書き込まれたコメントに基づいて何かする
    • reviewerの割当とラベルを変更する
      • Pull Requestがapproveされたらラベルを変え, 一旦upstreamとマージした結果を試す仮ブランチを作り, CIがgreenになることが保証されたら正式にマージする.
  • upstream側が変更され, Pull Requestがマージできなくなった場合, push logとともに其の旨を当該Pull Requestに書き込む

ラベルによる状態の可視化

popukoは以下のラベルを用いてPull Requestの状態を管理します. このラベル名はServo Projectのラベル定義を参考にしています.

ラベル名 意味
S-awaiting-review レビュー待ち
S-needs-rebase conflictしてるのでrebaseが必要
S-awaiting-merge マージ待ち
S-fails-tests-with-upstream Auto-Mergeを用いてマージしようとしたらテストが失敗してしまった

具体的な機能

それでは, それぞれの機能について見ていきましょう.

reviewerをassignする

r? @<reviewer> の形式でPull Requestにコメントすることにより, <reviewer>をAssigneesに登録し, ラベルをS-awaiting-reviewに変更します

popukoがreviewerをAssigneesに登録してラベルを変更する例
f:id:saneyuki_s:20170210193146p:plain

masterとのconflictを検知する

masterブランチにpushが為されたことをwebhookのpushイベント経由で受取り, openなPull Requestを巡回します. 巡回したPull Requestのうち, conflictを起こしているものに対して以下を行います

  • ラベルをS-needs-rebaseに付与する
  • pushイベント経由で取得したchangeset urlをコメントし, コンフリクトの原因となっている可能性の高い変更をコメントする

popukoによるconflict通知の例
f:id:saneyuki_s:20170210193216p:plain

マージ可能であると知らせる

@<botname> r+もしくは@<botname> r=<reviewer>とコメントすることにより, 当該Pull Requestの最新のcommit hashと共にreviewerの名前をコメントに書き込み, ラベルをS-awaiting-mergeに変更します. 他の作業とは異なり, このコメントを書き込める(botが反応する)のは, OWNERS.jsonファイルによって予め指定されたユーザーだけです.

Auto-Merge

OWNERS.jsonauto_merge.enabledtrueに指定されている場合, popukoはPull Requestの自動マージを試みます. フローは以下の通り:

  1. reviewerが@<botname> r+とコメントする
  2. popukoはマージ先のブランチに対して仮ブランチ(autoと呼びます)を作成する
  3. 2で作成されたauto branchに対して, Pull Requestの内容を仮マージする
  4. auto branchに対してCIを実行する(TravisCIであれば, CI対象ブランチを制限していなければ自動的にCIが動き出す)
  5. CIが完了したら其の結果を確認し, greenであれば改めて正式にPull Requestをマージする

これにより, masterにマージされるPull Requestは, マージ後も含めて常にgreenとなることが自動的に保証された上でマージされるようになります.

popukoによるブランチの取扱の図
f:id:saneyuki_s:20170210190956j:plain

popukoによるauto mergeの例
f:id:saneyuki_s:20170210193120p:plain

OWNERS.json

各リポジトリごとのrootディレクトリに配置します.

ここに@<botname> r+を発行できるreviewerの名前を記載しておくことで, popukoはそれを用いて, 許可した人間のみがPull Requestにマージ可能である旨を示せるようにしています.

導入の結果, 何が変わったのか?

popukoの導入の結果, 当初の目的通り, 以下の点が達成されました.

  • 3~4クリックが1クリックに減った,
  • 常にmasterの状態がgreenのまま維持されるようになった

特に前者に関しては, SlackとGitHubの二重のレビュー依頼をしている同僚も多かったのですが, popukoにより, GitHubに書くだけで自動的にassignまで行われるようになったのでGitHubだけで完結させることのほうが合理的になりました. Slackでレビュー依頼が必要な場合は, 長期間放置されているものの催促や緊急性の高い内容に限られるようになり, Slack上での会話の濃度も上がりました.

後者についても, Continuous Delivery/Deploymentの観点から非常に役に立ちます. 「動かない(既存のテストケースで洗い出せる問題のある)コードをウッカリmasterブランチに対してlandしてしまう」という人為的ミスが起こらなくなるので, より堅実かつ問題の切り分けを行いやすいプロダクトリリースを実現するための礎となります.

また, 導入前は「これ本当に便利なの?」と懐疑的だった同僚も, 今では「無いと仕事にならない. 病み付きになる」と述べるようになるなど, 「無くても成立するが, 一度使ってみると離れがたい引力がある」種類の道具となりました.

併せて社内に#popukoという名前のbotのサポート・開発専用channelを用意し, popukoのリポジトリへの設定方法についても Popuko as a Service (PaaS) と銘打って文書化しているため, 使用したい社内のリポジトリについては各リポジトリの判断で自由に導入できるようにしています.

FAQ

popukoって名前の由来は?

popukoは Practical Organized Productive Unlimited Kabuki-nized Operation の略です

popukoに機能追加するくらいならhomuをforkしたほうが良かったのでは?

forkせずスクラッチしたのは出来心も多分にありますが, Goでスクラッチしたのは以下の技術的な判断です:

  • go-gihubという便利なGitHub APIラッパーがあった
  • GitHubのREST APIを大量に呼び出すという性質と, 数多くのリポジトリで同時多発的にトリガーとなるコメントが書き込まれ, それを捌くことを考えた場合, goroutineによる処理モデルは当botの扱う問題に適していると判断した
  • シングルバイナリを生成して適当な環境に放り込んで動くのは便利
    • ゲリラ的に正規導入するかわからないプロジェクトを始めるときは, Dockerコンテナとか作らずに, とにかく大雑把に動かせることが重要
    • アプリケーション性質的にはNode.jsでも良かったが, とにかく単一ファイルを放り込んで終わりにしたかったので, この点に見合わなかった.
  • 別にhomuの機能全部は要らなかった
    • 「こんなの直ぐに実装終わるだろ!」と思った

類似のhomu再実装は他にもいくつか存在しますが, 以下の理由からそれらの採用は行っていません.

  • mgattozzi/thearesia
    • これは現在でも未完成なので, 自然と対象外になった.
  • gullintanni/gullintanni
    • r? @<reviewer>のようなhighfive相当の機能が実装されておらず, highfiveのデプロイも必要な事を考えると, homu + highfiveの構成をそのまま用意するのと大差は無かった.
    • reviewerに相当する設定をリポジトリ単位にコードとして管理させる機能も見当たらなかった
  • rultor
    • GitHub botのためにJVMを動かしたくはなかった(JVMを多用しているチームであれば, 有効だったかもしれない)
    • コマンド体系が(私は)良いとは思えなかった
    • highfive相当の機能も実装されていない

そもそもとして「 botと対話するインターフェースによって自動化がなされる 」というのが重要な点です. それを実現する実装は必ずしも重要ではありません. 今後, popukoへの機能追加が見合わないと判断した場合, 改めてhomuをforkすることや類似のbotに乗り換えることも勿論有りえます.

SlackからGitHub botを操作するほうが良いのでは?

私は良いとは思っていません. GitHubはコードを取り扱うサービスであり, コードおよびGitの操作に関してはGitHub上で完結させるべきだと考えています。merge botのヘルスチェックや再起動はSlackからの操作でも良いと思いますが、Gitの操作に関してはGitHub上でbotへの指示がなされるべきです.

masterとのconflictを検知する」とあるけれども, popukoはmaster以外に向けたPull Requestに対しては検知できないの?

現在のところは検知できる実装にはなっていません.

根本的な前提として, 我々のチームについてはmaster以外のブランチに向けてPull Requestを送る開発習慣が存在せず, masterブランチをtrunkと見做す方式でのTrunk駆動開発に基づいてプロダクトの開発をしています. feature branchを切ることや, release branch専用のPull Requestを用意することは頻度として稀です.

また, 設定を各リポジトリ毎に分散して持たせている以上, masterに相当するブランチは各リポジトリごとに持たせるべきではあります. このdesign上の制約により, masterブランチ以外も自由にtrunkとして設定可能にすると, pushイベントが発生するたびに設定ファイル取得のためにGitHub APIを消費してしまうという問題が有ります.

こうした点を考慮し, 課題であるとは認識していますが, 私達の実用上の観点から直ちに修正するべき課題であるとは考えていません.

まとめ

以下がまとめとなります

  • 面倒くさいことは自動化しましょう
    • 便利なのでGitHubにmerge botを導入しよう
  • masterブランチ(trunk)が常にgreenであることが機械的に保証される安心感はとても良い

お知らせ

弊社には AJITO という社内バーがあり, 毎夜のようにエンジニアが現れ, 酒を嗜み, 軽食をつまみつつ, エンジニアリング談義を行っています( #ajitingで既にご存知の方も多いと思います). 「私も一緒に(こういう)エンジニアリング談義をしたい!」という方がいらっしゃいましたら, 是非とも弊社エンジニア or @tech_voyageに, お声掛けいただければと思います.

また, fluctを含むVOYAGE GROUPアドテクユニットでは, 一緒に働いてくださる仲間を募集しております. VOYAGE GROUPや広告配信に興味を持たれましたら, お気軽にご連絡ください.