どのようにおよび/またはなぜGitでのマージはSVNよりも優れていますか?


400

分散バージョン管理システムが優れている主な理由の1つは、SVNのような従来のツールよりもはるかに優れていると聞いたことがあります。これは、実際には2つのシステムの動作方法に固有の違いが原因ですか、それともGit / Mercurialなどの特定の DVCS実装には、SVNよりも巧妙なマージアルゴリズムしかありませんか?


ここで素晴らしい答えを読んでも、私はまだ完全な答えを得ることができませんでした。転載- stackoverflow.com/questions/6172037/...
ripper234


モデルによって異なります。単一の開発ブランチでpush / merge / pull / pushを実行した場合にgitが実行できるように、誤って2ウェイマージを呼び出すことがないため、より単純なケースでは、svnの方がよくあります。参照:svnvsgit.com
Erik Aronesty

回答:


556

なぜSubversionよりもDVCSの方がマージが優れているという主張は、Subversionでブランチとマージがどのように機能していたかということにほとんど基づいていました。1.5.0より前のSubversion は、ブランチがマージされたときの情報を格納していなかったため、マージする必要がある場合は、マージする必要があるリビジョンの範囲を指定する必要がありました。

なぜSubversionのマージはなかった吸いますか

この例を考えてみましょう:

      1   2   4     6     8
trunk o-->o-->o---->o---->o
       \
        \   3     5     7
b1       +->o---->o---->o

b1の変更をトランクにマージする場合は、トランクがチェックアウトされているフォルダー上で次のコマンドを発行します。

svn merge -r 2:7 {link to branch b1}

…からの変更をb1ローカルの作業ディレクトリにマージしようとします。そして、競合を解決して結果をテストした後、変更をコミットします。リビジョンツリーをコミットすると、次のようになります。

      1   2   4     6     8   9
trunk o-->o-->o---->o---->o-->o      "the merge commit is at r9"
       \
        \   3     5     7
b1       +->o---->o---->o

ただし、リビジョンの範囲を指定するこの方法は、バージョンツリーが大きくなるとすぐに手に負えなくなります。サブバージョンには、いつどのリビジョンがマージされるかに関するメタデータがないためです。後で何が起こるかについて熟考する:

           12        14
trunk  …-->o-------->o
                                     "Okay, so when did we merge last time?"
              13        15
b1     …----->o-------->o

これは、Subversionが持っているリポジトリ設計による主な問題です。ブランチを作成するには、トランクのコピーを格納するリポジトリに新しい仮想ディレクトリを作成する必要がありますが、いつ、何に関する情報を保存しません。マージされた状態に戻ります。これにより、厄介なマージの競合が発生することがあります。さらに悪いことに、Subversionはデフォルトで双方向のマージを使用していたため、2つのブランチヘッドが共通の祖先と比較されない場合の自動マージにはいくつかの不自由な制限があります。

このSubversionを軽減するために、ブランチとマージのメタデータが保存されるようになりました。それですべての問題は解決しますか?

ちなみに、Subversionはまだひどい…

一元化されたシステムでは、Subversionのように、仮想ディレクトリは最低です。どうして?誰もがそれらを表示するためのアクセス権を持っているからです…ごみの実験的なものでさえです。実験したいが、全員とその叔母の実験を見たくない場合は、分岐が適しています。これは深刻な認知ノイズです。追加するブランチが多いほど、より多くのがらくたが表示されます。

リポジトリ内に公開ブランチが多いほど、さまざまなブランチすべてを追跡することが難しくなります。したがって、問題はブランチがまだ開発中であるか、集中管理されたバージョン管理システムではわかりにくいかどうかです。

ほとんどの場合、私が見た限りでは、組織はデフォルトで1つの大きなブランチを使用することになります。これは残念なことです。それは、テストやリリースのバージョンを追跡することが困難になるためです。

では、Git、Mercurial、BazaarなどのDVCSが、分岐とマージでSubversionよりも優れているのはなぜですか?

非常に単純な理由があります。分岐はファーストクラスのコンセプトです。設計上、仮想ディレクトリなく、ブランチはDVCSのハードオブジェクトであり、リポジトリの同期(つまり、pushpull)を単純に処理するためには、ハードオブジェクトである必要があります。

DVCSで作業するときに最初に行うことは、リポジトリ(git clone、hg clone、bzr branch)のクローンを作成することです。クローン作成は、バージョン管理でブランチを作成することと概念的に同じです。一部の人はこれをフォークまたはブランチングと呼びます(後者は同じ場所に配置されたブランチを参照するために使用されることもあります)が、それは同じことです。すべてのユーザーが独自のリポジトリを実行します。つまり、ユーザーごとのブランチが行われます。

バージョン構造はツリーではなく、グラフです。より具体的には、有向非巡回グラフ(DAG、つまりサイクルのないグラフを意味します)。各コミットに1つ以上の親参照(コミットの基になっているもの)があることを除いて、DAGの詳細を詳しく知る必要はありません。そのため、次のグラフでは、これによりリビジョン間の矢印が逆に表示されます。

マージの非常に簡単な例はこれです。と呼ばれる中央リポジトリoriginと、ユーザーのアリスが自分のマシンにリポジトリを複製するとします。

         a…   b…   c…
origin   o<---o<---o
                   ^master
         |
         | clone
         v

         a…   b…   c…
alice    o<---o<---o
                   ^master
                   ^origin/master

クローン中に発生することは、すべてのリビジョンがそれらとまったく同じように(一意に識別可能なハッシュIDによって検証される)Aliceにコピーされ、オリジンのブランチがどこにあるかをマークすることです。

次に、アリスは自分のリポジトリで作業し、自分のリポジトリにコミットして、変更をプッシュすることを決定します。

         a…   b…   c…
origin   o<---o<---o
                   ^ master

              "what'll happen after a push?"


         a…   b…   c…   d…   e…
alice    o<---o<---o<---o<---o
                             ^master
                   ^origin/master

解決策はかなり単純originです。リポジトリが行う必要があるのは、すべての新しいリビジョンを取り込み、そのブランチを最新のリビジョンに移動することです(gitは「早送り」と呼びます)。

         a…   b…   c…   d…   e…
origin   o<---o<---o<---o<---o
                             ^ master

         a…   b…   c…   d…   e…
alice    o<---o<---o<---o<---o
                             ^master
                             ^origin/master

上で説明した使用例では、何もマージする必要はありません。そのため、3者間マージアルゴリズムはすべてのバージョン管理システム間でほぼ同じであるため、問題はマージアルゴリズムにはありません。問題は何よりも構造についてです。

それでは、実際にマージを行う例を教えてください。

確かに上記の例は非常に単純なユースケースなので、より一般的なものではありますが、より多くのねじれたものを実行してみましょう。origin3つのリビジョンから始まったことを覚えていますか?まあ、それらをやった人は彼をBobと呼んで、自分で作業していて、自分のリポジトリでコミットしています:

         a…   b…   c…   f…
bob      o<---o<---o<---o
                        ^ master
                   ^ origin/master

                   "can Bob push his changes?" 

         a…   b…   c…   d…   e…
origin   o<---o<---o<---o<---o
                             ^ master

これで、ボブは自分の変更をoriginリポジトリに直接プッシュできなくなりました。システムがこれを検出する方法は、Bobのリビジョンがから直接下降しているかどうかをチェックすることですorigin。この場合はそうではありません。プッシュしようとすると、システムに「うーん……ボブをやらせられないのではないか」と似たような結果になります

(gitののとボブは、インプルして、変更をマージする必要がありますのでpull、またはHGのpullmergeまたはのbzrの; merge)。これは2段階のプロセスです。まず、ボブは新しいリビジョンを取得する必要がありoriginます。これにより、リポジトリからそのままの状態でコピーされます。グラフが発散していることがわかります。

                        v master
         a…   b…   c…   f…
bob      o<---o<---o<---o
                   ^
                   |    d…   e…
                   +----o<---o
                             ^ origin/master

         a…   b…   c…   d…   e…
origin   o<---o<---o<---o<---o
                             ^ master

プルプロセスの2番目のステップは、分岐するヒントをマージし、結果をコミットすることです。

                                 v master
         a…   b…   c…   f…       1…
bob      o<---o<---o<---o<-------o
                   ^             |
                   |    d…   e…  |
                   +----o<---o<--+
                             ^ origin/master

うまくいけば、マージが競合に遭遇しないことを期待します(それらが予想される場合は、fetchとでgitで2つの手順を手動で実行できますmerge)。後で行う必要があるのは、これらの変更を再びにプッシュoriginすることです。マージコミットはoriginリポジトリ内の最新の直接の子孫なので、早送りマージになります。

                                 v origin/master
                                 v master
         a…   b…   c…   f…       1…
bob      o<---o<---o<---o<-------o
                   ^             |
                   |    d…   e…  |
                   +----o<---o<--+

                                 v master
         a…   b…   c…   f…       1…
origin   o<---o<---o<---o<-------o
                   ^             |
                   |    d…   e…  |
                   +----o<---o<--+

gitとhgをマージする別のオプションとしてrebaseがあります。これは、Bobの変更を最新の変更の後に移動します。この回答をこれ以上冗長にしたくないので、代わりにgitmercurial、またはbazaarのドキュメントを読んでもらいます。

読者のための練習として、関係する別のユーザーとどのようにうまくいくかを引き出してみてください。上記の例と同様にボブを使用して行います。リポジトリ間のマージは、すべてのリビジョン/コミットが一意に識別できるため、思ったより簡単です。

また、各開発者間でパッチを送信するという問題もあります。これはSubversionで大きな問題であり、git、hg、bzrで一意に識別可能なリビジョンによって軽減されています。誰かが自分の変更をマージして(つまり、マージコミットを作成して)、中央リポジトリにプッシュするか、パッチを送信することで、チームの他の全員が使用できるように送信すると、すでに発生しているため、マージについて心配する必要はありません。 。マーティン・ファウラーは、このような無差別な統合の仕組みを呼び出しています。

Subversionとは構造が異なるため、DAGを採用することで、システムだけでなくユーザーにとっても簡単に分岐とマージを行うことができます。


6
私はあなたのbranchs == noise引数に同意しません。主要な開発者は大きな機能に使用するブランチをユーザーに指示する必要があるため、多くのブランチは人々を混乱させません...したがって、2人の開発者がブランチXで「空飛ぶ恐竜」を追加し、3人がYで「あなたを投げさせます」人の車」
ボーイ氏

16
ジョン:はい、ブランチの数が少ない場合、ノイズはほとんどなく、管理が容易です。しかし、Subversionまたは明確なケースで50以上のブランチやタグなどを目撃した後、それらのほとんどがアクティブであるかどうかを確認できない場合に戻ってきます。ツール以外のユーザビリティの問題; リポジトリにゴミが散らばっているのはなぜですか?少なくともp4では(ユーザーの「ワークスペース」は基本的にユーザーごとのブランチであるため)、gitまたはhgを使用すると、上流にプッシュするまで、行った変更について誰にも知らせないようにすることができます。これは安全です。変更が他の人に関連する場合は警戒してください。
スポーク

24
@Spoike、「実験的なブランチが多すぎてノイズの引数です」とは言えません。「Users」フォルダがあり、そこにはすべてのユーザーが独自のフォルダを持っています。そこで、彼は好きなだけブランチできます。Subversionのブランチは安価で、他のユーザーのフォルダーを無視した場合(どうしてもそれらを気にする必要がある場合)、ノイズは表示されません。しかし、私にとっては、SVNでのマージで問題が発生することはありません(私はそれを頻繁に行うので、小さくはありません)プロジェクト)だから、多分私が何かを間違った操作を行う;)それでもGitリポジトリとMercurialののマージが優れている、あなたはうまくそれを指摘した。。
ジョン・スミザーズ

11
svnでは、非アクティブなブランチを削除するのは簡単です。削除するだけです。人々が未使用のブランチを削除しないという事実は、雑然とすることはハウスキーピングの問題にすぎません。同様に、Gitの多くの一時的なブランチでも簡単に終了できます。私の職場では、標準のディレクトリに加えて「temp-branches」トップレベルディレクトリを使用しています。「公式」のコード行が保持されているブランチディレクトリを散らかす代わりに、個人のブランチと実験的なブランチがそこにあります(機能ブランチを使用)。
Ken Liu

10
これは、つまり、v1.5のSubversionはgitと同様に少なくともマージできることを意味しますか?
サム

29

歴史的に、Subversionはマージ情報を保存していなかったため、ストレート双方向のマージしか実行できませんでした。これには、一連の変更を取得してツリーに適用することが含まれます。マージ情報があっても、これは依然として最も一般的に使用されるマージ戦略です。

Gitはデフォルトで3ウェイマージアルゴリズムを使用します。これには、マージされるヘッドの共通の祖先を見つけ、マージの両側に存在する知識を利用することが含まれます。これにより、Gitは競合を回避する際によりインテリジェントになります。

Gitには、洗練された名前変更検索コードもいくつかあり、これも役立ちます。変更セットや追跡情報保存されません -コミットごとのファイルの状態を保存し、ヒューリスティックを使用して必要に応じて名前変更とコードの移動を特定します(ディスク上のストレージはこれよりも複雑ですが、インターフェイスロジック層に表示され、追跡は行われません)。


4
svnにマージの競合があるが、gitにはない例はありますか?
Gqqnbig 2017

17

簡単に言うと、マージの実装はSVNよりもGitの方が優れています。。1.5より前のバージョンでは、SVNはマージアクションを記録しなかったため、SVNが記録しなかった情報を提供する必要があるユーザーの助けなしに、将来のマージを実行することはできませんでした。1.5でより良くなり、実際、SVNストレージモデルはGitのDAGよりもわずかに優れています。しかし、SVNはマージ情報をかなり複雑な形式で保存しました。これにより、マージはGitよりも大幅に時間がかかります。実行時間には300のファクターが観察されました。

また、SVNは移動されたファイルのマージを支援するために名前変更を追跡すると主張しています。しかし、実際には、それらはまだそれらをコピーおよび個別の削除アクションとして保存し、マージアルゴリズムは、変更/名前変更の状況でそれらにつまずきます。マージされます。このような状況でも、疑似マージの競合が発生し、ディレクトリの名前を変更した場合、変更内容が失われることもあります。(SVNの人々は、変更がまだ履歴にあることを指摘する傾向がありますが、それらが表示されるはずのマージ結果に含まれていない場合はあまり役に立ちません。

一方、Gitは名前の変更さえ追跡しませんが、(マージ時に)名前変更後にそれらを把握し、非常に魔法のように実行します。

SVNマージ表現にも問題があります。1.5 / 1.6では、好きなだけ頻繁にトランクからブランチに自動的にマージできましたが、反対方向のマージはアナウンスする必要があり(--reintegrate)、ブランチを使用できない状態のままにしました。ずっと後に、これは実際にはそうではなく、a)自動的に計算--reintegrate できること、およびb)双方向で繰り返しマージが可能であることを発見しました。

しかし、これらすべて(IMHOが彼らのしていることに対する理解の欠如を示しています)の後で、私は(OK、私は)SVNを重要でない分岐シナリオで使用することに非常に注意を払い、Gitが考えていることを確認しようとするのが理想的ですマージ結果。

SVNでブランチを強制的にグローバルに可視化するなど、回答で行われた他のポイントは、マージ機能には関係ありません(ただし、使いやすさは)。また、「Gitは変更を保存しますが、SVNは(何か違うもの)を保存します」はほとんど問題になっています。Gitは概念的には、各コミットを個別のツリー(tarファイルなど)として格納し、かなりのヒューリスティックを使用して効率的に格納します。2つのコミット間の変更の計算は、ストレージの実装とは別です。真実は、Gitが履歴DAGをSVNがmergeinfoを実行するよりもはるかに簡単な形式で保存することです。後者を理解しようとする人は誰でも私が何を意味するか知っています。

一言で言えば、GitはSVNよりもはるかに単純なデータモデルを使用してリビジョンを保存するため、表現に対応しようとするのではなく、実際のマージアルゴリズムに多くのエネルギーを費やすことができます。


11

他の回答で言及されていないことの1つは、DVCSの大きな利点であり、変更をプッシュする前にローカルでコミットできることです。SVNでは、チェックインしたい変更があり、その間に誰かがすでに同じブランチでコミットを行っていたため、コミットするsvn update前に実行する必要がありました。これは、私の変更と他の人からの変更が混在していることを意味します。元に戻すコミットがないため、(git resetまたはのようにhg update -C)マージを中止する方法はありません。マージが重要でない場合、これは、マージ結果をクリーンアップするまで、機能の作業を続行できないことを意味します。

しかし、多分それは、別々のブランチを使用するのが難しい人にとってはメリットにすぎないでしょう(私が正しく覚えていれば、SVNを使用していた会社で開発に使用されたブランチは1つしかありませんでした)。


10

編集:これは主に質問のこの部分に対処しています:これは
実際には2つのシステムの動作方法の固有の違いによるものですか、それともGit / Mercurialのような特定のDVCS実装にはSVNよりも巧妙なマージアルゴリズムがあるだけですか?
TL; DR-これらの特定のツールには、より優れたアルゴリズムがあります。分散されていることはワークフローにいくつかの利点がありますが、マージの利点とは関係ありません。
編集を終了

受け入れた回答を読みます。それはただの間違いです。

SVNのマージは面倒な場合があり、扱いにくい場合もあります。ただし、実際の動作を1分間無視してください。Gitが保持または導出できる情報には、SVNも保持または導出できない情報はありません。さらに重要なことに、バージョン管理システムの個別の(場合によっては部分的な)コピーを保持することで、より実際的な情報が提供される理由はありません。2つの構造は完全に同等です。

あなたが「何か賢いこと」をしたいとしましょう。Gitは「得意」です。そして、あなたはSVNにチェックインされています。

SVNを同等のGitフォームに変換し、Gitで実行してから、おそらく複数のコミットを使用して、いくつかの追加のブランチで結果を確認します。SVNの問題をGitの問題に変える自動化された方法を想像できる場合、Gitには基本的な利点はありません。

結局のところ、どのバージョンコントロールシステムでも

1. Generate a set of objects at a given branch/revision.
2. Provide the difference between a parent child branch/revisions.

さらに、マージするために知っておくことも役立ちます(または重要です)

3. The set of changes have been merged into a given branch/revision.

Mercurial、Git、およびSubversion(現在はネイティブで、以前はsvnmerge.pyを使用していた)はすべて、3つの情報すべてを提供できます。DVCで根本的に優れたものを示すために、Git / Mercurial / DVCで利用できる4番目の情報を指摘してください。SVN/集中型VCでは利用できません。

それは彼らがより良いツールではないと言っているのではありません!


1
ええ、私は見出しではなく詳細で質問に答えました。svnとgitは同じ情報にアクセスできます(実際には、通常、svnはもっと多くの情報を持っています)ので、svn git 行うことは何でもできます。しかし、彼らは異なる設計上の決定をしたため、実際にはそうではありません。DVC /集中型の証明は、gitを集中型VCとして実行でき(おそらくいくつかのルールが課せられている)、svn distributedを実行できます(ただし、それは完全に不快です)。しかし、これはほとんどの人にとって学問的すぎる-gitとhgはsvnよりも分岐とマージをうまく行います。それがツールを選ぶときに本当に重要なことです:-)。
Peter

5
バージョン1.5までSubversion 必要な情報をすべて保存していませんでした。ポスト1.5 SVNの場合でも、格納される情報は異なります。Gitはマージコミットのすべての親を格納し、Subversionはブランチにすでにマージされたリビジョンを格納します。
JakubNarębski12年

4
svnリポジトリに再実装するのが難しいツールはgit merge-baseです。gitでは、「ブランチaとbがリビジョンxで分割されている」と言うことができます。しかし、svnは「ファイルがfooからbarにコピーされました」を格納するため、ヒューリスティックを使用して、プロジェクト内でファイルをコピーするのではなく、barへのコピーが新しいブランチを作成していたことを突き止める必要があります。コツは、svnのリビジョンがリビジョン番号ベースパスによって定義されることです。ほとんどの場合「トランク」であると想定することは可能ですが、実際にブランチがある場合は噛み付きます。
ダグラス

2
Re:「Gitが保持している、またはsvnが保持できない、または誘導できるという情報はありません。」-何がマージされたかをSVNが覚えていないことがわかりました。トランクからブランチに作業をプルして前後に移動したい場合は、マージが困難になる可能性があります。Gitでは、リビジョングラフの各ノードは、それがどこから来たかを認識しています。最大2つの親といくつかのローカル変更があります。私はGitがSVN以上のものをマージできると信じています。SVNでマージしてブランチを削除すると、ブランチの履歴は失われます。GITでマージしてブランチを削除すると、グラフは残り、それに「非難」プラグインが残ります。
Richard Corfield 2013年

1
svnがローカルと中央の両方のデータを調べて情報を導出する必要がある一方で、gitとmercurialはローカルに必要なすべての情報を持っているのではありませんか?
ウォーレンデュー

8

SVNはファイルを追跡し、Gitはコンテンツの変更を追跡します。あるクラス/ファイルから別のクラス/ファイルにリファクタリングされたコードのブロックを追跡するのに十分なほど賢いです。彼らはソースを追跡するために2つの完全に異なるアプローチを使用します。

私はまだSVNを頻繁に使用していますが、Gitを使用したことが何度かあることに非常に満足しています。

時間があれば読みやすい:なぜGitを選んだのか


それも私が読んだものであり、それは私が頼りにしていたものですが、実際には機能していません。
Rolf

Gitはファイルのコンテンツを追跡し、変更としてコンテンツのみを表示します
Ferrybig

6

Joelのブログの記事を読んでください(悲しいことに彼の最後の記事です)。これはMercurialに関するものですが、実際にはGitなどの分散型VCシステムの利点について述べています。

分散バージョン管理では、分散部分は実際には最も興味深い部分ではありません。興味深いのは、これらのシステムがバージョンではなく変更の観点から考えていることです。

こちらの記事をお読みください


5
それは、私がここに投稿する前に考えていた記事の1つでした。しかし、「変化の観点から考える」は非常に漠然としたマーケティングのように思える用語です(Joelの会社がDVCSを今販売していることを思い出してください)
Mr. Boy

2
それも漠然としていると思いました...チェンジセットはバージョン(またはリビジョン)ではなくてはならない部分だといつも思っていました。そのため、一部のプログラマーが変更に関して考えていないことに驚いています。
10

本当に「変更の観点から考える」システムについては、Darcs
Max

@Max:確かに、しかしプッシュが押し寄せてくると、Gitは、実際にマージすることになると、DarcsがSubversionと基本的に同じくらい苦痛なところを提供します。
tripleee

Gitの3つの欠点は、a)文書管理などのバイナリーでは、ブランチやマージを行う可能性が非常に低いため、あまり良くありません。b)すべてのクローンを作成することを想定しています。クローンの肥大化を引き起こす頻繁に変化するバイナリ。これらのユースケースでは、集中型のVCSの方がはるかに優れていると思います。Gitは、特にマージとブランチの定期的な開発にははるかに優れています。
ロッカ2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.