TT Lab
はじめる
学ぶ 学習パス コース

CI/CDパイプライン

グリーンはマージ後もグリーンのままか

TT Labで続きを見る

一言でいうと

ブランチでは緑だったのに、マージした途端に赤になることは、珍しくありません。ブランチの検査が見たのは、自分の変更にそのときのトランクを足したものであって、マージされたあとのトランクではないからです。このギャップを縮めるのが、短命なブランチとサーバー側のルールの役割です。

なぜ必要なのか

トランクベース開発は、「開発者がトランクと呼ばれる1つのブランチで協働し、ほかの長命な開発ブランチを作ろうとする圧力に抵抗するソース管理モデル」と定義されます。短命なブランチは、コードレビューとビルドの検査のために使いますが、すぐに消えることを前提にします。リリースブランチも、必要なときにトランクから切って使い、リリースのあと、しばらくして削除します。リリースに付けるバージョン名にも、同じ性質が求められます。セマンティックバージョニングの規約は、一度リリースされたバージョンの内容を変更してはならず、直すべきものがあれば新しいバージョンとして出さなければならないと明記しています。すでに出たものに手を加えないほうが、元に戻す対象を残します。

なぜ長命なブランチを避けるのでしょうか。ブランチが長くなるほど、トランクとの距離が開き、開いた距離は、マージするときにまとめて請求されます。そして、その請求書で怖いのは、テキストのコンフリクトではありません。テキストのコンフリクトは、ツールが教えてくれます。怖いのは意味のコンフリクトです。自分がブランチで使っていた関数の動作を、ほかの人がトランクで変更したなら、2つの変更は別々の行にあるので、きれいにマージされて、結果だけが間違います。バージョン管理ツールは、これを検知できません。

フィードバックの観点でも、答えは同じです。統合が先送りされるほど、問題に気づくのが遅れ、遅く気づくほど、原因の候補が増えます。「どんなコードも、2、3時間を超えて統合されないままにしない」というケント・ベックの文が、目標の線を最も短く示しています。

ブランチ保護は、サーバー側のルールでなければならない

ルールを人の約束にしておくと、守られません。忙しい日、障害対応の最中、新しく来た人の最初の週に、崩れます。そのため、ルールはプッシュする側ではなく、受け取る側に置きます。

git自体が、この区別を持っています。フックは、既定では$GIT_DIR/hooksにあり、クローンでついてきません。git initがテンプレートをコピーすることはできますが、自分が作ったpre-commitフックが、同僚のコピーに自然にできるわけではありません。つまり、ローカルのフックは便宜のための仕組みであり、強制する手段ではありません。強制は、サーバー側のフックが行います。

ホスティングサービスのブランチ保護も、同じ場所の機能です。GitHubの保護ルールには、マージ前にプルリクエストを必須にする設定、必須ステータスチェック、会話の解決を必須にする設定、署名付きコミットを必須にする設定、直線状の履歴を必須にする設定、マージキューを必須にする設定、デプロイの成功を必須にする設定、ブランチのロック、プッシュできる人の制限、強制プッシュの許可、削除の許可があります。

ここで、必ず知っておくべき既定値が1つあります。保護ルールは、既定では管理者に適用されません。ドキュメントは、リポジトリの管理者権限や保護のバイパス権限を持つ人には、制約が適用されないと書き、「上記の設定のバイパスを許可しない」を有効にすると、管理者にも適用されると案内しています。ルールを有効にして安心していたのに、最も危険な変更がそのルールを素通りしていく事態が、ここで起きます。

緑だったのに、マージ後に赤になる問題

必須ステータスチェックを有効にしても、ギャップは残ります。ブランチAとブランチBが、それぞれトランクを基準に緑だったのに、Aを先にマージすると、Bの検査結果はもう現在のトランクに対するものではありません。そのままマージすると、トランクが壊れます。

解決の方向は2つです。

長いブランチの代わりに、フィーチャーフラグ

「この機能はまだ完成していないので、マージできない」が、長命なブランチのよくある理由です。答えは、完成していないものをトランクに入れても、有効にしないことです。フィーチャーフラグと、抽象化によるブランチ(branch by abstraction)が、その方法です。未完成のコードがトランクにありながら、ユーザーには見えないので、統合は毎日行いつつ、公開の時点は別に決められます。

その代わり、フラグにはコストがあります。フラグが増えると、実際に動く組み合わせが増え、テストがカバーできない経路が生まれます。フラグを作るときに、削除する時点を一緒に決めておくことが、唯一通用する管理方法です。

現場での姿

参考

次のラボですること

ローカルにベアリポジトリを作って、受け取る側のルールを自分で作ります。pre-receiveとupdateのフックを使って、保護ブランチへの直接プッシュを防ぎ、特定のリファレンスだけを拒否するのと、プッシュ全体を拒否するのとの違いを、終了コードで確認します。続いて、ローカルのフックを作っておいて、クローンしたコピーにそのフックがついてこないことを目で見ます。必須チェックの模倣は、コミットに付けた検査結果をフックが確認する方式で作り、意味のコンフリクトは、2つのブランチがそれぞれ通ったあとにマージしたら壊れる例で再現します。最後に、マージ前にトランクを反映して再検査するキューの模倣をスクリプトで作り、列が長くなるときに検査の回数がどう変わるかを数えます。