はじめに
ブランチを使った並行開発では、最終的に変更内容を統合する「マージ」が必要になります。しかし、マージ時に「コンフリクト(競合)」が発生して戸惑った経験はありませんか。
本記事では、git mergeコマンドの仕組みからFast-forwardマージと3-wayマージの違い、マージコミットの役割、そしてコンフリクトが発生した際の解消手順までを体系的に解説します。この記事を読み終えると、以下のことができるようになります。
git mergeの基本的な使い方を理解できる- Fast-forwardマージと3-wayマージの違いを説明できる
- マージコミットの意味と役割を理解できる
- コンフリクトの発生パターンを把握できる
- コンフリクトを適切に解消し、マージを完了できる
実行環境と前提条件
本記事の内容は、以下の環境で動作確認を行っています。
| 項目 | 要件 |
|---|---|
| Git | 2.40以上 |
| OS | Windows 10/11、macOS 12以上、Ubuntu 22.04以上 |
| ターミナル | コマンドプロンプト、PowerShell、Terminal.app、bash等 |
| エディタ | VS Code推奨 |
前提条件として、以下の知識があることを想定しています。
- コマンドライン操作の基礎知識(
cd、ls/dir、mkdir等) - テキストエディタの基本操作
- Gitの基本コマンド(
git init、git add、git commit、git log)の理解 - ブランチの基本操作(
git branch、git switch)の理解
Gitのバージョンは以下のコマンドで確認できます。
|
|
git mergeコマンドの基本
マージとは何か
マージとは、異なるブランチで行われた変更を統合し、一つの履歴にまとめる操作です。例えば、featureブランチで開発した新機能をmainブランチに取り込む場合にマージを使用します。
gitGraph commit id: "C1" commit id: "C2" branch feature commit id: "C4" commit id: "C5" checkout main commit id: "C3" merge feature id: "C6" tag: "マージコミット"
git mergeの基本構文
git mergeコマンドの基本構文は以下のとおりです。
|
|
このコマンドは、現在チェックアウトしているブランチに、指定したブランチの変更を統合します。
基本的なマージの実行例を見てみましょう。
|
|
実行結果の例(Fast-forwardマージの場合):
|
|
実行結果の例(3-wayマージの場合):
|
|
Fast-forwardマージの仕組み
Fast-forwardマージとは
Fast-forwardマージは、マージ先のブランチがマージ元のブランチの直接の祖先である場合に発生する、最もシンプルなマージ方式です。この場合、Gitは単にブランチポインタを前進させるだけで、新しいマージコミットは作成されません。
gitGraph commit id: "C1" commit id: "C2" branch feature commit id: "C3" commit id: "C4" tag: "main, feature (マージ後)"
上図はFast-forwardマージ後の状態を示しています。mainのポインタがfeatureの先端まで「早送り」されます。
mainブランチからfeatureブランチが分岐した後、mainには新しいコミットが追加されていないため、mainのポインタをfeatureの先端まで「早送り(Fast-forward)」するだけでマージが完了します。
Fast-forwardマージの実践
実際にFast-forwardマージを体験してみましょう。
|
|
実行結果:
|
|
「Fast-forward」と表示されていることから、ポインタの早送りによるマージが行われたことがわかります。
マージ後の履歴確認
マージ後の履歴を確認してみましょう。
|
|
実行結果:
|
|
Fast-forwardマージでは、mainとfeatureの両方が同じコミットを指しており、マージコミットは作成されていません。履歴が一直線に保たれています。
Fast-forwardマージを無効にする
Fast-forwardマージが可能な場合でも、明示的にマージコミットを作成したい場合があります。ブランチの統合履歴を明確に残したい場合に有用です。
|
|
--no-ffオプションを使用すると、Fast-forwardが可能な場合でも強制的にマージコミットが作成されます。
|
|
3-wayマージの仕組み
3-wayマージとは
3-wayマージは、マージ先とマージ元の両方のブランチにそれぞれコミットが存在する場合に発生するマージ方式です。Gitは以下の3つのスナップショットを使用してマージを行います。
- 共通の祖先(Base): 両方のブランチが分岐した時点のコミット
- 現在のブランチ(Ours): マージ先のブランチの最新コミット
- マージ対象のブランチ(Theirs): マージ元のブランチの最新コミット
gitGraph commit id: "C1" commit id: "C2" tag: "共通祖先" branch feature commit id: "C5" commit id: "C6" checkout main commit id: "C3" commit id: "C4" merge feature id: "C7" tag: "マージコミット"
Gitは共通の祖先(C2)を基準として、両方のブランチで行われた変更を分析し、それらを統合した新しいマージコミット(C7)を作成します。
3-wayマージの実践
3-wayマージを体験してみましょう。
|
|
実行結果:
|
|
両方のブランチに独自のコミットが存在することがわかります。この状態でマージを実行します。
|
|
エディタが開き、マージコミットのメッセージを編集できます。デフォルトのメッセージを使用する場合は、そのまま保存して閉じます。
実行結果:
|
|
マージコミットの確認
マージ後の履歴を確認してみましょう。
|
|
実行結果:
|
|
マージコミット(c4d5e6f)が作成され、2つの親コミットを持っていることがグラフから確認できます。
マージコミットの詳細を確認してみましょう。
|
|
実行結果の例:
|
|
Merge: b2c3d4e a1b2c3dという行から、このコミットが2つの親を持つマージコミットであることがわかります。
マージコミットの理解
マージコミットとは
マージコミットは、2つ以上の親コミットを持つ特別なコミットです。通常のコミットは1つの親を持ちますが、マージコミットは統合された複数のブランチの履歴を表現するために複数の親を持ちます。
flowchart TB
subgraph normal["通常のコミット"]
C3_n[C3] --> C4_n["C4 (1つの親)"]
end
subgraph merge["マージコミット"]
C4_m["C4 (親1)"] --> C7["C7 (2つの親)"]
C6_m["C6 (親2)"] --> C7
endマージコミットの親を確認する
マージコミットの親コミットは、以下のコマンドで確認できます。
|
|
または、git logで親のハッシュ値を表示することもできます。
|
|
マージコミットのメッセージ
マージコミットには、デフォルトで「Merge branch ‘ブランチ名’」というメッセージが設定されます。-mオプションでカスタムメッセージを指定することも可能です。
|
|
チームで開発する際は、マージコミットのメッセージに「何をマージしたか」「なぜマージしたか」を記録しておくと、後から履歴を追跡しやすくなります。
マージコンフリクトの発生原因
コンフリクトとは
マージコンフリクト(競合)は、異なるブランチで同じファイルの同じ箇所を異なる内容に変更した場合に発生します。Gitは、どちらの変更を採用すべきか自動的に判断できないため、開発者に手動での解決を求めます。
flowchart LR
Base["共通祖先<br>10行目は 'X'"] --> Main["mainブランチ<br>10行目を 'A' に変更"]
Base --> Feature["featureブランチ<br>10行目を 'B' に変更"]
Main --> Conflict{"コンフリクト発生<br>Gitは 'A' と 'B' の<br>どちらを採用すべきか<br>判断できない"}
Feature --> Conflictコンフリクトが発生するパターン
以下のようなケースでコンフリクトが発生します。
- 同じ行の変更: 両方のブランチで同じファイルの同じ行を編集した場合
- 一方で編集、他方で削除: 片方のブランチでファイルや行を削除し、もう片方で編集した場合
- ファイルの移動と編集: 片方でファイルを移動(リネーム)し、もう片方で同じファイルを編集した場合
一方、以下のケースではコンフリクトは発生しません。
- 異なるファイルを編集した場合
- 同じファイルでも異なる箇所を編集した場合
- 両方のブランチで全く同じ変更を行った場合
コンフリクトの再現
実際にコンフリクトを発生させてみましょう。
|
|
実行結果:
|
|
コンフリクトが発生し、マージが中断されました。
マージコンフリクトの解消手順
ステップ1: コンフリクトの状態を確認する
まず、git statusでコンフリクトの状態を確認します。
|
|
実行結果:
|
|
both modifiedと表示されているファイルがコンフリクトしているファイルです。
ステップ2: コンフリクトマーカーを確認する
コンフリクトが発生したファイルを開くと、Gitが挿入したコンフリクトマーカーが確認できます。
|
|
実行結果:
|
|
コンフリクトマーカーの構造は以下のとおりです。
| マーカー | 説明 |
|---|---|
<<<<<<< HEAD |
現在のブランチ(マージ先)の変更開始 |
======= |
区切り線 |
>>>>>>> feature |
マージ元ブランチの変更終了 |
=======より上が現在のブランチ(main)の内容、下がマージ対象ブランチ(feature)の内容です。
ステップ3: コンフリクトを解決する
コンフリクトを解決するには、以下のいずれかの方法を選択します。
- 現在のブランチの変更を採用する
- マージ元ブランチの変更を採用する
- 両方の変更を組み合わせる
- 全く新しい内容に書き換える
今回は、両方の変更を組み合わせた新しい内容に修正してみましょう。
|
|
コンフリクトマーカー(<<<<<<<、=======、>>>>>>>)は全て削除し、最終的に残したい内容のみを記述します。
ステップ4: 解決したファイルをステージングする
修正が完了したら、git addでファイルをステージングします。これにより、Gitはコンフリクトが解決されたと認識します。
|
|
再度git statusで状態を確認します。
|
|
実行結果:
|
|
「All conflicts fixed」と表示され、コンフリクトが解決されたことがわかります。
ステップ5: マージコミットを完了する
最後に、git commitでマージコミットを完了します。
|
|
エディタが開き、マージコミットのメッセージを編集できます。デフォルトのメッセージには、コンフリクトが発生したファイルの情報が含まれています。
|
|
保存して閉じると、マージが完了します。
|
|
実行結果:
|
|
コンフリクト解消の便利なコマンド
git merge –abort: マージを中止する
コンフリクトの解決が難しい場合や、マージを最初からやり直したい場合は、git merge --abortでマージを中止できます。
|
|
このコマンドを実行すると、マージ開始前の状態に戻ります。
git checkout –ours / –theirs: 一方の変更を採用する
コンフリクトが発生した際、特定のファイルについて一方のブランチの変更のみを採用したい場合は、以下のコマンドが便利です。
|
|
git diff: コンフリクトの詳細を確認する
git diffコマンドで、コンフリクトの詳細を確認できます。
|
|
VS Codeでのコンフリクト解消
VS Codeでは、コンフリクトマーカーの上にボタンが表示され、ワンクリックで解決方法を選択できます。
- Accept Current Change: 現在のブランチの変更を採用
- Accept Incoming Change: マージ元ブランチの変更を採用
- Accept Both Changes: 両方の変更を順番に採用
- Compare Changes: 変更を並べて比較
VS Codeのソース管理パネルからも、マージコンフリクトの状態を視覚的に確認できます。
マージのベストプラクティス
マージ前の準備
マージを実行する前に、以下の点を確認しましょう。
- 作業中の変更をコミットまたはスタッシュする
|
|
- 最新の状態を取得する
|
|
- マージ対象のブランチを確認する
|
|
マージ戦略の選択
プロジェクトやチームの方針に応じて、適切なマージ戦略を選択しましょう。
| 戦略 | コマンド | 使用場面 |
|---|---|---|
| Fast-forward(デフォルト) | git merge feature |
履歴をシンプルに保ちたい場合 |
| No Fast-forward | git merge --no-ff feature |
マージの履歴を明示的に残したい場合 |
| Squashマージ | git merge --squash feature |
複数のコミットを1つにまとめたい場合 |
コンフリクト予防のヒント
コンフリクトを完全に防ぐことはできませんが、以下の工夫で頻度を減らせます。
- こまめにメインブランチの変更を取り込む: 長期間分岐したままにせず、定期的に
git merge mainを実行する - 小さな単位でコミット・マージする: 大きな変更は分割して段階的にマージする
- ファイルの責任範囲を明確にする: チーム内で担当ファイルを分けることで、同じファイルへの同時編集を減らす
- コミュニケーションを取る: 同じファイルを編集する予定がある場合は、事前にチームメンバーと調整する
マージに関するよくある問題と対処法
Already up to date と表示される
|
|
このメッセージは、マージ対象のブランチの変更がすでに現在のブランチに含まれていることを意味します。
|
|
マージを取り消したい
マージコミットを取り消すには、git resetまたはgit revertを使用します。
|
|
-m 1オプションは、マージの最初の親(マージ先のブランチ)の状態に戻すことを指定します。
コンフリクトマーカーが残ったままコミットしてしまった
コンフリクトマーカーが残ったままコミットした場合は、ファイルを修正して新しいコミットを作成します。
|
|
まとめ
本記事では、Gitのマージ機能について体系的に解説しました。
- git mergeの基本:
git merge <ブランチ名>で現在のブランチに変更を統合する - Fast-forwardマージ: 履歴が一直線の場合、ポインタを早送りするだけで完了する
- 3-wayマージ: 両方のブランチに変更がある場合、共通祖先を基準に統合しマージコミットを作成する
- マージコミット: 複数の親を持つ特別なコミットで、ブランチの統合履歴を表現する
- コンフリクトの発生: 同じファイルの同じ箇所を異なる内容に変更した場合に発生する
- コンフリクトの解消: コンフリクトマーカーを確認し、手動で修正後
git addとgit commitで完了する
マージは日常的に使用するGitの重要な操作です。Fast-forwardマージと3-wayマージの違いを理解し、コンフリクトが発生しても落ち着いて対処できるようになれば、チーム開発がよりスムーズに進められるようになります。
次のステップとして、git rebaseを使った履歴の整理方法を学ぶことで、より高度なブランチ管理ができるようになります。