Git の merge.conflictStyle を merge から diff3 に変更したら, マージコンフリクトがより解決しやすくなった


Git でマージしてマージコンフリクトが発生した場合, 発生箇所に <<<<<<<, =======,>>>>>>> といったマーカが表示されますが, その 3 つのマーカに加えて ||||||| という新たなマーカを表示させたら, マージコンフリクトがより解決しやすくなったので, そのことをシェアさせていただきます.

merge.conflictStylemerge の場合

まず Git の merge.conflictStyle の値がデフォルトの merge の場合, マージコンフリクトはどのように生じるのかということから見ていきたいと思います.

次のようなコミットをしてきたとします:

             D <- hotfix
            /
           /
    A --- B --- C <- master <- HEAD

そして A, B, C, D のコミットで example.txt を次のように編集してきたとします:

Aexample.txt:

This is the first sentence from branch 'master'.

master ブランチより一文加えました.

Bexample.txt:

This is the first sentence from branch 'master'.
This is the second sentence from branch 'master'.

もう一文加えました.

Cexample.txt:

This is the first sentence from branch 'master'.
This is the modified second sentence from branch 'master'.
This is the third sentence from branch 'master'.

二行目の文中に modified という形容詞を付け加え, さらにもう一文を加えました.

Dexample.txt:

This is the first sentence from branch 'master'.
This is the second sentence from branch 'master'.
This is the third sentence from branch 'hotfix'.

hotfix ブランチより B の内容に一文加えました.

そして次のコマンドで hotfix ブランチを HEAD である master ブランチにマージさせると:

$ git merge hotfix
Auto-merging example.txt
CONFLICT (content): Merge conflict in example.txt
Automatic merge failed; fix conflicts and then commit the result.

メッセージに Merge conflict in example.txt とあるように example.txt に次のようなマージコンフリクトが発生します:

This is the first sentence from branch 'master'.
<<<<<<< HEAD
This is the modified second sentence from branch 'master'.
This is the third sentence from branch 'master'.
=======
This is the second sentence from branch 'master'.
This is the third sentence from branch 'hotfix'.
>>>>>>> hotfix

このように merge.conflictStyle の値がデフォルトの merge だと, 他のブランチとのコンフリクトが発生したところに <<<<<<<, =======, >>>>>>> というマーカが書き加えられます.

このマーカによって, <<<<<<<======= の間の部分は HEAD (master ブランチ) のもので, =======>>>>>>> の間の部分は hotfix ブランチのものと確認できます.

つまりはその部分で競合が起こっているとわかります.

そしてこの情報を参考に, どちらか一方のブランチのものを残したり, それぞれのブランチのものから一部切り取って新しい内容にしたりして, 最終的な内容が決まったらそれらのマーカを削除して git add example.txt でマージコンフリクトを解決し, git commit でマージコミットを作ります.

こんな感じで merge.conflictStyle の値が merge だと, <<<<<<<, =======, >>>>>>> というマーカによって, マージさせるブランチ同士の先端の違いを確認することができます.

merge.conflictStylediff3 の場合

ただ git merge hotfix によるマージは hotfix という 1 つのブランチを HEADmaster ブランチにマージさせるものなので, マージストラテジが自動的に recursive (再帰的) となります.

再帰的マージストラテジは 3-way マージというアルゴリズムが使われるので, マージさせる masterhotfix ブランチのそれぞれの先端 CD に加えて, その 2 つの先端の共通祖先であるコミット B が使われます.

なのでその共通祖先 B の情報もマージコンフリクトを解決する時, 知りたいなという場合, merge.conflictStyle の値を diff3 と変更すると ||||||| という新たなマーカが追加され, 共通祖先の情報も一緒に確認することができるようになります.

一例として次のコマンドでそのように設定できます:

git config --global merge.conflictStyle diff3

すると, 先ほどの git merge hotfix により example.txt に発生するマージコンフリクトは次のようになります:

This is the first sentence from branch 'master'.
<<<<<<< HEAD
This is the modified second sentence from branch 'master'.
This is the third sentence from branch 'master'.
||||||| merged common ancestors
This is the second sentence from branch 'master'.
=======
This is the second sentence from branch 'master'.
This is the third sentence from branch 'hotfix'.
>>>>>>> hotfix

このように ||||||| の下の This is the second sentence from branch 'master'. という一行が, マージコンフリクトが発生している <<<<<<< から >>>>>>> までの範囲における B の時の内容と確認することができます.

あと一行目の This is the first sentence from branch 'master'. はそのマージコンフリクトを示すマーカの範囲外なので, B の完全なファイル内容は, その一行目と ||||||| の下の一行との二行だったということもわかります.

このように共通祖先 B の時の example.txt の内容を知ることができます.

そして <<<<<<<||||||| の間の二行は, その共通祖先の内容と照らし合わせることによって, その一行目の modified という形容詞は元の一文に書き加えられたもの, その二行目は新しく追加された一文であるとわかります.

そして =======>>>>>>> の間の二行は, 同じように共通祖先の内容と照らし合わせて, その一行目は元からあった一文であること, その二行目は新たに追加された一文であるとわかります.

このようにマージコンフリクトを解決する際に, マージするブランチ同士の共通祖先の内容を確認することができると, このブランチのこの内容は元からあったものなのか, 修正されたものなのか, それとも新しく追加されたものなのかといったより詳しい情報を得ることができます.

これが merge.conflictStyle の値を diff3 に設定すると得られるメリットです.

デフォルトの merge ですと, マージする 2 つのブランチの先端の違い, 比較にとどめられます. その違い, 競合がどのような経緯で発生したのかということまではわかりません.

まとめ

merge.conflictStyle の設定をデフォルトの merge のままでも, 十分マージコンフリクトを解決することができますが, マージするブランチ同士の共通祖先の情報も確認してより詳しい情報をもってマージコンフリクトを解決されたいという場合は merge.conflictStyle の設定を diff3 にされることをオススメします.

僕は merge.conflictStyle の値は merge のままでも十分だと思っていたのですが, diff3 に変更してから共通祖先という情報が結構重要な情報だということに気付かされました sweat_smile

なんでこの diff3 という設定がデフォルトじゃないんだろうとも思いましたが, 2 つのブランチをマージさせる時は 3-way マージというアルゴリズムが使われているということを知らないと, ||||||| というマーカは混乱を招いてしまう恐れがあるからなのかもしれません.

僕もマージが 3-way マージということを知らなく, マージコンフリクトが発生した時に ||||||| というマーカが表示されたとしたら確実に混乱してグーグル先生と格闘して容易にその日を終えてしまっていただろうことは想像に容易いです dizzy_face

参考資料