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

Git実戦

同じ衝突を三度目に解いている人は、何かを見落としている

TT Labで続きを見る

一言でいうと

rerereは、衝突をどう解決したかを記録しておき、同じ衝突が再び出てきたら、そのまま適用します。merge.conflictStyle=zdiff3は、衝突マーカーに共通祖先も一緒に表示して、「誰が何を変えたのか」を推測しなくて済むようにします。-X oursと-s oursは、名前が似ているだけで、結果はまったく違います。

なぜ必要なのか

長く生きる機能ブランチをmainの上にリベースし続けると、同じ箇所で同じ衝突が毎回起きます。コミット10個をリベースすると、その衝突を10回解決します。うっかり--abortしてやり直すと、また10回です。疲れると手が滑り、滑った解決は、静かにコミットされます。

そして、衝突マーカー自体も、情報が不足しています。デフォルトの表示は、次のようになります。

<<<<<<< HEAD
b by main
=======
b by topic
>>>>>>> topic

ここで、わからないことが1つあります。元は何だったのかです。片側が削除したのか、双方が別々に直したのか、片側はそのままで、もう片側だけが直したのかが、区別できません。そのため、「安全に見える側」を選ぶことになり、その判断が間違っていると、他の人の修正が消えます。

どう動くのか

zdiff3: 共通祖先を一緒に表示する

git config merge.conflictStyle zdiff3

すると、衝突マーカーに、真ん中の欄がもう1つできます。

<<<<<<< HEAD
b by main
||||||| 75364cd
b
=======
b by topic
>>>>>>> topic

|||||||の下が、共通祖先の内容です。これで、「元はbだったのに、双方が別々に直した」ことが、ひと目でわかります。diff3という古い設定値も同じことをしますが、zdiff3(git 2.35以上)は、双方に共通して残った行を衝突の外に出してくれるため、表示が短くなります。

rerere: 解決を記憶する

git config rerere.enabled true

オンにしておくと、衝突が起きたとき、gitが衝突前の姿(preimage)を.git/rr-cacheに記録し、コミットするときに、解決した結果(postimage)を同じ場所に付けておきます。次に同じpreimageが出てくると、こう言います。

Resolved 'handler.txt' using previous resolution.

注意すべきことが3つあります。1つ目は、rerereは解決を適用するだけで、ステージングはしないことです。結果を見てgit addするのは、人の役目です(rerere.autoUpdateをオンにすると、自動でステージングされますが、確認なしで通り過ぎてしまうため、勧めません)。2つ目は、記録が.gitの中にあるため、自分のマシンにしか残らないことです。3つ目は、間違って解決したものも、同じように記憶することです。そのときは、git rerere forget <경로>で、その記録を削除する必要があります(プレースホルダーはパスです)。

-X oursと-s oursは、まったく違う

名前が似ているため、よく取り違えられます。

スイッチ 何か 結果
-X ours デフォルトのマージ戦略に渡すオプション 衝突した箇所だけを自分たちのものに選び、衝突しなかった相手の変更は、そのまま入ってきます
-s ours 戦略そのもの 相手のツリーをまるごと捨てます。結果のツリーは、マージ前の自分たちのものと、1文字も違いません

-s oursは、「このブランチはマージしないことにした」という事実を、履歴に残す用途です。相手のブランチが祖先として付くため、次回からそのブランチは、マージ対象から外れます。内容を取り込む意図でこれを使うと、履歴にはマージされたと書かれているのに、コードには何も入ってきていない状態になります。そして、あとで誰もそれを疑いません。

現場での姿

次のラボですること

同じ箇所を別々に直した2つのブランチを作り、デフォルトの表示とzdiff3の表示を、並べて見ます。rerereをオンにして一度解決したあと、マージを取り消して再びマージし、gitが自分で同じ解決を適用するのを確認します。最後に、-X oursと-s oursを1回ずつ使って、結果のファイルに何が残り、何が消えるのかを、内容で確認します。

公式ドキュメントは、git-rerere、git-merge、git-configです。