Git チーム ワークフローのベスト プラクティス: マージかリベースか?

Nicola Paolucci
質問は簡単です。 git
とフィーチャー ブランチを利用しているソフトウェアチームにとって、完了済みの作業を開発のメインラインに取り込む最も優れた方法は何でしょうか。これは、それぞれ強い主張を持つ両陣営が何度も繰り返している議論の一つですが、落ち着いた話し合いが時として難しくなる場合があるものです。 (激しい議論が交わされた他の例については、インターネット をご覧ください)。
リベース ポリシーに従い、リポジトリの履歴をフラットかつクリーンに保つべきでしょうか?それとも、可読性と明晰さを犠牲にすることでトレーサビリティを得られる、マージを行うべきでしょうか (ファストフォワード マージを禁止するなど)
議論
このトピックは、vim と Emacs や Linux と BSD ほどまでには有名な論争の的とはなっていないものの、双方共に遠慮なく意見を述べ合っています。
all-things-git
に対する私の経験的な印象としては、常にマージするアプローチの方が若干マインド シェアが大きい気がします。しかし、常にリベースするアプローチの援護者も、オンライン上でかなり積極的に意見を述べています。以下はその例です。
正直なところ、ローカル クリーンアップとしてのリベースとチーム ポリシーとしてのリベースは別物であるため、常にリベース vs. 常にマージという両陣営の対決は話がややこしいのです。
余談: コーディングのライフサイクルにおいては最高な、クリーンアップとしてのリベース
チーム ポリシーとしてのリベースは、クリーンアップとしてのリベースとは異なります。クリーンアップとしてのリベースは、git
の専門家のコーディング ライフサイクルの健全な一部を構成しています。以下に、リベースを行うことが合理的かつ効果的となる場合とならない場合の、いくつかのシナリオを解説します。
- ローカルで開発中。あなたは、自分の作業を誰とも共有していません。この時点では、履歴をクリーンに保つためにはマージよりもリベースを優先した方がよいでしょう。仮に、リポジトリの個人的なフォークがあってそれを他の開発者と共有していない場合、自分のフォークにプッシュした後でもリベースできます。
- コード レビューの準備が整っている。あなたがプル リクエストを作成しており、その作業を他の開発者が確認している状況です。開発者がローカルで確認するために自分達のフォークにフェッチしている場合もあります。この時点では、あなたは自身の作業をリベースしてはなりません。「リワーク」のコミットを作成して、フィーチャー ブランチを更新する必要があります。これにより、プル リクエストのトレーサビリティが向上し、不慮の履歴破損を防ぐことができます。
- レビューは終了して、ターゲット ブランチへの統合の準備が整っている。おめでとうございます! これで、フィーチャー ブランチを削除できます。これ以降、他の開発者はこれらの変更点をフェッチしてマージすることがないため、これを機に履歴を削除しましょう。ここでは履歴をリライトして、元のコミットとやっかいな「pr リワーク」を、それに重点を置く少数のコミットにマージしてまとめることができます。これらコミットへの明示的なマージの作成は任意ですが、フィーチャーからマスターへの進行が記録されるため、その価値はあります。
この件について明らかにしたところで、ポリシーについて説明しましょう。私はこの議論に対してバランスの取れた見解を保つよう心がけると共に、Atlassian 内におけるこの問題への対処法を紹介します。
リベースチームポリシー: 定義、良い点と悪い点
それぞれのチームは皆異なるため一般論を述べるのは難しいですが、とりあえず取り組んでみないことには始まりません。まず、次のポリシーを一例として検討してみてください。フィーチャー ブランチの開発が完了したら、すべての作業内容を可能な限り最小限のコミット数にまでリベース/スカッシュして、マージ コミットの作成を回避します。この際、変更点が確実にファストフォワードされます (あるいは単純に、それらのコミットをターゲット ブランチに cherry-pick
します)。
作業がまだ進行中で、上流のターゲット ブランチに合わせてフィーチャー ブランチを最新の状態にする必要がある場合、履歴を上辺だけのマージで汚さないためにも pull
または merge
の代わりに rebase
を使用します。
良い点
- コード履歴は、フラットかつ可読のままになります。クリーンかつ明確なコミットメッセージは、コードコメント、または課題トラッカーへのコメント等と同様にコードベースの文書の一部を構成しています。このため、単一のフィーチャーまたはバグ修正のために相殺してしまう、31の単一行のコミットで履歴を汚してしまわない事が重要です。このような状況においては、バグまたはフィーチャーが導入された時期とその理由を求めて履歴を遡って探し出す行為は、非常に難しくなります。
- 単一のコミット操作が簡単(例、元に戻す)。
悪い点
- 全ての開発履歴を備えた履歴ブランチを保持しておかない限り、フィーチャーを一握りのコミットにまで融合してしまうとコンテキストが隠れてしまいます。
- 誰かがリベースしてしまうと、その人が行った細かい変更点を確認できないため、リベースはプル リクエストとうまく適合しません (また、Bitbucket 開発チーム内では、プル リクエスト中には絶対にリベースしないことがコンセンサスとなっています)
- リベースには、危険な場合があります! 共有ブランチの履歴をリライトすると、チームワークが崩れる要因を作ってしまうのです。これは、フィーチャー ブランチのコピー上でリベース/スカッシュを行うことでこの問題は軽減できるものの、リベースの扱いには能力と注意力が要求されることを暗示しているのです。
- 作業が増えます。お使いのフィーチャー ブランチを最新の状態に保つためにリベースを利用する場合、類似したコンフリクトを何度も繰り返し解決しなくてはなりません。確かに、rerere (reuse recorded resolutions) が可能な場合もありますが、ここでは
merge
に軍配が上がります。マージならば、一度コンフリクトを解決すればすべて完了です。 - リモート ブランチを使用してリベースすることのもう 1 つの副作用は、ある時点で強制的にプッシュしなくてはならない点にあります。今まで Atlassian で起きた最大の問題は、皆が強制的にプッシュしたまではよかったけれども、
git push.default
を設定していないという点でした。こうなると、ローカルとリモート双方における同一名のブランチがすべて更新されるため、非常に煩わしい状況になります。
注: 複数の開発者が携わっている共有ブランチで履歴がリライトされると、破損が生じます。
マージチームポリシー: 定義、良い点と悪い点
逆に、常にマージすることをベースにしたポリシーは次のような流れになります。フィーチャー ブランチが完了したら、ターゲット ブランチ (main
または develop
、または next
) にマージします。
必ず、マージは --no-ff
で明示的に指定します。これによって、変更点を自動的にターゲット ブランチで再現できる場合でも、git
はすべてのケースでマージ コミットを記録するよう強制されます。
良い点
- トレーサビリティ: フィーチャーブランチの歴史に関する情報を保持して、フィーチャーの全てのコミット部分を集合化します。
悪い点
- 大量のマージ コミットによって履歴が極度に煩雑になり、リポジトリの視覚的チャートには情報としてさほど役に立たない虹色のブランチ ラインが表示され、何が起きているのかがわかりづらくなります。(公平を期すために言えば、履歴内をどのようにナビゲートするかを把握しておくだけで、混乱は簡単に解決できます。ここでのポイントは、たとえば
git log --first-parent
を利用して、何が起きたかを理解することです)

git bisect
を利用したデバッグは、マージ コミットのために非常に難しくなります。
決断しなければなりません。あなたにとって最も重要なものは何でしょうか?
どれが一番良いのでしょうか?専門家のお勧めはどれでしょうか?
あなた自身、あるいはあなたのチームが rebase
の複雑さに慣れていないか、これを理解できないのであれば、利用しない方が賢明でしょう。このような状況では、常にマージすることが最も安全な選択肢となります。
あなた自身、およびあなたのチームが両方の選択肢を十分理解しているのであれば、以下が決断のポイントとなります。重視するのは、クリーンでリニアな履歴ですか? それとも、ブランチのトレーサビリティでしょうか?前者であればリベース ポリシー、後者であればマージ ポリシーを選びましょう。
また、リベースポリシーには少数の禁忌が存在するため、より多くの手間が要求されますのでご注意下さい。
アトラシアンのケース
アトラシアンの Bitbucket チーム内においては、フィーチャー ブランチを常にマージすること、そして品質とコード レビューのためにブランチはプル リクエストを通じてマージされることがポリシーとなっています。ただし、チームはファストフォワードに関してはさほど厳密ではありません。
結論と謝辞
この記事は Bitbucket チームとの洞察力あふれるやり取りの confluence (合流) から生まれました。
この記事によってこれまでの疑念が解消され、あなたのチームに適したアプローチを採用するきっかけとなれば幸いです。git
のすばらしさをもっと知りたい場合は、@durdn で私、または @AtlDevtools チームをフォローしてください。