web-dev-qa-db-ja.com

Gitはマージの問題をどのように解決しますか?

SVNはブランチを非常に安価にすることでブランチをはるかに簡単にしましたが、マージはSVNの本当の問題であり、Gitが解決すると思われます。

Gitはこれをどのように実現しますか?

(免責事項:Gitについて私が知っていることはすべて、Linusの講義に基づいています-合計git noobはこちら)

56
Assaf Lavie

Gitはマージでの競合を防止しませんが、親の祖先を共有していない場合でも履歴を調整できます。
(through graftsファイル(.git/info/grafts これは、コミットとそれに続く親の1行に1つのリストであり、「調整」の目的で変更できます。)
とても強力です。

しかし、「マージがどのように検討されてきたか」を実際に垣間見るために、あなたは Linus自身に目を向けることから始めることができます であり、この問題は「アルゴリズム」についてはそれほど重要ではないことを理解します。

Linus:私個人的に、非常に再現性が高く、賢くないものが欲しい。私が理解しているものorは、それを実行できないことを教えてくれます。
そして率直に言って、単一ファイルの履歴をマージするなし他のすべてのファイルの履歴を考慮に入れると、「大変」になります。

マージの重要な部分は、それがどのようにして競合を処理するかではありません(とにかく興味深い場合は、人間が検証する必要があります)が、履歴を正しく統合する必要があります。将来のマージのための新しい強固な基盤があります。

言い換えれば、重要な部分はtrivialの部分です。つまり、親の名前と、それらの関係の追跡です。衝突ではありません。

また、SCMの99%の人々は、その解決策はコンテンツのマージについてより賢明であると考えているようです。それは完全に要点を逃しています。


したがって、Wincent Colaiutaは次のように付け加えます(私の強調)

派手なメタデータ、名前の変更の追跡などは必要ありません。
保存する必要があるのは、変更前後のツリーの状態だけです。

どのファイルの名前が変更されましたか?どれがコピーされましたか?どれが削除されましたか?追加された行は何ですか?どれが削除されましたか?内部で変更された行はどれですか?あるファイルから別のファイルにコピーされたテキストのスラブはどれですか?
これらの質問について気にする必要はありません。また、答えを出すために特別な追跡データを保持する必要もありません:すべての変更ツリー(追加、削除、名前変更、編集など)は、ツリーの2つの状態の間のデルタに暗黙的にエンコードされます;あなただけtrackcontent

絶対にすべてを推測できます(そして推測する必要があります)

Gitはファイルではなくコンテンツについて考えるので、型を壊します。
名前の変更は追跡されず、コンテンツが追跡されます。そして、ツリー全体のレベルでそうします。
これは、ほとんどのバージョン管理システムからの根本的な逸脱です。
ファイルごとの履歴を保存する必要はありません。代わりに、ツリーレベルで履歴を保存します。
差分を実行すると、2つのファイルではなく2つのツリーを比較します。

他の根本的にスマートな設計決定は、Gitがどのようにマージするかです。
マージアルゴリズムはスマートですが、あまりスマートにしようとはしていません。明確な決定は自動的に行われますが、疑わしい場合はユーザーが決定する必要があります。
これは本来あるべき姿です。あなたはそれらの決定をあなたに代わって行うマシンを望んでいません。あなたはそれを望みません。
これが、マージに対するGitアプローチの基本的な洞察です。他のすべてのバージョン管理システムはよりスマートになりつつありますが、Gitは喜んで「愚かなコンテンツマネージャー」と自己紹介されており、その方が適しています。

76
VonC

現在、現在のブランチ( 'ours')のバージョン、マージされたブランチ( 'theirs')のバージョンを考慮した3ウェイマージアルゴリズム(おそらく、名前の変更の検出やより複雑な履歴の処理などの機能強化により)に同意しています)、およびマージされたブランチの共通の祖先のバージョン(「祖先」)は、(実用的な観点から)マージを解決する最良の方法です。ほとんどの場合、ほとんどのコンテンツツリーレベルのマージ(取得するファイルのバージョン)で十分です。コンテンツの競合に対処する必要はほとんどなく、diff3アルゴリズムで十分です。

3ウェイマージを使用するには、マージされたブランチの共通の祖先(マージベースと呼ばれる)を知る必要があります。このためには、それらのブランチ間の full 履歴を知る必要があります。 (現在の)バージョン1.5より前のSubversionに欠けていたもの(SVKやsvnmergeなどのサードパーティツールなし)は、merge trackingでした。マージコミットでは、マージで使用された親(どのコミット)か。この情報がないと、マージが繰り返される場合に共通の祖先を正しく計算できません。

次の図を考慮してください。

---.---a---.---b---d---.---1
        \        /
         \-.---c/------.---2

(これはおそらく壊れてしまいます...ここにASCIIアート図を描く機能があるといいですね)
コミット 'b'と 'c'をマージする(コミット 'd'を作成する)とき、共通の祖先は分岐点であるコミット 'a'でした。しかし、コミット「1」と「2」をマージしたい場合、共通の祖先はコミット「c」になります。マージ情報を保存しないと、コミット 'a'であると誤って判断する必要があります。

Subversion(バージョン1.5より前)および以前のCVSでは、共通の祖先を自分で計算し、マージを行うときに祖先に関する情報を手動で指定する必要があったため、マージが困難になりました。

Gitは、コミットのすべての親(マージコミットの場合は複数の親)に関する情報をコミットオブジェクトに格納します。このように、GitはリビジョンのDAG(直接非循環グラフ)を保存し、コミット間の関係を保存および記憶していると言えます。


(Subversionが下記の問題にどのように対処するのかわかりません)

さらに、Gitでのマージでは、さらに2つの複雑な問題に対処できます。 file renames (片方がファイルの名前を変更した場合と、もう一方が変更しなかった場合。名前を変更したいのですが、正しいファイルに適用された変更)および criss-cross merges (複数の共通の祖先がある場合は、より複雑な履歴)。

  • マージ中のファイルの名前変更は、ヒューリスティックな類似性スコアに基づいて管理されます(ファイルの内容の類似性とパス名の類似性の両方が考慮されます)検出名の変更。 Gitは、マージされたブランチ(および祖先)で互いに対応するファイルを検出します。実際には、実際のケースでは非常にうまく機能します。
  • Criss-cross merges 、see definition at revctrl.org wiki 、(and present of multiple merge bases 再帰的マージ戦略を使用して管理され、単一の仮想共通祖先を生成します。
18
Jakub Narębski

上記の答えはすべて正しいですが、私にとってはgitの簡単なマージの中心点を見逃していると思います。 SVNマージでは、何をマージしたかを追跡し、覚えておく必要があります。これは巨大なPITAです。彼らのドキュメントから:

svn merge -r 23:30 file:///tmp/repos/trunk/vendors

今ではそれはキラーではありませんが、23-30と23-30のどちらが包括的か、またはそれらのコミットの一部をすでにマージしているかどうかを忘れた場合は、回避できません。コミットの繰り返しまたは欠落。神はあなたが枝を分岐させるならあなたを助けます。

Gitを使用すると、gitマージだけですべての処理がシームレスに行われます。2つのコミットを厳選したり、幻想的なgitランドの機能をいくつも実行したりした場合でも同様です。

9
jdwyah

私の知る限り、マージアルゴリズムは他のバージョン管理システムよりも賢くはありません。ただし、gitの分散性により、一元化されたマージ作業は必要ありません。すべての開発者は、他の開発者からの小さな変更をいつでもリベースまたはツリーにマージできるため、発生する競合は少なくなる傾向があります。

5
hillu