同じ衝突を三度目に解いている人は、何かを見落としている
一言でいうと
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は、「このブランチはマージしないことにした」という事実を、履歴に残す用途です。相手のブランチが祖先として付くため、次回からそのブランチは、マージ対象から外れます。内容を取り込む意図でこれを使うと、履歴にはマージされたと書かれているのに、コードには何も入ってきていない状態になります。そして、あとで誰もそれを疑いません。
現場での姿
- リリースブランチを
mainに定期的に戻してマージするとき、バージョン番号の行で毎回同じ衝突が起きます。rerereをオンにしておくと、2回目からは、マーカーを見るだけで通り過ぎます。 - リベースを中断して再開することが多い人にとって、rerereの価値が最も大きいです。同じ衝突を繰り返し経験するからです。
- 生成ファイル(ロックファイル、バンドルのアーティファクト)が毎回衝突するなら、解決を記憶させるより、
.gitattributesにマージ方式を指定するか、そもそも追跡しないほうがよいです。 - チームのルールとして、
merge.conflictStyle=zdiff3を勧める場所が増えています。設定1行で、「元は何だったのか」を推測しなくて済むようになるからです。
次のラボですること
同じ箇所を別々に直した2つのブランチを作り、デフォルトの表示とzdiff3の表示を、並べて見ます。rerereをオンにして一度解決したあと、マージを取り消して再びマージし、gitが自分で同じ解決を適用するのを確認します。最後に、-X oursと-s oursを1回ずつ使って、結果のファイルに何が残り、何が消えるのかを、内容で確認します。
公式ドキュメントは、git-rerere、git-merge、git-configです。