このドキュメントでは、rerereのロジックについて説明します。
競合の正規化
記録された競合解決をrerereデータベースで確実に検索できるように、ブランチが異なる順序でマージされた場合でも、 異なるブランチがマージされて同一の競合が発生するか、または、異なる競合スタイル設定が使用されている場合、rerereはそれらをrerereデータベースに書き込む前に競合を正規化します。
競合マーカーからラベルを取り除き、そして、 diff3
または zdiff3
競合スタイルから共通の祖先のバージョンを削除することにより、異なる競合スタイルとブランチ名が正規化されます。 異なる順序でマージされるブランチは、競合ハンクを並べ替えることによって正規化されます。 以下のセクションで、これらの各ステップについて詳しく説明します。
これらの2つの正規化操作が適用されると、正規化された競合に基づいて競合IDが計算されます。これは、後でrerereが、rerereデータベースで競合を検索するために使用します。
Removing the common ancestor version
AB、AC、AC2 の3つのブランチがあるとします。 これらのブランチの共通の祖先には、文字列「A」の行を含むファイルがあります(簡潔にするために、これはドキュメントの残りの部分では「行A」と呼びます)。 ブランチABでは、この行は「B」に変更され、ACでは、この行は「C」に変更され、ブランチAC2は、行が「C」に変更された後、ACから分岐します。
ブランチABACをブランチABからフォークし、ACをブランチABにマージすると、以下のような競合が発生します:
<<<<<<< HEAD
B
=======
C
>>>>>>> AC
AC2と同様のことを行うと(ブランチABからブランチABAC2をフォークし、次にブランチAC2をそれにマージする)、diff3 または zdiff3 競合スタイルを使用して、以下のような競合が発生します:
<<<<<<< HEAD
B
||||||| merged common ancestors
A
=======
C
>>>>>>> AC2
この競合を解決して行Dを残すために、ユーザーは以下のように宣言します:
ABとACのブランチが何をしたかを調べた後、
行Aを行Dにすることが、
ABとACがやりたかったことと互換性のある最善の方法であると私は信じています。
ブランチAC2はACと同じコミットを参照するため、上記は、これがABとAC2が実行したかったこととも互換性があることを意味します。
ひいては、これは、rerereが上記の競合が同一であることを認識する必要があることを意味します。 これを行うには、競合マーカーのラベルを削除し、共通の祖先バージョンを削除します。 上記の例は両方とも、以下の正規化された競合を引き起こします:
<<<<<<<
B
=======
C
>>>>>>>
Sorting hunks
上記同様に、共通の祖先が、前半に行A、後期に行Xのファイルを持っていたと想像してみてください。 そして、これらのことを行う4つのブランチがフォークされます:
-
AB: changes A to B
-
AC: changes A to C
-
XY: changes X to Y
-
XZ: changes X to Z
ここで、ブランチABACをブランチABからフォークしてから、ACをそれにマージし、ブランチACABをブランチACからフォークしてから、ABをブランチにマージすると、異なる順序で競合が発生します。 前者は「Aが、BかCになった、今は何?」と言うでしょう。 後者は「Aが、CまたはBになった、今は何?」と言うでしょう。
思い出してください、ACをABACにマージし、競合を解決して行Dを残すという行為は、ユーザーが以下の宣言をすることを意味します:
ABとACのブランチが何をしたかを調べた後、
行Aを行Dにすることが、
ABとACがやりたかったことと互換性のある最善の方法であると私は信じています。
したがって、ABをACABにマージするときに発生する競合は、同一の方法で解決する必要があります — その宣言に沿った決断です。
同様に、以前にブランチXYXZがXYからフォークされ、XZがそれにマージされ、「Xが、YまたはZになった」を「XがWになった」に解決されたと想像してください。
ここで、ブランチABXYがABから分岐され、XYがマージされた場合、ABXYの初期部分には行Bがあり、後期部分には行Yがあります。 このようなマージは非常にクリーンです。 これらの4つのブランチ((AB, AC) x (XY, XZ)
)を使用して4つの組み合わせを構築できます。
ABXYとACXZをマージすると、「初期のAが、BまたはCになり、後期のXが、YまたはZになります」という競合が発生し、ACXYとABXZをマージすると、「初期のAが、CまたはBになり、後期のX、がYまたはZになります」。 これで("BまたはC"、 "CまたはB")x( "XまたはY"、 "YまたはX")の4つの組み合わせがあることがわかります。
並べ替えることにより、競合には「初期部分がBまたはCになり、後期部分がXまたはYになります」という正規の名前が付けられ、これら4つのパターンのいずれかが表示されるたびに、先ほど見た同一の競合と解決に到達できます。
並べ替えがないと、組み合わせの爆発から以前の解決策をどうにかして見つける必要があります。
Conflict ID calculation
競合の正規化が行われると、競合IDは、<NUL>文字で区切られ、競合ハンクのお互いを合わせたモノのsha1ハッシュとして計算されます。 競合マーカーは、sha1が計算される前に削除されます。 したがって、上記の例では、行Aを行Cに変更するブランチACを、行Aを行Cに変更するブランチABにマージすると、競合IDは SHA1(B <NUL> C <NUL>
)になります。
1つのファイルに複数の競合がある場合、sha1は同じ方法で計算され、すべてのハンクがファイルに表示される順序で<NUL>文字で区切られてお互いに合わせられます。
Nested conflicts
入れ子になった競合は、「単純な」競合と非常によく似た処理が行われます。 単純な競合と同様に、競合は最初に、競合マーカーからラベルを削除し、共通の祖先バージョンを削除し、外部と内部の両方の競合について競合ハンクを並べ替えることによって正規化されます。 これは再帰的に実行されるため、入れ子になった競合をいくつでも処理できます。
注意:これは、「きれいに入れ子になった」競合マーカーに対してのみ機能することに注意してください。 一致しない競合マーカーがある場合、rerereは競合の処理と競合解決の記録に失敗します。
唯一の違いは、競合IDの計算方法にあります。 内部競合の場合、sha1を計算する前に競合マーカー自体が削除されません。
たとえば、以下の競合があるとします:
<<<<<<< HEAD
1
=======
<<<<<<< HEAD
3
=======
2
>>>>>>> branch-2
>>>>>>> branch-3~
競合マーカーのラベルを取り除き、ハンクを並べ替えると、競合は以下のようになります:
<<<<<<<
1
=======
<<<<<<<
2
=======
3
>>>>>>>
>>>>>>>
最後に、競合IDは次のように計算されます。 sha1('1<NUL><<<<<<<\n3\n=======\n2\n>>>>>>><NUL>')