マージコミットの分割が1本線の履歴の分割よりも困難となる理由

git-bisect(1) コマンドはマージコミットを含んだ履歴を正確に扱います。 しかし、コミットがマージコミットである時は、そのコミットが何故問題を起こしている かの原因を見つけ出すのに苦労することがあります。

次の履歴を考えてください:

      ---Z---o---X---...---o---A---C---D
          \                       /
           o---o---Y---...---o---B

上側の開発ライン上のコミットXにて Zから存在する関数の意味が 変更されたとします。ZからAにつながるコミットは、関数の実装を変更し、 Zの時点で存在する全コール箇所と新しく追加したコール箇所の両方を変更して、 矛盾のない状態にしていたとします。 A の時点ではバグはありません。

それと同時に下側の開発ラインではコミットYにてその関数の新しい コール箇所を追加していたとします。 ZからBにつながるコミットの全ては、その関数の古い動作を想定していて コール元とコール先は互いに矛盾していません。Bにもバグはありません。

2つの開発ラインがきれいにマージできたとすると、 コンフリクトの解消要求はありません。

にもかかわらず、Cのソースは壊れています。なぜなら 下側の開発ラインは上側の開発ラインで行われた新しい動作に変換 されていないからです。 したがって、git-bisect(1) は D に問題があり、 Z には問題ないこと、そしてC が問題の原因であると伝えます。 どのようにしたら、問題が動作変更によるものだと見つけることが できるでしょうか?

git bisect の結果が非マージコミットの場合、通常、単にそのコミットを 確認するだけで問題を見つけることができます。 開発者はコミットを自己完結したより小さいものに分割することで これを容易に行うことができます。しかし、 上記場合には1つのコミットを確認することでは問題が明らかにならない為、 このような方法では解決の役に立ちません;その代わりに開発の大局的な視点が 必要となります。さらに悪いことに、問題となっている関数の動作変更が 上側の開発ラインの単なる小さな部分の変更であるかもしれません。

その一方で、C のマージをする代わりに、ZからBの履歴を Aの先頭 にリベースした場合、次のような1行の履歴を取得することができます:

    ---Z---o---X--...---o---A---o---o---Y*--...---o---B*--D*

Z と D* の間を git bisect すると、1つの問題の原因となるコミット Y* を見つけることができ、Y* が何故問題を引き起こしている理由を 容易に理解することができるでしょう。

部分的にですが、この理由により、たくさん経験を積んだ git ユーザは マージが頻繁に行われるプロジェクトで作業する場合でさえも 最新の上流バージョンに対するリベースを行うことによって 履歴を1ラインに保つようにしています。