「gitmerge」はどのように詳細に機能しますか?


94

'git merge'の背後にある正確なアルゴリズム(またはその近く)を知りたいです。少なくともこれらのサブ質問への回答は役に立ちます:

  • gitは、特定の競合しない変更のコンテキストをどのように検出しますか?
  • gitは、これらの正確な行に競合があることをどのように見つけますか?
  • gitはどのようなものを自動マージしますか?
  • ブランチをマージするための共通ベースがない場合、gitはどのように機能しますか?
  • ブランチをマージするための共通のベースが複数ある場合、gitはどのように機能しますか?
  • 複数のブランチを一度にマージするとどうなりますか?
  • マージ戦略の違いは何ですか?

しかし、アルゴリズム全体の説明ははるかに優れています。


8
私は...あなたがこれらの答えを一冊の本を埋めることができると思います
ダニエルHilgarth

2
または、コードを読んで、「アルゴリズム全体を説明する」のと同じくらいの時間がかかることもあります
Nevik Rehnel 2013

3
@DanielHilgarthすでにそのような本がどこかにあるかどうか、私は見つけてうれしいです。参照は大歓迎です。
abyss.7 2013

5
@NevikRehnelはい、できます。しかし、誰かがこのコードの背後にある理論をすでに知っている場合、それははるかに簡単になる可能性があります。
abyss.7 2013

1.「特定の競合しない変更のコンテキスト」とは何ですか?ポイント2と3は同じですが否定されています。これらの2つの質問をマージしましょう。
CiroSantilli郝海东冠事病六四事件法轮功2014

回答:


65

3方向マージアルゴリズムの説明を探すのが最善かもしれません。高レベルの説明は次のようになります。

  1. 適切なマージベースを見つけますB-新しいバージョン(XY)の両方の祖先であるファイルのバージョン、および通常は最新のそのようなベース(ただし、さらに戻る必要がある場合がありますが、これはgitデフォルトのrecursiveマージの機能)
  2. XwithBYwithの差分を実行しBます。
  3. 2つの差分で識別された変更ブロックをウォークスルーします。両側が同じ場所に同じ変更を導入する場合は、どちらかを受け入れます。一方が変更を導入し、もう一方がその領域をそのままにしておく場合は、ファイナルで変更を導入します。両方がスポットに変更を導入したが、それらが一致しない場合は、競合を手動で解決するようにマークします。

完全なアルゴリズムの多くの詳細にこのを扱い、さらにいくつかのドキュメント(持ってhttps://github.com/git/git/blob/master/Documentation/technical/trivial-merge.txtと一緒に1のため、git help XXXページをここで、XXXはの一つであるmerge-basemerge-filemergemerge-one-fileそしておそらくいくつかの他)。それが十分に深くない場合は、常にソースコードがあります...


11

ブランチをマージするための共通のベースが複数ある場合、gitはどのように機能しますか?

この記事は非常に役に立ちました:http//codicesoftware.blogspot.com/2011/09/merge-recursive-strategy.html(ここにパート2があります)。

Recursiveは、diff3を再帰的に使用して、祖先として使用される仮想ブランチを生成します。

例えば:

(A)----(B)----(C)-----(F)
        |      |       |
        |      |   +---+
        |      |   |
        |      +-------+
        |          |   |
        |      +---+   |
        |      |       |
        +-----(D)-----(E)

次に:

git checkout E
git merge F

2つの最良の共通の祖先(他の祖先ではない共通の祖先)CD。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ます。これは簡単なマージであるため、競合は発生しません。

これらのコマンドは、新しいコミットを生成し、3つ以上の親を持ちます。

私は1つをやったmerge -X octopus、それが行く方法を確認するために、競合することなく、Gitは1.8.5に。

初期状態:

   +--B
   |
A--+--C
   |
   +--D

アクション:

git checkout B
git merge -Xoctopus C D

新しい状態:

   +--B--+
   |     |
A--+--C--+--E
   |     |
   +--D--+

予想通り、E3人の親がいます。

TODO:タコが1つのファイルの変更でどの程度正確に動作するか。再帰的な2x 2の3ウェイマージ?

ブランチをマージするための共通ベースがない場合、gitはどのように機能しますか?

@Torekは、2.9以降、マージは--allow-unrelated-histories。なしで失敗すると述べています。

私はGit1.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、単一行の追加としてベースとの3方向マージで解決されます
  • だと思うベースファイルなしの3ウェイマージは、単なる差分である2ウェイマージと呼ばれていること

1
この質問への新しいSOリンクがあるので、この回答をスキャンしました(これは非常に良いです)。最近のGitの変更により、最後のセクションが少し古くなっていることに気付きました。Gitバージョン2.9(commit e379fdf34fee96cd205be83ff4e71699bdc32b18)以降、を追加しない限り、マージベースがない場合、Gitはマージを拒否するようになりました--allow-unrelated-histories
torek 2016年

1
@Ciroが投稿した記事のフォローアップ記事は次のとおりです。blog.plasticscm.com
2012/01

最後に試したときから動作が変わっていない限り、--allow-unrelated-historiesマージするブランチ間に共通のファイルパスがない場合は省略できます。
ジェレミーリスト

小さな修正:oursマージ戦略はありますが、マージ戦略はありませんtheirsrecursive+theirs戦略は2つのブランチしか解決できません。git-scm.com/docs/git-merge#_merge_strategies
nekketsuuu

9

私も興味があります。答えはわかりませんが...

動作する複雑なシステムは、動作する単純なシステムから常に進化していることがわかります

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 / Diff3- > http://www.cis.upenn.edu/~bcpierce/papers/diff3-short.pdf

その最後のリンクは、diff3アルゴリズムを詳細に説明している論文のpdfです。これがグーグルのPDFビューアーバージョンです。長さはわずか12ページで、アルゴリズムはわずか数ページですが、完全な数学的処理です。それは少し形式的すぎるように思えるかもしれませんが、gitのマージを理解したい場合は、最初に単純なバージョンを理解する必要があります。まだ確認していませんが、のような名前の場合diff3、おそらくdiff(最長共通部分列アルゴリズムを使用)も理解する必要があります。しかし、diff3あなたがグーグルを持っているなら、そこにもっと直感的な説明があるかもしれません...


今、私はちょうど、比較実験を行ったdiff3git merge-file。彼らは、同じ3つの入力ファイル取るVERSION1 oldversion VERSION2で、マークの競合方法は同じ<<<<<<< version1=======>>>>>>> version2diff3も持っている||||||| oldversion)、彼らの共通の遺産を示します。

oldversionには空のファイルを使用し、version1version2にほぼ同じファイルを使用しversion2に1行だけ追加しました

結果:git merge-file変更された単一の行が競合として識別されました。しかし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の最長共通部分列アルゴリズム、文脈自由文法、または関係代数と同じくらい深いように見えます。根底に行きたいのならできると思いますが、ある程度の検討が必要です。



0

gitは、競合しない特定の変更のコンテキストをどのように検出しますか?
gitは、これらの正確な行に競合があることをどのように見つけますか?

マージの両側で同じ行が変更された場合、それは競合です。そうでない場合は、一方の側からの変更(存在する場合)が受け入れられます。

gitはどのようなものを自動マージしますか?

競合しない変更(上記を参照)

ブランチをマージするための共通のベースが複数ある場合、gitはどのように機能しますか?

Gitマージベースの定義によると、(最新の共通祖先)は1つだけです。

複数のブランチを一度にマージするとどうなりますか?

これはマージ戦略によって異なります(octopusおよびours/theirs戦略のみが3つ以上のブランチのマージをサポートします)。

マージ戦略の違いは何ですか?

これはgit mergeマンページで説明されています。


2
「同じ行」とはどういう意味ですか?他の2つの間に空でない新しい行を挿入してマージすると、どの行が同じですか?あるブランチのいくつかの行を削除した場合、別のブランチの「同じ」行はどれですか?
abyss.7 2013

1
テキストで答えるのは少し難しいです。Gitは[diffs](en.wikipedia.org/wiki/Diff)を使用して、2つのファイル(またはファイルの2つのリビジョン)の違いを表現します。コンテキスト(デフォルトでは3行)を比較することにより、行が追加または削除されたかどうかを検出できます。「同じ行」とは、追加と削除を念頭に置いて、コンテキストによって意味します。
Nevik Rehnel 2013

1
「同じ行」の変更は競合を示していると提案します。自動マージエンジンは本当にラインベースですか?それともハンクベースですか?共通の祖先は1つだけですか?もしそうなら、なぜgit-merge-recursive存在するのですか?
エドワードトムソン

1
@EdwardThomson:はい、解像度は行ベースです(1行だけが残るまで、ハンクを小さなハンクに分割できます)。デフォルトのマージ戦略では、最新の共通祖先が参照として使用されますが、他のものを使用する場合は他の方法もあります。そして、私はどうあるgit-merge-recursiveべきかわかりません(manページがなく、グーグルは何も生み出しません)。これに関する詳細は、git mergeおよびgit merge-basemanページにあります。
Nevik Rehnel 2013

1
git-mergemanページとgit-merge-baseは、複数の共通の祖先と再帰的なマージを話し合うことを指摘manページ。そのような議論なしでは、あなたの答えは不完全だと思います。
エドワードトムソン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.