GitHubのプルリクエストをチェンジセット中心に再構成する
私はこれまで、非常に規模の大きいオープンソースプロジェクト(コントリビューターが1,000人超)でメンテナーを務めたり、非常に規模の大きいクローズドソースの企業プロジェクトでエンジニアとして働いたり、それより小規模なプロジェクトにも携わったりしてきました。そうした経験を重ねてきた今、GitHubを使っている時間のほとんどはGitHubのプルリクエストに費やしています。そして私にとって、残念ながらそこはGitHubで最もストレスのたまる部分でもあります。
プルリクエストには、改善してほしい点がたくさんあります。しかし、私が抱えている問題のかなりの部分は、たった一つの大きな機能、つまりチェンジセットによって解決できるはずです。このブログ記事では、その提案と、私が実現してほしいことについて説明します。
免責事項:ここで述べるアイデアは、私が考え出した独自のものではありません。これらのアイデアを自分が発案したとは主張しません。ここでの提案は、十分に検討されてきたGitのワークフローに基づいており、GerritやPhabricator、あるいは昔ながらのメールベースのパッチレビューといった他の製品や仕組みで、部分的または全面的にすでに実装されています。
現在の問題
現在のGitHubにおけるプルリクエストのライフサイクルは、実質的に一つの巨大な可変チェンジセットです。これはひどい状態です!
現在の典型的なPRは、次のようなものです。コントリビューターがブランチに一連のコミットをプッシュし、PRを作成します。すると、そのPRはそのブランチを表すものになります。参加者はコメントを通じてPRについて議論します。コントリビューターが新しい変更をプッシュすると、それらは同じPRに直接現れ、PRはすぐに更新されます。レビュアーがコメントを残すのと同時にコントリビューターが変更をプッシュすることもできます。そして、そのすべてが同じPR上で更新されます。
これには多くの問題があります。
レビュアーが、PRの過去の状態に対してレビューを行うことがあります。その最中にコントリビューターが変更をプッシュすると、レビューはたちまち古いものになってしまいます。
さらに悪いことに、レビューの一部だけが古くなり、コントリビューターがプッシュした変更を前提にすると、残りのフィードバックが意味をなさなくなることもあります。たとえば、ある行へのコメントに「前のコメントと同じ指摘」と書かれていても、コントリビューターがその行を移動する変更をプッシュしたために、前のコメントが今では消えている、または表示されなくなっていることがあります。1
レビューには、それがどのコミットに紐づいていたのかというメタデータがありません。記録されるのは送信時刻だけです。ユーザーは時刻とコミットをおおまかに対応づけられますが、完全に正確ではありません。レビュー中にコミットが入ると、時刻だけを見ると直近のコミットより後にレビューしたように見えても、実際にはその一つ前のコミットを対象にしていたかもしれないからです。😕
レビューのフィードバックに対応するための作業途中のコミットは、ブランチにプッシュされた時点ですぐに見えるようになります。そのため、コントリビューターはすべてのフィードバックに一つのコミットで対応することを強いられるか、レビュアーが対応途中のフィードバックを扱わなければならなくなります。
PRの過去の状態を簡単にたどることができません。後のコミットを無視して以前のコミット群をレビューしたい場合、手作業で「compare」ビューを作るか、ローカルのチェックアウトを使う必要があります(私は後者を使っています)。しかし、どちらの方法でも得られるのはコードの変更だけで、その時点でのレビュアーのフィードバックまでは得られません。
これと似ていますが、コントリビューターが新しいコミットを複数プッシュした場合、新しいコミット群と古いコミット群を簡単に比較できません。実際にたどれるのは一度に一つのコミットだけです。この場合も、結局はローカルの
gitに頼って、手作業で差分を組み立てる必要があります。ほかにもあります……このあといくつか取り上げますが、言いたいことは伝わったと思います。
上に挙げた項目の細部については、私が間違っているところもきっとあるでしょう。「問題5(a)なら、こうすれば解決できたのに」と言う人もいるはずです。それは有益な指摘です。しかし私が言いたいのは、一歩引いて考えれば、こうした問題を引き起こしている根本の仕組みこそが本当の問題だということです。つまり、コミット単位でブランチを追跡する、一つの可変チェンジセットです。
チェンジセット
解決策はチェンジセットです。プルリクエストを、単調増加する番号(v1、v2……)によってバージョン管理できるようにします。こうしたバージョンは、しばしば「チェンジセット」と呼ばれます。
各チェンジセットは、ある固定された時点におけるブランチの状態を指します。これらのバージョンは不変です。新しいコミットがプッシュされると、それは新しいチェンジセットの一部になります。コントリビューターがブランチに強制プッシュした場合も、同じく新しいチェンジセットの一部になります。以前のチェンジセットは永続的に保存されます。
新しいチェンジセットは、すぐに公開しても構いません(コミットごとに公開する方式です)。あるいは、コントリビューターがレビュー用の新しいバージョンを提案すると決めるまで保留しても構いません。後者なら、以前のフィードバックに対応するためのコミットを複数作成し、準備ができたと思った時点で初めて変更を公開できます。
チェンジセットの世界では、フィードバックはチェンジセットに紐づきます。レビュアーがあるチェンジセットのレビューを始めたあとに新しいチェンジセットが公開されても問題ありません。レビューは一つのまとまりとして、以前のチェンジセットに紐づいたままだからです。
後続のチェンジセットでは、以前のチェンジセットで未解決のコメントが付いているファイルや行を示せると便利なことがよくあります。これにより、以前のチェンジセットへのフィードバックが失われず、どのチェンジセットを受け入れる前にも対応しなければならないことが明確になります。
通常、各チェンジセットは異なるGitのrefで表されます。たとえば、現在のGitHubのプルリクエストは通常refs/pr/1234であり、ローカルでgitを使ってこのように任意のプルリクエストをチェックアウトできます。チェンジセットなら、たとえば(仮に)refs/pr/1234/v2のようになり、個々のチェンジセットもチェックアウトできます。
PRを「承認」してマージする代わりに、レビュアーはチェンジセットを承認します。これにより、コントリビューターは一つのPRの中で、問題に対する異なるアプローチを取ったチェンジセットを複数投稿できます。そしてメンテナーは、最新ではないチェンジセットを、マージしたい変更一式として選べる可能性もあります。
GitHubにお願い
チェンジセットは、多くのオープンソースプロジェクトや企業で確立されたパターンです。GerritやPhabricatorのような既存の製品で、ユーザー体験としてもすでに十分に検討されています。また、チェンジセットは既存の仕組みを壊さずに導入できるとも考えています(現在のPRが、一つの可変チェンジセットとして動作しているようなものだからです)。
チェンジセットがあれば、より大規模なプロジェクトや組織でも、プルリクエストをはるかにスケーラブルに扱えるようになります。規模への対応だけでなく、PRに関わる双方にとって、レビューのプロセスがより整理され、安全になります。
もちろん、ここで語れるのは自分自身の経験と意見だけです。しかし、この一つの大きな機能があれば、GitHubを使う際の私の生活の質と能力は劇的に向上するでしょう。2
脚注
記事をランダムに読む