1. ホーム

[解決済み】Git のマージは SVN よりも優れているのでしょうか?

2022-04-13 06:51:03

質問

分散バージョン管理システムが優れている主な理由の1つは、SVNのような従来のツールよりもはるかに優れたマージ機能であると、いくつかの場所で耳にしたことがあります。 これは、2つのシステムがどのように動作するかの本質的な違いによるものなのでしょうか? 特定 Git/Mercurial などの DVCS 実装は、SVN よりも巧妙なマージ・アルゴリズムを持っているだけなのでしょうか?

解決方法は?

なぜSubversionよりもDVCSでのマージの方が良いのかという主張は、少し前のSubversionでのブランチとマージの動作に大きく基づいています。以前の Subversion は 1.5.0 ブランチがいつマージされたかという情報を保存していなかったため、マージを行う際には、マージされるべきリビジョンの範囲を指定する必要がありました。

では、なぜSubversionのマージは 吸う ?

この例を熟考してください。

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

をしたいとき マージ b1 の変更を trunk に取り込むには、trunk がチェックアウトされたフォルダーに立ちながら、次のコマンドを実行します。

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はまだダメなんだ...。

サブバージョンのような中央集権的なシステムで 仮想ディレクトリ は吸う。なぜか?なぜなら、誰もがそれを見ることができるからです...ゴミのような実験的なものでさえも。実験的なものであれば、ブランチは良い でも、みんなの実験やおばさんの実験を見たくはないでしょう? . これは深刻な認知ノイズです。枝葉を増やせば増やすほど、くだらないものが増えていきます。

リポジトリに公開ブランチが増えれば増えるほど、すべての異なるブランチを追跡することが難しくなります。つまり、そのブランチがまだ開発中なのか、それとも本当に死んでしまったのか、どんな集中型バージョン管理システムでも見分けるのは難しい、という疑問が出てくるのです。

私が見たところ、ほとんどの場合、組織ではとにかくひとつの大きなブランチを使うことがデフォルトになっています。なぜなら、テスト版とリリース版、そしてブランチから得られるその他の良いものを追跡することが難しくなるからです。

では、なぜGit、Mercurial、BazaarなどのDVCSは、Subversionよりもブランチングやマージに優れているのでしょうか?

その理由はとてもシンプルです。 ブランチはファーストクラスの概念である . あるのは 仮想ディレクトリなし ブランチはDVCSのハードオブジェクトであり、リポジトリの同期を簡単に行うためにはそのようなオブジェクトである必要があります。 プッシュ プル ).

DVCSで作業するときに最初にすることは、リポジトリのクローンです(gitの clone hgの clone とbzrの branch ). クローン作成は、概念的にはバージョン管理でブランチを作成するのと同じことです。これをこう呼ぶ人もいます。 分岐 または ブランチング (後者はしばしば同位置のブランチを指す場合にも使われますが)、同じようなものです。すべてのユーザーが自分のリポジトリを実行することで、あなたは ユーザーごとのブランチング が進行しています。

バージョン構成は ツリーではない であり、むしろ グラフ の代わりに より具体的には 有向非循環グラフ (DAG、サイクルを持たないグラフの意)。DAGの詳細については、各コミットが1つ以上の親リファレンス(コミットの基となったもの)を持っていること以外は、深く考える必要はないでしょう。そのため、以下のグラフでは、リビジョン間の矢印が逆になっています。

マージの非常に簡単な例は次のようなものです。 origin と、そのリポジトリを自分のマシンにクローンしているユーザー、アリス。

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

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

クローン中に起こることは、すべてのリビジョンが正確にAliceにコピーされ(これは一意に識別可能なハッシュIDによって検証されます)、オリジンのブランチがどこにあるのかをマークします。

アリスはそれから自分のリポジトリで作業し、コミットし、その変更をプッシュすることにしました。

         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 はこれを "fast-forward" と呼んでいます)。

         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方向マージのアルゴリズムは、どのバージョン管理システムでもほとんど同じなので、問題はマージのアルゴリズムではありません。 問題は、何よりも構造についてです .

では、どのような例があるのか、見せてもらったらどうでしょう。 リアル をマージしますか?

上の例は非常に単純なものなので、もっと一般的なものではありますが、もっとひねった例をしてみましょう。次のことを思い出してください。 origin は3つのリビジョンで始まりましたね。さて、それらを行った人、仮に彼を ボブ は、自分自身のリポジトリにコミットしています。

         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

これで、Bob は自分の変更を直接 origin リポジトリに保存されます。システムはどのようにこれを検出するかというと、Bobのリビジョンが直接 origin のもので、この場合はそうではありません。プッシュしようとすると、システムは " のようなことを言うでしょう。 ええと...残念ながら、それはできません。 ."

そのため、Bob は変更を取り込んでからマージしなければなりません (git の pull またはhgの pull merge または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

プルプロセスの第二段階は、分岐したチップをマージして、その結果をコミットすることです。

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

うまくいけばマージでコンフリクトが発生することはありません(もしコンフリクトが発生することが予想される場合は、git で fetch 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でマージするためのオプションとして、もう一つ リベース これはボブの変更を最新の変更の後に移動させます。この回答がこれ以上冗長になるのは避けたいので、以下の文章を読んでください。 ギット , マーキュリアル または バザール のドキュメントを参照してください。

読者のための練習として、他のユーザーを巻き込んでどう動くかを描いてみてください。上のBobの例と同じようなことができます。リポジトリ間のマージは、すべてのリビジョン/コミットが一意に識別可能なので、思ったより簡単です。

また、各開発者間でパッチを送るという問題もあります。これは Subversion では大きな問題でしたが、git、hg、bzr ではリビジョンを一意に識別できるようにすることで緩和されています。誰かが自分の変更をマージし(つまりマージコミット)、それを中央リポジトリにプッシュするかパッチを送るかして、チームの他の全員が利用できるようにすると、マージについて心配する必要がなくなります。Martin Fowler はこのような働き方を次のように呼んでいます。 プロミスキャス統合 .

Subversionとは構造が異なるので、代わりにDAGを採用することで、システムだけでなくユーザーにとっても、より簡単にブランチやマージを行うことができるようになりました。