「git merge」の背後にある正確なアルゴリズム(またはその近く)を知りたいです。少なくともこれらのサブ質問に対する回答は役に立ちます:
しかし、アルゴリズム全体の説明ははるかに優れています。
3ウェイマージアルゴリズムの説明を探すのが最善かもしれません。高レベルの説明は次のようになります。
B
-両方の新しいバージョン(X
とY
)の祖先であるファイルのバージョン、および通常は最新のベースを見つけます。 (さらに戻る必要がある場合もありますが、これはgit
s default recursive
mergeの機能の1つです)X
とB
およびY
とB
の差分を実行します。完全なアルゴリズムはこれをより詳細に扱っており、ドキュメント(/usr/share/doc/git-doc/technical/trivial-merge.txt
とともに、git help XXX
ページ、XXXはmerge-base
、merge-file
、merge
、merge-one-file
および場合によっては他のいくつか)。それが十分に深くなければ、常にソースコードがあります...
ブランチをマージするための複数の共通ベースがある場合、gitはどのように動作しますか?
この記事はとても役に立ちました: http://codicesoftware.blogspot.com/2011/09/merge-recursive-strategy.html (こちらは part 2 ))。
Recursiveはdiff3を再帰的に使用して、祖先として使用される仮想ブランチを生成します。
例えば。:
(A)----(B)----(C)-----(F)
| | |
| | +---+
| | |
| +-------+
| | |
| +---+ |
| | |
+-----(D)-----(E)
次に:
git checkout E
git merge F
C
とD
の2つの最良の共通の祖先(他の祖先ではない共通の祖先)があります。 Gitはそれらを新しい仮想ブランチV
にマージし、V
をベースとして使用します。
(A)----(B)----(C)--------(F)
| | |
| | +---+
| | |
| +----------+
| | | |
| +--(V) | |
| | | |
| +---+ | |
| | | |
| +------+ |
| | |
+-----(D)--------(E)
より良い共通の祖先があれば、GitはV
を次の祖先とマージするだけで続行すると思います。
この記事では、仮想ブランチの生成中にマージの競合が発生した場合、Gitは競合マーカーをそのままにして、続行します。
複数のブランチを一度にマージするとどうなりますか?
@Nevik Rehnelが説明したように、それは戦略に依存し、man git-merge
MERGE STRATEGIES
セクションでよく説明されています。
octopus
とours
/theirs
のみが、複数のブランチを一度にマージすることをサポートします。たとえば、recursive
はサポートしません。
octopus
は競合がある場合はマージを拒否し、ours
は競合が発生しないように取るに足らないマージです。
これらのコマンドは、新しいコミットを生成し、2つ以上の親を持ちます。
Git 1.8.5で競合なしにmerge -X octopus
を1つ実行し、その結果を確認しました。
初期状態:
+--B
|
A--+--C
|
+--D
アクション:
git checkout B
git merge -Xoctopus C D
新しい状態:
+--B--+
| |
A--+--C--+--E
| |
+--D--+
予想どおり、E
には3つの親があります。
TODO:単一のファイル変更でタコが正確に動作する方法。再帰的な2 x 2の3方向マージ?
ブランチをマージするための共通のベースがない場合、gitはどのように動作しますか?
@Torekは、2.9以降、--allow-unrelated-histories
なしでマージが失敗することに言及しています。
Git 1.8.5で経験的に試してみました:
git init
printf 'a\nc\n' > a
git add .
git commit -m a
git checkout --Orphan b
printf 'a\nb\nc\n' > a
git add .
git commit -m b
git merge master
a
の内容:
a
<<<<<<< ours
b
=======
>>>>>>> theirs
c
次に:
git checkout --conflict=diff3 -- .
a
の内容:
<<<<<<< ours
a
b
c
||||||| base
=======
a
c
>>>>>>> theirs
解釈:
a\nc\n
を1行追加する3方向のマージで解決されます。私も興味があります。答えはわかりませんが、...
動作する複雑なシステムは、動作する単純なシステムから進化したものであることが常に判明しています。
Gitのマージは非常に洗練されており、理解するのは非常に難しいと思いますが、これにアプローチする1つの方法は、その前駆体からであり、懸念の中心に焦点を当てることです。つまり、共通の祖先を持たない2つのファイルが与えられた場合、git mergeはどのようにそれらをマージするのか、競合はどこにあるのでしょうか?
いくつかの前駆体を見つけてみましょう。 git help merge-file
から:
git merge-file is designed to be a minimal clone of RCS merge; that is,
it implements all of RCS merge's functionality which is needed by
git(1).
ウィキペディアから: http://en.wikipedia.org/wiki/Git_%28software%29 -> http://en.wikipedia.org/wiki/Three-way_merge#Three -way_merge -> http://en.wikipedia.org/wiki/Diff -> http://www.cis.upenn.edu/~bcpierce/papers/ diff3-short.pdf
最後のリンクは、diff3
アルゴリズムを詳細に説明している論文のPDFです。 google pdf-viewer version です。長さはわずか12ページで、アルゴリズムは数ページしかないが、完全な数学的処理です。それは少し形式的すぎるように思えるかもしれませんが、gitのマージを理解したい場合は、最初に単純なバージョンを理解する必要があります。私はまだチェックしていませんが、diff3
のような名前では、おそらくdiff( longest common subsequenceを使用する必要があります) アルゴリズム)。ただし、Googleをお持ちの場合は、diff3
のより直感的な説明があります。
さて、diff3
とgit merge-file
を比較する実験を行ったところです。同じ3つの入力ファイルversion1 oldversion version2を使用し、<<<<<<< version1
、=======
、>>>>>>> version2
(diff3
には||||||| oldversion
)もあり、共通の遺産を示しています。
oldversionには空のファイルを使用し、version1およびversion2version2に1行だけ追加します。
結果:git merge-file
は、1つの変更された行を競合として識別しました。ただし、diff3
は2つのファイル全体を競合として扱いました。したがって、diff3のように洗練されているため、この最も単純な場合でも、gitのマージはさらに洗練されています。
これが実際の結果です(テキストには@twalbergの回答を使用しました)。必要なオプションに注意してください(それぞれのマンページを参照)。
$ git merge-file -p fun1.txt fun0.txt fun2.txt
You might be best off looking for a description of a 3-way merge algorithm. A
high-level description would go something like this:
Find a suitable merge base B - a version of the file that is an ancestor of
both of the new versions (X and Y), and usually the most recent such base
(although there are cases where it will have to go back further, which is one
of the features of gits default recursive merge) Perform diffs of X with B and
Y with B. Walk through the change blocks identified in the two diffs. If both
sides introduce the same change in the same spot, accept either one; if one
introduces a change and the other leaves that region alone, introduce the
change in the final; if both introduce changes in a spot, but they don't match,
mark a conflict to be resolved manually.
<<<<<<< fun1.txt
=======
THIS IS A BIT DIFFERENT
>>>>>>> fun2.txt
The full algorithm deals with this in a lot more detail, and even has some
documentation (/usr/share/doc/git-doc/technical/trivial-merge.txt for one,
along with the git help XXX pages, where XXX is one of merge-base, merge-file,
merge, merge-one-file and possibly a few others). If that's not deep enough,
there's always source code...
$ diff3 -m fun1.txt fun0.txt fun2.txt
<<<<<<< fun1.txt
You might be best off looking for a description of a 3-way merge algorithm. A
high-level description would go something like this:
Find a suitable merge base B - a version of the file that is an ancestor of
both of the new versions (X and Y), and usually the most recent such base
(although there are cases where it will have to go back further, which is one
of the features of gits default recursive merge) Perform diffs of X with B and
Y with B. Walk through the change blocks identified in the two diffs. If both
sides introduce the same change in the same spot, accept either one; if one
introduces a change and the other leaves that region alone, introduce the
change in the final; if both introduce changes in a spot, but they don't match,
mark a conflict to be resolved manually.
The full algorithm deals with this in a lot more detail, and even has some
documentation (/usr/share/doc/git-doc/technical/trivial-merge.txt for one,
along with the git help XXX pages, where XXX is one of merge-base, merge-file,
merge, merge-one-file and possibly a few others). If that's not deep enough,
there's always source code...
||||||| fun0.txt
=======
You might be best off looking for a description of a 3-way merge algorithm. A
high-level description would go something like this:
Find a suitable merge base B - a version of the file that is an ancestor of
both of the new versions (X and Y), and usually the most recent such base
(although there are cases where it will have to go back further, which is one
of the features of gits default recursive merge) Perform diffs of X with B and
Y with B. Walk through the change blocks identified in the two diffs. If both
sides introduce the same change in the same spot, accept either one; if one
introduces a change and the other leaves that region alone, introduce the
change in the final; if both introduce changes in a spot, but they don't match,
mark a conflict to be resolved manually.
THIS IS A BIT DIFFERENT
The full algorithm deals with this in a lot more detail, and even has some
documentation (/usr/share/doc/git-doc/technical/trivial-merge.txt for one,
along with the git help XXX pages, where XXX is one of merge-base, merge-file,
merge, merge-one-file and possibly a few others). If that's not deep enough,
there's always source code...
>>>>>>> fun2.txt
これに本当に興味があるなら、それはちょっとしたうさぎの穴です。私には、正規表現、最長共通部分列diff、文脈自由文法、または関係代数のアルゴリズムと同じくらい深いようです。あなたがそれの一番下に到達したい場合、私はあなたができると思うが、それはいくつかの断固とした研究が必要になります。
これが元の実装です
http://git.kaarsemaker.net/git/blob/857f26d2f41e16170e48076758d974820af685ff/git-merge-recursive.py
基本的に、2つのコミットの共通の祖先のリストを作成し、それらを再帰的にマージします。それらを高速転送するか、ファイルの3者間マージの基礎に使用される仮想コミットを作成します。
Gitは、競合しない特定の変更のコンテキストをどのように検出しますか?
これらの正確な行に矛盾があることをgitはどのように検出しますか?
マージの両側で同じ行が変更された場合、それは競合です。そうでない場合、一方からの変更(存在する場合)が受け入れられます。
Gitが自動マージするのはどれですか?
競合しない変更(上記を参照)
ブランチをマージするための複数の共通ベースがある場合、gitはどのように動作しますか?
Git merge-base の定義により、1つのみ(最新の共通の祖先)が存在します。
一度に複数のブランチをマージするとどうなりますか?
それはマージ戦略に依存します(octopus
およびours
/theirs
戦略のみが3つ以上のブランチのマージをサポートします)。
マージ戦略の違いは何ですか?
これは git merge
manpage 。