[解決済み】Git のマージは SVN よりも優れているのでしょうか?
質問
分散バージョン管理システムが優れている主な理由の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を採用することで、システムだけでなくユーザーにとっても、より簡単にブランチやマージを行うことができるようになりました。
関連
-
[解決済み] Git で直近のローカルコミットを取り消すには?
-
[解決済み] Gitブランチをローカルやリモートで削除するには?
-
[解決済み] git pull」と「git fetch」の違いは何ですか?
-
[解決済み] コミット前に 'git add' を取り消すにはどうすればよいですか?
-
[解決済み] リモートのGitブランチをチェックアウトするには?
-
[解決済み] Git リポジトリを以前のコミットに戻すにはどうすればよいですか?
-
[解決済み] 現在のGit作業ツリーからローカル(未追跡)ファイルを削除する方法
-
[解決済み] Git で、ステージされていない変更を破棄するにはどうしたらいいですか?
-
[解決済み】"git pull" でローカルファイルを強制的に上書きするには?
-
[解決済み】ローカルのGitブランチの名前を変更するには?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] svn log -v' のように 'git log' にファイル名を表示させる方法
-
[解決済み] SVNコミットコマンド
-
[解決済み] SVN逆マージ?
-
[解決済み】Subversionでコミットされていない変更を一時的に片付ける("git-stash "のようなもの)。
-
[解決済み】TortoiseSVNでユーザーを変更する方法
-
[解決済み】TortoiseSVNで、あるフォルダから別のフォルダにファイル(またはフォルダ)を移動するにはどうすればよいですか?
-
[解決済み】「前の操作が終了していません」でSubversionが固まる?
-
[解決済み】SVNチェックアウトでローカルの変更を破棄する方法は?
-
[解決済み】「ローカル編集、更新時に着信削除」メッセージの解決方法
-
[解決済み] マージ Hg/Git vs. SVN