Git の 3-way マージで共通祖先が 2 つの場合, 共通祖先はどんな感じになるのか調べてみた


マージされる 2 つのブランチの共通祖先が 2 つの場合, マージコンフリクトが発生した場合の共通祖先はどんな感じになるのか, サクッとはいかなかったのですが, それなりに調べてみました.

ヒストリを意図的に作る

共通祖先が 2 つあるマージを行うため, 次のようなヒストリを意図的に作ってみました:

      B---E <- master <- HEAD
     / \ /
    A   X
     \ / \
      C---D <- hotfix

このようなヒストリで ED をマージさせると, 交差マージ (criss-cross merge) となり ED の共通祖先は BC の 2 つなので, その 2 つが更にマージされて 1 つの共通祖先となります.

実際の A, B, C, D, E の 5 つのコミットは次のように作りました:

echo 'A' > example.txt
git add example.txt
git commit -m 'A'

echo 'B' >> example.txt
git commit -am 'B'

git checkout -b hotfix HEAD^
echo 'C' >> example.txt
git commit -am 'C'

git merge master
echo "C\nB\nA" > example.txt
git commit -am 'D'

git checkout master
git merge hotfix^
echo "A\nB\nC" > example.txt
git commit -am 'E'

それぞれのコミットの example.txt の内容は次のようになります.

Aexample.txt:

A

Bexample.txt:

A
B

Cexample.txt:

A
C

Dexample.txt:

C
B
A

Eexample.txt:

A
B
C

実際にマージしてみる

そして次のコマンドで hotfix (D) を master (E) にマージさせてみます:

git merge hotfix

すると merge.verbosity の値を 5 にしているので, 次のような詳細情報が表示されます:

Merging:
71d4d37 E
virtual hotfix
found 2 common ancestors:
c8d7410 B
945be04 C
  Merging:
  c8d7410 B
  945be04 C
  found 1 common ancestor:
  02b437c A
  Auto-merging example.txt
  CONFLICT (content): Merge conflict in example.txt
Auto-merging example.txt
CONFLICT (content): Merge conflict in example.txt
Automatic merge failed; fix conflicts and then commit the result.

この情報から Ehotfix をマージして, BC の 2 つの共通祖先が見つかり, BC をマージして, 共通祖先 A が見つかり, マージコンフリクトが 2 回発生してオートマージが失敗したということがわかります.

マージコンフリクトの内容

マージコンフリクトが 2 回発生した example.txt の内容は次のようになります:

<<<<<<< HEAD
A
B
||||||| merged common ancestors
A
<<<<<<<<< Temporary merge branch 1
B
||||||||| merged common ancestors
=========
=======
>>>>>>> hotfix
C
<<<<<<< HEAD
||||||| merged common ancestors
>>>>>>>>> Temporary merge branch 2
=======
B
A
>>>>>>> hotfix

マージコンフリクトが 2 回発生しただけあって, なかなかやばいですね scream

しかも merge.conflictStylediff3 に設定しているので余計です.

このことから共通祖先が 2 つあるマージを行うと, このような事態になりかねないということがわかりました.

また実験している分にはいいですが, 実際のマージで共通祖先が 2 つ見つかるという事は出来れば避けたいなという気持ちになりました sweat

マーカの意味を紐解いてみる

せっかくなので, これらの難解なマーカを頼りに, どのように example.txt でマージコンフリクトが発生しているのか一つ一つ紐解いてみたいと思います.

まず example.txt から次の部分を切り取り:

||||||| merged common ancestors
A
<<<<<<<<< Temporary merge branch 1
B
||||||||| merged common ancestors
=========
=======
>>>>>>> hotfix
C
<<<<<<< HEAD
||||||| merged common ancestors
>>>>>>>>> Temporary merge branch 2

次の部分から:

<<<<<<< HEAD
A
B
=======
B
A
>>>>>>> hotfix

HEADhotfix に共通する部分はなく, この <<<<<<<======= の間の二行と, =======>>>>>>> の間の二行による競合が最初のマージコンフリクトとわかります.

そして先ほど切り取った次の部分から:

||||||| merged common ancestors
A
<<<<<<<<< Temporary merge branch 1
B
||||||||| merged common ancestors
=========
=======
>>>>>>> hotfix
C
<<<<<<< HEAD
||||||| merged common ancestors
>>>>>>>>> Temporary merge branch 2

一行目の ||||||| より下の部分が, 最初のマージコンフリクトの共通先祖の部分になり, その共通先祖は BC マージから成るので, Temporary merge branch 1Temporary merge branch 2 という文が表示されています.

つまりはその部分が二番目のマージコンフリクトになります.

また二行目の A は競合していないということもわかります.

そして競合している次の部分ですが:

<<<<<<<<< Temporary merge branch 1
B
||||||||| merged common ancestors
=========
=======
>>>>>>> hotfix
C
<<<<<<< HEAD
||||||| merged common ancestors
>>>>>>>>> Temporary merge branch 2

Temporary merge branch 1 の次の部分はいいのですが:

B
||||||||| merged common ancestors

その下の ========= の下の部分, つまりは Temporary merge branch 2 の部分:

=======
>>>>>>> hotfix
C
<<<<<<< HEAD
||||||| merged common ancestors

この部分には, 正直戸惑いました.

えっ, 次のような内容ではないのかと:

C

最初の二行や:

=======
>>>>>>> hotfix

最後の二行はなんなのかと:

<<<<<<< HEAD
||||||| merged common ancestors

どうして最初のマージコンフリクトの HEADhotfix のマーカがここに再度書き込まれているのだろうかと.

それなりに悪戦苦闘したのですが, 結局それらの 4 行の意味はわかりませんでした.

申し訳ありません.

マージコンフリクト解決ツールを使っみた

僕は Neovim を使っているので git mergetool で, その example.txt で発生しているるマージコンフリクトの内容を表示してみたところ:

Git mergetool

画面上半分の真ん中のウィンドウが ED の共通祖先, つまりは BC のマージされた内容になり:

A
<<<<<<<<< Temporary merge branch 1
B
||||||||| merged common ancestors
=========
C
>>>>>>>>> Temporary merge branch 2

このように, Temporary merge branch 2 の方には, 僕を混乱に陥れた先ほどの 4 行がなく, 極めてわかりやすいです.

このように表示してくれればいいのになと思ってしまうのですが, 何かしらの理由があるのでしょう…

まとめ

Git でマージする時に共通祖先が 2 つ見つかった場合, その 2 つの共通祖先がマージされたものが, 実際の共通祖先となるので, その 2 つの共通祖先のマージでマージコンフリクトが発生した場合は, 多少頭を悩ますことになるかもしれません.

なのでできれば, このような交差マージにならないように気をつけることができればいいですね.

参考資料