ITエンジニアによるITエンジニアのためのブログ

Gitのマージ戦略の違いと比較

Gitのマージには、「マージ戦略(Merge Strategy)」と呼ばれる複数の異なる統合方法が存在します。

通常はGitが最も適切な戦略を自動的に判断してくれるため、意識することは少ないかもしれません。しかし、それぞれの戦略の違いを知らないと、マージ後のコミット履歴が意図しない形になったり、不適切な方法を選んでコンフリクトの解決に手間取ったりすることがあります。

そこで本記事では、Gitのマージ戦略の違いを、マージ前後のコミット履歴図を使って視覚的に解説します。

目次

  • マージ前チェックとAlready up-to-date
  • Fast-forwardマージ
  • Resolveマージ
  • ORTマージ
  • Octopusマージ
  • Oursマージ
  • Subtreeマージ
  • マージ戦略の自動選択の順番
  • まとめ

マージ前チェックとAlready up-to-date

現在のブランチが、マージ元ブランチの全てのコミットを既に含んでいる状態であれば、そもそも新しいマージ作業を行う必要がありません。

この場合、git merge コマンドは「Already up to date.」(既に最新です)というステータスメッセージを表示して処理を終了します。

$ git merge develop
Already up to date.

Already up-to-dateの例

Already up-to-dateの例

Fast-forwardマージ

Fast-forward(早送り)マージは、現在のブランチの最終コミット(HEAD)から見て、マージ元ブランチの履歴が一本道で続いている場合に使われます。つまり、分岐が発生していない状態です。

このマージ方法では、新しいマージコミットは作成されません。現在のブランチのHEADが、マージ元ブランチの最終コミットの位置まで単に移動(早送り)するだけです。これにより、両方のブランチが全く同じコミット履歴を参照するようになります。

Fast-forwardマージの例

Fast-forwardマージの例

上記の例で、develop ブランチを main ブランチにマージするとします。main の最終コミットは develop ブランチの祖先であるため、Fast-forwardマージが適用されます。

マージ後は、以下の図のように maindevelop は同一のコミットを指すようになります。

Fast-forwardマージ後の状態

Fast-forwardは、ローカルでコミットを行わずに git pull を実行し、リモートリポジトリの更新を取り込む際などによく発生します。

Resolveマージ

Resolveマージは、2つのブランチを統合する標準的な方法で、共通の祖先が1つだけの場合に使用されます。これは一般的に「3-wayマージ(3方向マージ)」と呼ばれます。

3方向とは、以下の3つのポイントを指します。

  1. 共通の祖先(マージベース)
  2. マージ元ブランチの最終コミット
  3. マージ先ブランチ(現在のブランチ)の最終コミット

Resolveマージの例

Resolveマージの例

上記の図で developmain にマージする場合、共通の祖先である「コミットa」がマージベースとして使われ、以下の順序で処理が実行されます。

  1. マージベース「a」と develop ブランチの最終コミットの差分を計算。
  2. その差分を main ブランチに適用し、新しい「マージコミットb」を作成。
Resolveマージ後の状態

このように、Resolveマージでは分岐した履歴を一つにまとめるための新しいコミット(マージコミット)が作成されます。

ORTマージ

ORTマージは、Git 2.34以降でデフォルトとなった新しいマージ戦略です。2つのブランチをマージするという点はResolveと似ていますが、共通の祖先(マージベース)が2つ以上ある複雑なケースにも対応できる、より強力で汎用的な戦略です。

ORTは以下の手順でマージを行います。

  1. 全てのマージベースを再帰的にマージし、1つの「仮想マージベース」を作り出します。
  2. その仮想マージベースと、マージ元ブランチの最終コミットの差分を計算します。
  3. 差分を現在のブランチに適用し、マージコミットを作成します。
  4. 仮想マージベースを破棄します。

ORTマージの例

ORTマージの例

上記の図のように、コミットaとbの両方が共通の祖先になり得る場合、以下の順序で処理されます。

  1. さらにその祖先である「c」をベースとして、aとbを一時的にマージします。
  2. この一時的なマージ結果を「仮想マージベース」として使用し、maindevelop を3-wayマージします。
  3. 最終的にマージコミット「d」が作成されます。

このように、共通の祖先が複数ある場合でも再帰的に祖先を解決して3-wayマージを行うのがORTの特徴です。

ORTマージによる仮想マージベース作成とマージ後の状態

Note: Recursive戦略とORT戦略 以前のGitでは、この再帰的な処理を「Recursive」戦略が担っていました。ORTは “Ostensibly Recursive’s Twin”(表向きはRecursiveの双子)の略で、Recursiveと同じ結果を出しつつ、より高速で正確に動作するようにゼロから書き直された改良型エンジンです。 現在のGitではORTがデフォルトであり、従来のRecursive戦略はレガシーな扱いとなっています。

なお、Gitはマージベースの発見を自動的に行いますが、手動で確認したい場合は以下のコマンドを使用します。

git merge-base --all branch1 branch2 
# 複数のマージベースが存在する場合、2つ以上のコミットIDが表示されます

Octopusマージ

Octopusマージは、3つ以上のブランチを一度にマージする際に使われます。タコ(Octopus)が複数の足を持っているように、複数のブランチを一つのコミットに統合します。

Octopusマージの例

Octopusマージ前の状態

Octopusマージ前の例

例えば、stage ブランチに maindevelop の両方を一度にOctopusマージする場合、内部では各ブランチとの一時的なマージが繰り返され、最終的に単一のマージコミットが生成されます。

$ git merge main develop
# 出力例
Trying simple merge with main
Trying simple merge with develop
Merge made by the 'octopus' strategy.

Octopusマージ後の状態(aはマージコミット)

Octopusマージ後の状態

結果として作成されるマージコミットは、3つ以上(元の親 + マージしたブランチ数)の親コミットを持つことになります。

# マージコミットのMerge項目に3つの親コミットIDが存在
$ git show stage
commit 0f31830c7b71003dc9d0197cffe4befc9d1aa849 (HEAD -> stage)
Merge: b9fc6cc d002565 db2f517

Oursマージ

Oursマージは特殊な戦略です。複数のブランチを統合したという「記録(履歴)」は残したいが、マージ元ブランチの変更内容(コードの差分)は一切取り込みたくない場合に使用します。

$ git merge -s ours main develop

Oursマージの例

マージ前

Git Oursマージ前

例えば、maindevelopstage にOursマージすると、履歴図の上では合流しますが、ファイルの内容は stage のまま変わりません。

Git Oursマージ後

これは、git cat-file コマンドでツリーオブジェクトを確認することで証明できます。マージコミットが参照するツリーIDは、マージ前のHEADのツリーIDと完全に一致します。

# マージコミットのツリーオブジェクト
git cat-file -p stage
# 出力例
tree 8bea564ff91cad5b1fbf5e23d913c51722f5ee12
parent b9fc6cc18cd17740cd7091712e7847476f69b875
parent d0025657d669321c5e7281eb47d53e170e9c072e
parent 11de1e08e696c4fcd3e864e5e2e67aa97b37c1e3

# マージ前のツリーオブジェクト
git cat-file -p stage^1
tree 8bea564ff91cad5b1fbf5e23d913c51722f5ee12
parent a0c07eda4181ae8f39723299f9d65b02ea915170

Oursマージは頻繁に使うものではありませんが、「あるブランチの開発を終了し、その履歴だけを統合済みとしてマークしておきたい(が、内容は取り込みたくない)」といった特殊な運用フローで役立ちます。

Subtreeマージ

Subtreeマージは、マージ元ブランチのプロジェクト全体を、マージ先プロジェクトのあるサブディレクトリ(サブツリー)として取り込む際に使用します。

Gitサブツリーマージ前

Gitはファイルパスやディレクトリ構造を解析し、どのサブディレクトリにマージすべきかを自動的に(ヒューリスティックに)判断します。

Subtreeマージの概念図(sはマージコミット)。

例えば、外部ライブラリを自分のリポジトリ内の lib/ フォルダで管理しており、そのライブラリの更新をGit経由で同期したい場合に有効です。

1.初回導入時: 既存のファイルと履歴を関連付けるため、まず ours 戦略で履歴を統合させます(--allow-unrelated-histories が必要な場合もあります)。

git pull -s ours --allow-unrelated-histories repository_url refspec

2. 更新取り込み時: ライブラリの更新を取り込む際は、Subtree戦略を指定します。

git pull -s subtree repository_url refspec

Gitはリポジトリ内の構造を確認し、lib/ フォルダの構造と外部リポジトリの構造が一致することを発見して、適切に差分を lib/ フォルダ内に適用します。

マージ戦略の自動選択の順番

明示的に戦略を指定しない限り、Gitは以下の優先順位で戦略を自動選択します。

  1. Octopus: 3つ以上のブランチを同時にマージする場合。
  2. Already up-to-date: マージ元の最新コミットが、既に現在のブランチに含まれている場合(処理終了)。
  3. Fast-forward: 現在のブランチのHEADが、マージ元ブランチの直接の祖先である場合。
  4. ORT: 上記以外で、2つのブランチをマージする場合(Git 2.34以降のデフォルト)。

Resolve、Ours、Subtreeなどの戦略を使いたい場合は、以下のように -s オプションで明示的に指定する必要があります。

git merge -s strategy_name branch_name

まとめ

本記事で紹介したGitマージ戦略を表にまとめると以下の通りです。

戦略マージコミット作成差分の適用統合ブランチ数特徴
Fast-forwardなしなし2単にHEADを進めるだけ(早送り)。
Resolveありあり2共通祖先が1つの場合の標準的なマージ。
ORTありあり2共通祖先が複数の場合も対応する高機能なデフォルト戦略。
Octopusありあり3以上複数のブランチを一度に統合。
Oursありなし2以上履歴のみ統合し、他ブランチの変更は破棄。
Subtreeありあり2別のリポジトリをサブディレクトリとして統合。

Gitの各マージ戦略がどのように差分を扱い、履歴を形成するのかを理解することで、より安全で効率的な開発フローを構築できます。ぜひご自身のプロジェクト管理に役立ててください。