Stack Overflowの質問GitでのマージがSVNよりも優れている理由および/または理由はいくつかの素晴らしい質問です素晴らしい答え。ただし、Gitでのマージが [〜#〜] svn [〜#〜] よりもうまく機能する簡単な例を示しているものはありません。
この質問が重複してクローズされる可能性がありますが、
いくつかのポイント:
実用的の観点から、私が「ビッグバンマージ」問題と呼んでいるため、マージは伝統的に「難しい」ものでした。開発者がしばらくの間コードを処理していて、まだコミットしていないとします(おそらく、開発者はtrunk
に対してSubversionで作業することに慣れていて、未完成のコードをコミットしません)。開発者が最終的にコミットすると、多くの変更がすべて1つのコミットにまとめられます。自分の作業をこの「ビッグバン」コミットとマージしたい他の開発者にとって、VCSツールは、最初の開発者がコミットしたポイントに到達した方法について十分な情報を持っていないので、「ここに巨大な競合がありますこの関数全体で、修正してください。」.
一方、Gitや安価なローカルブランチを持つ他のDVCSを使用する通常のstyleは、定期的にコミットすることです。かなり意味のある作業を1つ行ったら、それをコミットします。完璧である必要はありませんが、一貫性のある作業単位である必要があります。マージに戻ったとき、元の状態から現在の状態に戻ったことを示す、より小さなコミットの履歴がまだありますhow。 DVCSがこれを他の作業とマージしようとすると、いつどのような変更が行われたかについてより多くの情報が得られ、最終的に小さいおよび少ないの競合が発生します。
重要なのは、何かを終えた後でのみ1つのビッグバンをコミットすることで、Gitとの難しい問題をマージすることができるということです。 Gitは、コミットを小さくすることをお勧めします(可能な限り痛みを伴わないようにすることで)。これにより、将来のマージが容易になります。
GitがSubversionよりも優れていなかったという小さな実験についてのみお伝えできます(同じ問題)。
このケースについて疑問に思っていました。同じコミットに基づく2つのブランチ「mytest1」と「mytest2」から始めます。関数blub()を含むCファイルがあります。ブランチmytest1で、「blub()」をファイル内の別の位置に移動してコミットします。ブランチmytest2で、blub()を変更してコミットします。ブランチmytest2で、「gitmergemytest1」を使用しようとします。
マージの競合が発生しているようです。 Gitが「blub()」がmytest1で移動されたことを認識し、mytest2の変更をmytest1の移動と自動マージできることを期待していました。しかし、少なくとも私が試したとき、これは自動的に機能しませんでした...
したがって、Gitは何がマージされ、何がまだマージされていないかを追跡するのにはるかに優れていることを完全に理解していますが、GitがSVNよりも優れている「純粋な」マージケースがあるのではないかと思います...
この質問は長い間私を悩ませてきたので、Gitがより良いであるのに対し、SVNでのマージは失敗する具体的な例を作成しようとしていました。
ここで見つけました https://stackoverflow.com/a/2486662/191752 ですが、これには名前の変更が含まれており、ここでの質問は名前の変更がない場合でした。
それで、これは基本的にこれを試みるSVNの例です:
bob +-----r3----r5---r6---+
/ / \
anna / +-r2----r4----+--+ \
/ / \ \
trunk r1-+-------------------r7-- Conflict
ここでのアイデアは次のとおりです。
これが Bash スクリプトで、この競合が発生します(SVN1.6.17およびSVN1.7.9を使用)。
#!/bin/bash
cd /tmp
rm -rf rep2 wk2
svnadmin create rep2
svn co file:///tmp/rep2 wk2
cd wk2
mkdir trunk
mkdir branches
echo -e "A\nA\nB\nB" > trunk/f.txt
svn add trunk branches
svn commit -m "Initial file"
svn copy ^/trunk ^/branches/anna -m "Created branch anna"
svn copy ^/trunk ^/branches/bob -m "Created branch bob"
svn up
echo -e "A\nMA\nA\nB\nB" > branches/anna/f.txt
svn commit -m "anna added text"
echo -e "A\nMB\nA\nB\nMB\nB" > branches/bob/f.txt
svn commit -m "bob added text"
svn up
svn merge --accept postpone ^/branches/anna branches/bob
echo -e "A\nMAB\nA\nB\nMB\nB" > branches/bob/f.txt
svn resolved branches/bob/f.txt
svn commit -m "anna merged into bob with conflict"
svn up
svn merge --reintegrate ^/branches/anna trunk
svn commit -m "anna reintegrated into trunk"
svn up
svn merge --reintegrate --dry-run ^/branches/bob trunk
最後の「--dry-run」は、競合が発生することを示しています。代わりに、最初にアンナの再統合をボブのブランチにマージしようとすると、競合も発生します。したがって、最後のsvn merge
を次のように置き換えると
svn merge ^/trunk branches/bob
これも競合を示しています。
これはGit1.7.9.5と同じです:
#!/bin/bash
cd /tmp
rm -rf rep2
mkdir rep2
cd rep2
git init .
echo -e "A\nA\nB\nB" > f.txt
git add f.txt
git commit -m "Initial file"
git branch anna
git branch bob
git checkout anna
echo -e "A\nMA\nA\nB\nB" > f.txt
git commit -a -m "anna added text"
git checkout bob
echo -e "A\nMB\nA\nB\nMB\nB" > f.txt
git commit -a -m "bob added text"
git merge anna
echo -e "A\nMAB\nA\nB\nMB\nB" > f.txt
git commit -a -m "anna merged into bob with conflict"
git checkout master
git merge anna
git merge bob
F.txtの内容はこのように変わります。
初期バージョン
A
A
B
B
アンナの修正
A
MA
A
B
B
ボブの修正
A
MB
A
B
MB
B
アンナの支店がボブの支店に統合された後
A
MAB
A
B
MB
B
すでに多くの人が指摘しているように、問題は、SubversionがBobがすでに競合を解決したことを思い出せないことです。したがって、ボブのブランチをトランクにマージしようとすると、競合を再解決する必要があります。
Gitの動作はまったく異なります。ここにgitが行っていることのいくつかのグラフィック表現があります
bob +--s1----s3------s4---+
/ / \
anna / +-s1----s2----+--+ \
/ / \ \
master s1-+-------------------s2----s4
s1/s2/s3/s4は、gitが取得する作業ディレクトリのスナップショットです。
ノート:
これがすべてを示す「gitref-log」の出力です。
88807ab HEAD@{0}: merge bob: Fast-forward
346ce9f HEAD@{1}: merge anna: Fast-forward
15e91e2 HEAD@{2}: checkout: moving from bob to master
88807ab HEAD@{3}: commit (merge): anna merged into bob with conflict
83db5d7 HEAD@{4}: commit: bob added text
15e91e2 HEAD@{5}: checkout: moving from anna to bob
346ce9f HEAD@{6}: commit: anna added text
15e91e2 HEAD@{7}: checkout: moving from master to anna
15e91e2 HEAD@{8}: commit (initial): Initial file
あなたがこれから見ることができるように:
「gitcat-fileHEAD @ {8} -p」を使用すると、最初のコミットオブジェクトの完全な詳細を調べることができます。上記の例では、次のようになりました。
tree b634f7c9c819bb524524bcada067a22d1c33737f
author Ingo <***> 1475066831 +0200
committer Ingo <***> 1475066831 +0200
Initial file
「ツリー」行は、このコミットが参照するスナップショットs1(== b634f7c9c819bb524524bcada067a22d1c33737f)を識別します。
「gitcat-fileHEAD @ {3} -p」を実行すると、次のようになります。
tree f8e16dfd2deb7b99e6c8c12d9fe39eda5fe677a3
parent 83db5d741678908d76dabb5fbb0100fb81484302
parent 346ce9fe2b613c8a41c47117b6f4e5a791555710
author Ingo <***> 1475066831 +0200
committer Ingo <***> 1475066831 +0200
anna merged into bob with conflict
上記は、annaの開発ブランチをマージするときに作成されるコミットオブジェクトbobを示しています。ここでも、「ツリー」行は作成されたスナップショット(ここではs3)を指します。さらに、「親」行に注意してください。 「親346ce9f」で始まる2番目のものは、後でgitに、bobの開発ブランチをmasterブランチにマージしようとすると、bobのこの最後のコミットには祖先としてのannaの最後のコミットがあることを伝えます。これが、Gitがbobの開発ブランチのマスターブランチへのマージが「早送り」であることを知っている理由です。
具体的な例はありませんが、繰り返しマージは特に難しいです。呼び出された十字マージ。
a
/ \
b1 c1
|\ /|
| X |
|/ \|
b2 c2
b2とc2のマージ
の違いを説明するSubversionWikiのwikiページ マージ情報 ベースの非対称Subversionマージ(「sync」および「reintegrate」方向)およびマージtrackingベースの対称DVCSでのマージには、「 Criss-Crossマージとの対称マージ "」セクションがあります。
私が考えることができる最も具体的な例は、マージの競合を引き起こさない最も単純なマージです。ただし、その例の(TL; DR)Gitは、Subversionよりも本質的に単純な手順です。理由を確認しましょう:
Subversionで次のシナリオを考えてみましょう。トランクと機能ブランチ:
1 2 3
…--o--o--o trunk
\4 5
o--o branches/feature_1
マージするには、Subversionで次のコマンドを使用できます。
# thank goodness for the addition of the --reintegrate flag in SVN 1.5, eh?
svn merge --reintegrate central/repo/path/to/branches/feature_1
# build, test, and then... commit the merge
svn commit -m "Merged feature_1 into trunk!"
Subversionでは、変更をマージするには別のコミットが必要です。これは、機能ブランチ仮想ディレクトリの変更をトランクに適用してマージが行った変更を公開するためです。これにより、トランクを操作するすべての人がトランクを使用できるようになり、リビジョングラフは次のようになります。
1 2 3 6
…--o--o--o------o /trunk
\4 5/
o--o /branches/feature_1
これがgitでどのように行われるかを見てみましょう。
Gitでは、ブランチはリビジョングラフ上の栄光のブックマークであるため、このマージコミットは実際には必要ありません。したがって、同じ種類のリビジョングラフ構造では、次のようになります。
v-- master, HEAD
1 2 3
…--o--o--o
\4 5
o--o
^-- feature_branch
現在マスターブランチにあるヘッドを使用して、機能ブランチとの単純なマージを実行できます。
# Attempt a merge
git merge feature_branch
# build, test, and then... I am done with the merge
...そして、機能ブランチが指しているコミットにブランチを早送りします。これが可能になるのは、Gitがマージの目標が直接の子孫であり、現在のブランチが発生したすべての変更を取り込むだけでよいことを知っているためです。改訂グラフは次のようになります。
1 2 3 4 5
…--o--o--o--o--o
^-- feature_branch, master, HEAD
Gitが行ったのはブランチ参照をさらに前に移動することだけなので、変更には新しいコミットは必要ありません。残っているのは、もしあれば、これを公開リポジトリに公開することだけです。
# build, test, and then... just publish it
git Push
この単純なシナリオを考えると、SubversionとGitの違いで2つのことを主張できます。
これが最も単純なマージシナリオであることを考えると、Subversionがgitよりも簡単であると主張するのは困難です。
より難しいマージシナリオでは、gitは、履歴の書き換えを犠牲にして、より単純なリビジョングラフを生成する ブランチをリベースする 機能も提供します。ただし、コツをつかんで、すでに公開されているものの履歴の書き換えを公開しないようにしたら、それはそれほど悪いことではありません。
インタラクティブリベース 質問の範囲外ですが、正直に言うと;これにより、コミットを再配置、スキッシュ、および削除することができます。履歴の書き換えは設計上不可能であるため、Subversionに切り替えたくありません。
GitでのマージはSVNよりも簡単であるというのは神話のようです...
たとえば、Gitは、SVNとは異なり、変更のある作業ツリーにマージできません。
次の単純なシナリオを考えてみましょう:作業ツリーにいくつかの変更があり、独自の変更をコミットせずにリモート変更を統合したい 。
[〜#〜] svn [〜#〜]:update
、[競合の解決]。
Git:stash
、fetch
、rebase
、stash pop
、[競合の解決]、[stash drop
競合があった場合]。
または、Gitでもっと簡単な方法を知っていますか?
ところで、このユースケースは非常に重要であるため、IntelliJは、上記の手動手順を自動化できるGitの欠落している「プロジェクトの更新」機能(SVN更新へのアナログ)を実装しました。
答えを短くする-DVCSでは、ローカルソース管理があるため、マージプロセスで何かが失敗した場合(大規模なマージで発生する可能性があります)、変更が加えられた以前のローカルバージョンにいつでもロールバックできます。マージする前に作成してから、再試行してください。
したがって、基本的には、プロセス中にローカルの変更が破損することを恐れずにマージを実行できます。
「Merge-RefactoredHell」を除外すると、サンプルを取得できませんfairサンプルが存在しないため、