454

git-merge の man ページには、使用できるマージ戦略がいくつかあります。

  • 解決- これは、3 ウェイ マージ アルゴリズムを使用して 2 つのヘッド (つまり、現在のブランチとプルした別のブランチ) のみを解決できます。交差するマージのあいまいさを慎重に検出しようとし、一般的に安全で高速であると考えられています。

  • recursive - これは、3 方向マージ アルゴリズムを使用して 2 つのヘッドのみを解決できます。3 方向マージに使用できる共通の祖先が複数ある場合、共通の祖先のマージ ツリーを作成し、それを 3 方向マージの参照ツリーとして使用します。これにより、Linux 2.6 カーネルの開発履歴から得られた実際のマージ コミットで行われたテストによって、ミスマージを引き起こすことなく、マージの競合が少なくなることが報告されています。さらに、名前の変更を含むマージを検出して処理できます。これは、1 つのブランチをプルまたはマージするときのデフォルトのマージ戦略です。

  • タコ- これは 2 頭以上のケースを解決しますが、手動での解決が必要な複雑なマージを拒否します。これは主に、トピックのブランチ ヘッドをまとめるために使用することを目的としています。これは、複数のブランチをプルまたはマージする場合のデフォルトのマージ戦略です。

  • ours - これは任意の数のヘッドを解決しますが、マージの結果は常に現在のブランチ ヘッドになります。これは、サイドブランチの古い開発履歴に取って代わるために使用されることを意図しています。

  • サブツリー- これは修正された再帰戦略です。ツリー A と B をマージするとき、B が A のサブツリーに対応する場合、同じレベルでツリーを読み取るのではなく、最初に B が A のツリー構造に一致するように調整されます。この調整は、共通の祖先ツリーに対しても行われます。

デフォルト以外のものをいつ指定する必要がありますか? それぞれどのようなシナリオに最適ですか?

4

4 に答える 4

324

私は解決に慣れていませんが、他のものを使用しました:

再帰的

再帰的は、非早送りマージのデフォルトです。私たちは皆、そのことをよく知っています。

たこ

マージする必要のあるツリーがいくつかあるときに、タコを使用しました。これは、多くのブランチが独立して開発されており、すべてが 1 つのヘッドにまとめられる準備ができている大規模なプロジェクトで見られます。

octopus ブランチは、クリーンに実行できる限り、1 つのコミットで複数のヘッドをマージします。

たとえば、マスターがあり、マージする 3 つのブランチ (a、b、c と呼びます) があるプロジェクトがあるとします。

一連の再帰的なマージは次のようになります (再帰を強制しなかったため、最初のマージは早送りであることに注意してください)。

一連の再帰的マージ

ただし、単一のタコのマージは次のようになります。

commit ae632e99ba0ccd0e9e06d09e8647659220d043b9
Merge: f51262e... c9ce629... aa0f25d...

タコのマージ

私たちのもの

私たちのもの == 別の head を取り込みたいのですが、head が導入するすべての変更を破棄します。

これにより、ブランチの影響を受けずにブランチの履歴が保持されます。

(読んでください:それらのブランチ間の変更も見られません。ブランチはマージされただけで、ファイルには何も行われません。他のブランチでマージしたい場合、毎回「私たちのファイルバージョンまたはそれらのファイルのバージョン」という質問があります。バージョン」を使用できますgit merge -X ours)

サブツリー

サブツリーは、別のプロジェクトを現在のプロジェクトのサブディレクトリにマージする場合に便利です。サブモジュールとして含めたくないライブラリがある場合に便利です。

于 2008-12-14T20:25:49.760 に答える
51

実際、ブランチによってもたらされた変更を放棄したいが、ブランチを履歴に残したい場合、選択したい唯一の2つの戦略と、独立したプロジェクトをスーパープロジェクトのサブディレクトリにマージする場合のサブツリーです(「git-gui」のように) git' リポジトリ)。

octopus merge は、2 つ以上のブランチをマージするときに自動的に使用されます。 resolveは、主に歴史的な理由と、再帰的なマージ戦略のまれなケースに遭遇した場合のためにここにあります。

于 2008-12-15T00:56:24.037 に答える
30

「解決」対「再帰」マージ戦略

再帰は現在のデフォルトの 2 ヘッド戦略ですが、いくつかの検索の後、最終的に「解決」マージ戦略に関する情報を見つけました。

O'Reilly book Version Control with Git ( Amazon ) から引用 (言い換え):

もともと、「解決」は Git マージのデフォルトの戦略でした。

複数の可能なマージ ベースがある十字型マージの状況では、解決戦略は次のように機能します。可能なマージ ベースの 1 つを選択し、最善を期待します。これは実際には思ったほど悪くはありません。ユーザーがコードのさまざまな部分で作業していることがよくあります。その場合、Git は既に適用されているいくつかの変更を再マージしていることを検出し、重複する変更をスキップして競合を回避します。または、これらが競合を引き起こすわずかな変更である場合、少なくとも競合は開発者にとって簡単に処理できるはずです..

デフォルトの再帰戦略では失敗した「解決」を使用して、ツリーを正常にマージしました。エラーが発生していました。このブログ投稿( mirrorfatal: git write-tree failed to write a tree )のおかげで、「-s resolve」を試してみましたが、うまくいきました。理由はまだ正確にはわかりません...しかし、両方のツリーに重複した変更があり、それらを適切に「スキップ」したためだと思います。

于 2012-05-17T13:15:38.813 に答える
4

Git 2.30 (2021 年第 1 四半期) では、ORT (「表向きは再帰的なツイン」)という新しいマージ戦略が導入されます。

git merge -s ort

これは、Elijah Newrenのこのスレッドから来ています。

今のところ、私はそれを「表向きは再帰的なツイン」、または略して「ort」と呼んでいます。> 最初は、(特に大きなリポジトリの場合) もう少し速くできると思うという事実を除いて、人々はそれと現在の再帰戦略の違いに気付かないはずです。

しかし、現在の設計では処理が難しいいくつかの (確かにまれなケースですが) バグを修正できるはずです。また、マージを行わないか、いくつかの楽しい新機能が可能になると思い$GIT_WORK_TREEます$GIT_INDEX_FILE
とにかくそれが希望です。

問題:

理想的な世界では、次のことを行う必要があります。

  • 「 」なしで「 」を実行するよう依頼unpack_trees()します。read-tree -m-u

  • すべてのマージ再帰計算をコア内で実行し、現在のインデックスをそのまま維持しながら、結果のインデックスを準備します。

  • 現在のインコア インデックスと結果のインコア インデックスを比較し、作業ツリーで追加、更新、または削除する必要があるパスを確認し、変更が作業ツリーに反映されたときに情報が失われないことを確認します。木;
    たとえば、結果は作業ツリーに現在削除できない内容のディレクトリがあるファイルを作成したい場合や、作業ツリー ファイルがローカルで変更されているファイルを削除したい場合などです。
    そして最後に

  • 作業ツリーの更新を実行して、結果のコア内インデックスがどのように見えるべきかを一致させます。

結果:

コミット 14c4586 ( 2020 年 11 月 2 日)、コミット fe1a21d (2020 年 10 月 29 日)、コミット 47b1e89コミット 17e5574 (newren 2020 年 10 月 27 日) を参照してください。
( 2020 年 11 月 18 日、コミット a1f9595Junio C Hamanoによってマージされました)gitster

merge-ort: 実装が空の新しいマージ戦略のベアボーン API

署名者: Elijah Newren

これは、新しい統合戦略の始まりです。

いくつかの API の違いがあり、実装にはいくつかの動作の違いがありますが、基本的には の最終的なドロップイン置換として意図されていmerge-recursive.cます。

ただし、マージ再帰と並行して存在するように構築されているため、人々がマージ再帰にフォールバックできる一方で、これらの違いが現実の世界でどのように機能するかを調べる十分な時間があります。
(また、安定性を保つために、このプロセス中にマージ再帰を変更することは避けるつもりです。)

ここで注目すべき主な違いは、作業ツリーとインデックスの更新がマージ アルゴリズムと同時に行われるのではなく、別個の後処理ステップであるということです。
新しい API は、マージを繰り返し (リベースやチェリー ピックなどで) 実行できるように設計されており、中間結果ごとに更新するのではなく、最後に 1 回だけインデックスと作業ツリーを更新できます。

また、インデックスまたは作業ツリーを壊すことなく、どちらもインデックスまたは作業ツリーに一致しない 2 つのブランチ間でマージを実行できます。

と:

commit 848a856、commit fd15863commit 23bef2ecommit c8c35f6commit c12d1f2commit 727c75bcommit 489c85fcommit ef52778commit f06481f (2020 年 10 月 26 日) by Elijah Newren ( newren)を参照してください。
( 2020 年 11 月 18 日、コミット 66c62eaJunio C Hamanoによってマージされました)gitster

t6423, t6436: ダーティ ファイルでの ort 処理の改善に注意してください

署名者: Elijah Newren

unpack_trees()「再帰的」バックエンドは、ステージングされていない変更がマージによって上書きされるかどうかを確認するために依存していますが、名前のunpack_trees()変更を理解していません。また、戻ると、作業ツリーとインデックスにすでに多くの更新が書き込まれています。
そのため、「再帰的」には特別な 4 方向のマージを行う必要があり、作業コピーを追加の相違点として扱う必要があり、競合を避けるためにファイルを上書きして新しい場所に移動することを慎重に回避する必要がありました。

対照的に、「ort」バックエンドはメモリ内で完全なマージを行い、後処理ステップとしてインデックスと作業コピーのみを更新します
途中でダーティ ファイルがある場合は、単純にマージを中止できます。

t6423: ort バックエンドで改善された競合マーカー ラベルが期待されます

署名者: Elijah Newren

競合マーカーには、REF-OR-COMMIT:FILENAME という形式の追加の注釈が含まれており、コンテンツがどこから来ているかを区別するのに役立ちます。:FILENAME履歴の両側で同じである場合、その部分は除外されます (したがって、コンテンツ競合のある名前変更のみが保持されます)。注釈のその部分)。

ただし、:FILENAMEme​​rge-recursive の every-codepath-needs-a-copy-of-all-special-case-code 形式が原因で、誤って注釈が省略される場合がありました。

t6404, t6423: ort バックエンドでの名前変更/削除処理の改善が期待されます

署名者: Elijah Newren

ファイルの名前が変更され、コンテンツの競合がある場合、マージ再帰には、インデックス内の古いファイル名のステージと新しいファイル名のステージがありません。代わりに、古いファイル名に対応するすべてのステージを新しいファイル名に対応する場所にコピーするため、新しいファイル名に対応する 3 つの上位ステージがすべて存在します。

このようにすることで、ユーザーが異なるバージョンにアクセスしやすくなり、競合を解決しやすくなります (古いバージョンと新しいバージョンを手動で ' ' ( man git rm)する必要はありませ)git add

名前の変更/削除も同様に処理する必要があります。名前を変更するファイルには、1 つだけではなく 2 つの段階が必要です。
マージ再帰を今すぐ不安定にしたくないので、代わりに関連するテストを更新して、 " recursive" または " ort" マージ戦略が使用されているかどうかに応じて異なる期待を持たせます。


Git 2.30 (2021 年第 1 四半期)、新しいマージ戦略の準備。

commit 848a856、commit fd15863commit 23bef2ecommit c8c35f6commit c12d1f2commit 727c75bcommit 489c85fcommit ef52778commit f06481f (2020 年 10 月 26 日) by Elijah Newren ( newren)を参照してください。
( 2020 年 11 月 18 日、コミット 66c62eaJunio C Hamanoによってマージされました)gitster

merge tests: ort でのディレクトリ/ファイルの競合処理の改善が期待されます

署名者: Elijah Newren

merge-recursive.cunpack_trees()結果を得るために、実行してから「マイナータッチアップを行う」という考えに基づいて構築されています。
残念なことに、unpack_trees()は随時更新モードで実行されていたため、merge-recursive.cそれに続き、すぐに評価して随時修正する設計になってしまいました。

ディレクトリ/ファイルの競合のようなものは、インデックス データ構造ではうまく表現できず、処理するために特別な追加コードが必要でした。
しかし、その後、名前変更/削除の競合がディレクトリ/ファイルの競合にも関係している可能性があることが判明したとき、特別なディレクトリ/ファイル競合処理コードを名前変更/削除コードパスにコピーする必要がありました。
...そして、変更/削除、および名前変更/名前変更(1to2)の競合のためにコピーする必要がありました...それでも、まだ一部を見逃していました。
さらに、ファイル/サブモジュールの競合とサブモジュール/ディレクトリの競合もあることが判明したとき、コードベース全体のすべての特殊ケースに特別なサブモジュール処理コードをコピーする必要がありました。

そして、ディレクトリ/ファイルの競合の処理が最適ではないことが判明しました。これは、競合するファイルの内容を格納するために追跡されていないファイルが作成され、誰かが ' git merge --abort' ( man )または ' git rebase --abort'を実行してもクリーンアップされないためです。 () .

また、インデックス内のディレクトリ/ファイルの競合が原因で、これらのファイルに対応するインデックス エントリを追加または削除しようとすることも困難でした。
しかし、merge-recursive.cこれらを正しく処理するように変更することは非常に困難でした。ディレクトリ/ファイル/サブモジュールの競合をすべて更新する必要がある、似ているが同一ではないコードを持つサイトがコード内に非常に多くあったためです。

私は、単一のコードパスを介して、merge-ort ですべてのディレクトリ/ファイル/サブモジュールの競合処理をプッシュし、追跡されたコンテンツを保存するための追跡されていないファイルを作成しないように懸命に取り組んできました (代替パスで物事を記録しますが、より高次の段階があることを確認します)。索引で)。


Git 2.31 (2021 年第 1 四半期) では、「正しく行われた」マージ バックエンドが出現し始めています。
例:

Junio C Hamano ( )によるcommit 6d37ca2 (2020 年 11 月 11 日)を参照してください。コミット89422D2 、コミットEF2B369 コミット70912F6 コミット6681CE5 コミット9FEFCE6 BB470F4のコミット、 EE4012DのコミットコミットA9945BBコミット6A02DD9コミットメント9884のコミットメントのコミットメントを参照してくださいc801717gitster
コミット e4171b1コミット 231e2ddコミット 5b59c3d (2020 年 12 月 13 日) by Elijah Newren ( newren)
( 2021 年 1 月 6 日にコミット f9d29da でJunio C Hamanoによってマージされました)gitster

merge-ort: の実装を追加record_conflicted_index_entries()

署名者: Elijah Newren

の後checkout()、作業ツリーには適切なコンテンツがあり、インデックスは作業コピーと一致します。
つまり、変更されていない完全にマージされたすべてのファイルには正しいインデックス エントリがありますが、競合するエントリは更新する必要があります。

これを行うには、競合するエントリをループし、パスの既存のインデックス エントリを でマークし、CE_REMOVEインデックスの最後にパス用にステージングされた新しい高次を追加し (通常のインデックスの並べ替え順序を無視)、ループの最後にCE_REMOVED-markedキャッシュ エントリを削除し、インデックスを並べ替えます。


Git 2.31 (2021 年第 1 四半期) では、"ORT" マージ戦略に名前変更の検出が追加されました。

commit 6fcccbd、commit f1665e6commit 35e47e3commit 2e91dddcommit 53e88a0commit af1e56c ( 2020年 12 月 15 日)、commit c2d267dcommit 965a7bccommit f39d05ccommit e1a124e、 commit 864075eij を参照してください。( 2021 年 1 月 25 日コミット 2856089Junio C Hamanoによってマージされました)newren
gitster

例:

merge-ort: 通常の名前変更処理の実装を追加

署名者: Elijah Newren

通常の名前変更の処理を実装します。
このコードは、 の以下を置き換えますmerge-recurisve.c

  • RENAME_NORMALに関連するコードprocess_renames()
  • RENAME_NORMAL場合process_entry()

merge-recursive.cまた、このケース (または他の名前変更ケース) では不要になる、複数の異なる名前変更ケース用の共有コードがいくつかあります。

  • handle_rename_normal()
  • setup_rename_conflict_info()

4 つの個別のコードパスを 1 つに統合することは、設計の変更によって可能になりました。名前変更以外のすべての競合タイプ (ディレクトリ/ファイル、変更/削除など) を直交的に処理できるようにエントリをprocess_renames()微調整します。conflict_infoopt->priv->pathsprocess_entry()

これは、ある種の競合タイプの組み合わせの特別な実装を見逃す可能性がはるかに低いことを意味します ( 66c62ea ("Merge branch 'en/merge-tests'", 2020-11-18, Git v2.30.0)によってもたらされたコミットを参照) -rc0 --バッチ #6に記載されているマージ)、特にコミット ef52778 (「マージ テスト: ort でのディレクトリ/ファイル競合処理の改善を期待」、2020-10-26、Git v2.30.0-rc0 --バッチ #に記載されているマージ6)詳細について)。

これは、ワークツリー/インデックスの更新を関数内で直角に処理できるようにすることと合わせて、merge_switch_to_result()さまざまな特別な名前変更のケースのコードを劇的に簡素化します。

(公平を期すために、通常の名前変更を処理するためのコードは、以前はそれほど複雑ではありませんでしたが、今でもはるかに単純です。)

また、Git 2.31 (2021 年第 1 四半期) でも引き続き、Git 2.31 (2021 年第 1 四半期) では、oRT マージ戦略はマージ競合のサポートをさらに学習します。

commit 4ef88fc、commit 4204cd5commit 70f19c7commit c73cda7commit f591c47commit 62fdec1commit 991bbdccommit 5a1a1e8commit 23366d2commit 0ccfa4e (01 Jan 2021) by Elijah Newren ( newren)を参照してください。
( 2021 年 2 月 5 日にコミット b65b9ffJunio C Hamanoによってマージされました)gitster

merge-ort: 同じパスにある異なる種類のファイルの処理を追加します

署名者: Elijah Newren

次のタイプの衝突を明示的に考慮する処理を追加します。

  • ファイル/サブモジュール
  • ファイル/シンボリックリンク
  • submodule/symlink> それらを競合として同じパスに残すと、ユーザーが解決するのが難しくなるため、一方または両方を脇に移動して、それぞれが独自のパスを取得できるようにします。

再帰処理 (すなわち
call_depth > 0) の場合、変更/削除の競合、バイナリ ファイル、競合するサブモジュール値などの場合と同様に、2 つのマージ ベースのマージ ベースをマージ結果として使用できることに注意してください。

于 2020-11-22T02:20:23.097 に答える