はじめに

ブランチを使った並行開発では、最終的に変更内容を統合する「マージ」が必要になります。しかし、マージ時に「コンフリクト(競合)」が発生して戸惑った経験はありませんか。

本記事では、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推奨

前提条件として、以下の知識があることを想定しています。

  • コマンドライン操作の基礎知識(cdls/dirmkdir等)
  • テキストエディタの基本操作
  • Gitの基本コマンド(git initgit addgit commitgit log)の理解
  • ブランチの基本操作(git branchgit switch)の理解

Gitのバージョンは以下のコマンドで確認できます。

1
git --version

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コマンドの基本構文は以下のとおりです。

1
git merge <ブランチ名>

このコマンドは、現在チェックアウトしているブランチに、指定したブランチの変更を統合します。

基本的なマージの実行例を見てみましょう。

1
2
3
4
5
# mainブランチに切り替え
git switch main

# featureブランチをmainにマージ
git merge feature

実行結果の例(Fast-forwardマージの場合):

1
2
3
4
Updating a1b2c3d..e4f5g6h
Fast-forward
 index.html | 5 +++++
 1 file changed, 5 insertions(+)

実行結果の例(3-wayマージの場合):

1
2
3
4
Merge made by the 'ort' strategy.
 index.html | 3 +++
 style.css  | 10 ++++++++++
 2 files changed, 13 insertions(+)

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マージを体験してみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 練習用ディレクトリを作成して移動
mkdir git-merge-practice
cd git-merge-practice

# Gitリポジトリを初期化
git init

# 最初のファイルを作成してコミット
echo "# My Project" > README.md
git add README.md
git commit -m "Initial commit"

# featureブランチを作成して切り替え
git switch -c feature

# featureブランチで変更を加える
echo "新機能を追加しました" >> README.md
git add README.md
git commit -m "Add new feature description"

# さらに変更を追加
echo "## 機能一覧" >> README.md
git add README.md
git commit -m "Add feature list section"

# mainブランチに戻る
git switch main

# featureブランチをマージ
git merge feature

実行結果:

1
2
3
4
Updating a1b2c3d..e4f5g6h
Fast-forward
 README.md | 2 ++
 1 file changed, 2 insertions(+)

「Fast-forward」と表示されていることから、ポインタの早送りによるマージが行われたことがわかります。

マージ後の履歴確認

マージ後の履歴を確認してみましょう。

1
git log --oneline --graph

実行結果:

1
2
3
* e4f5g6h (HEAD -> main, feature) Add feature list section
* c3d4e5f Add new feature description
* a1b2c3d Initial commit

Fast-forwardマージでは、mainfeatureの両方が同じコミットを指しており、マージコミットは作成されていません。履歴が一直線に保たれています。

Fast-forwardマージを無効にする

Fast-forwardマージが可能な場合でも、明示的にマージコミットを作成したい場合があります。ブランチの統合履歴を明確に残したい場合に有用です。

1
git merge --no-ff feature

--no-ffオプションを使用すると、Fast-forwardが可能な場合でも強制的にマージコミットが作成されます。

1
2
3
4
5
6
*   f6g7h8i (HEAD -> main) Merge branch 'feature'
|\
| * e4f5g6h (feature) Add feature list section
| * c3d4e5f Add new feature description
|/
* a1b2c3d Initial commit

3-wayマージの仕組み

3-wayマージとは

3-wayマージは、マージ先とマージ元の両方のブランチにそれぞれコミットが存在する場合に発生するマージ方式です。Gitは以下の3つのスナップショットを使用してマージを行います。

  1. 共通の祖先(Base): 両方のブランチが分岐した時点のコミット
  2. 現在のブランチ(Ours): マージ先のブランチの最新コミット
  3. マージ対象のブランチ(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マージを体験してみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 新しい練習用ディレクトリを作成
mkdir git-3way-merge-practice
cd git-3way-merge-practice
git init

# 最初のファイルを作成
echo "# プロジェクト概要" > README.md
echo "これはサンプルプロジェクトです。" >> README.md
git add README.md
git commit -m "Initial commit"

# featureブランチを作成して切り替え
git switch -c feature

# featureブランチで変更を加える
echo "" >> README.md
echo "## 新機能" >> README.md
echo "- 機能A: データ処理の高速化" >> README.md
git add README.md
git commit -m "Add feature section"

# mainブランチに戻って別の変更を加える
git switch main
echo "" >> README.md
echo "## 連絡先" >> README.md
echo "support@example.com" >> README.md
git add README.md
git commit -m "Add contact section"

# 現在の状態を確認
git log --oneline --graph --all

実行結果:

1
2
3
4
* b2c3d4e (HEAD -> main) Add contact section
| * a1b2c3d (feature) Add feature section
|/
* 9a8b7c6 Initial commit

両方のブランチに独自のコミットが存在することがわかります。この状態でマージを実行します。

1
git merge feature

エディタが開き、マージコミットのメッセージを編集できます。デフォルトのメッセージを使用する場合は、そのまま保存して閉じます。

実行結果:

1
2
3
Merge made by the 'ort' strategy.
 README.md | 4 ++++
 1 file changed, 4 insertions(+)

マージコミットの確認

マージ後の履歴を確認してみましょう。

1
git log --oneline --graph

実行結果:

1
2
3
4
5
6
*   c4d5e6f (HEAD -> main) Merge branch 'feature'
|\
| * a1b2c3d (feature) Add feature section
* | b2c3d4e Add contact section
|/
* 9a8b7c6 Initial commit

マージコミット(c4d5e6f)が作成され、2つの親コミットを持っていることがグラフから確認できます。

マージコミットの詳細を確認してみましょう。

1
git show --stat c4d5e6f

実行結果の例:

1
2
3
4
5
6
7
8
9
commit c4d5e6f...
Merge: b2c3d4e a1b2c3d
Author: Your Name <your.email@example.com>
Date:   Wed Jan 1 19:00:00 2026 +0900

    Merge branch 'feature'

 README.md | 4 ++++
 1 file changed, 4 insertions(+)

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

マージコミットの親を確認する

マージコミットの親コミットは、以下のコマンドで確認できます。

1
2
3
4
5
# 最初の親(マージ先のブランチ)
git show HEAD^1 --oneline

# 2番目の親(マージ元のブランチ)
git show HEAD^2 --oneline

または、git logで親のハッシュ値を表示することもできます。

1
git log -1 --format="%P"

マージコミットのメッセージ

マージコミットには、デフォルトで「Merge branch ‘ブランチ名’」というメッセージが設定されます。-mオプションでカスタムメッセージを指定することも可能です。

1
git merge feature -m "feature: ユーザー認証機能を統合"

チームで開発する際は、マージコミットのメッセージに「何をマージしたか」「なぜマージしたか」を記録しておくと、後から履歴を追跡しやすくなります。

マージコンフリクトの発生原因

コンフリクトとは

マージコンフリクト(競合)は、異なるブランチで同じファイルの同じ箇所を異なる内容に変更した場合に発生します。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. 同じ行の変更: 両方のブランチで同じファイルの同じ行を編集した場合
  2. 一方で編集、他方で削除: 片方のブランチでファイルや行を削除し、もう片方で編集した場合
  3. ファイルの移動と編集: 片方でファイルを移動(リネーム)し、もう片方で同じファイルを編集した場合

一方、以下のケースではコンフリクトは発生しません。

  • 異なるファイルを編集した場合
  • 同じファイルでも異なる箇所を編集した場合
  • 両方のブランチで全く同じ変更を行った場合

コンフリクトの再現

実際にコンフリクトを発生させてみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 新しい練習用ディレクトリを作成
mkdir git-conflict-practice
cd git-conflict-practice
git init

# 最初のファイルを作成
cat << 'EOF' > greeting.txt
おはようございます。
今日も良い一日を。
EOF
git add greeting.txt
git commit -m "Initial commit"

# featureブランチを作成して変更
git switch -c feature
cat << 'EOF' > greeting.txt
こんにちは。
今日も良い一日を。
EOF
git add greeting.txt
git commit -m "Change greeting to afternoon"

# mainブランチに戻って別の変更
git switch main
cat << 'EOF' > greeting.txt
おはよう!
今日も良い一日を。
EOF
git add greeting.txt
git commit -m "Make greeting more casual"

# マージを試みる
git merge feature

実行結果:

1
2
3
Auto-merging greeting.txt
CONFLICT (content): Merge conflict in greeting.txt
Automatic merge failed; fix conflicts and then commit the result.

コンフリクトが発生し、マージが中断されました。

マージコンフリクトの解消手順

ステップ1: コンフリクトの状態を確認する

まず、git statusでコンフリクトの状態を確認します。

1
git status

実行結果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both modified:   greeting.txt

no changes added to commit (use "git add" and/or "git commit -a")

both modifiedと表示されているファイルがコンフリクトしているファイルです。

ステップ2: コンフリクトマーカーを確認する

コンフリクトが発生したファイルを開くと、Gitが挿入したコンフリクトマーカーが確認できます。

1
cat greeting.txt

実行結果:

1
2
3
4
5
6
<<<<<<< HEAD
おはよう!
=======
こんにちは。
>>>>>>> feature
今日も良い一日を。

コンフリクトマーカーの構造は以下のとおりです。

マーカー 説明
<<<<<<< HEAD 現在のブランチ(マージ先)の変更開始
======= 区切り線
>>>>>>> feature マージ元ブランチの変更終了

=======より上が現在のブランチ(main)の内容、下がマージ対象ブランチ(feature)の内容です。

ステップ3: コンフリクトを解決する

コンフリクトを解決するには、以下のいずれかの方法を選択します。

  1. 現在のブランチの変更を採用する
  2. マージ元ブランチの変更を採用する
  3. 両方の変更を組み合わせる
  4. 全く新しい内容に書き換える

今回は、両方の変更を組み合わせた新しい内容に修正してみましょう。

1
2
3
4
cat << 'EOF' > greeting.txt
こんにちは!
今日も良い一日を。
EOF

コンフリクトマーカー(<<<<<<<=======>>>>>>>)は全て削除し、最終的に残したい内容のみを記述します。

ステップ4: 解決したファイルをステージングする

修正が完了したら、git addでファイルをステージングします。これにより、Gitはコンフリクトが解決されたと認識します。

1
git add greeting.txt

再度git statusで状態を確認します。

1
git status

実行結果:

1
2
3
4
5
6
On branch main
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:
        modified:   greeting.txt

「All conflicts fixed」と表示され、コンフリクトが解決されたことがわかります。

ステップ5: マージコミットを完了する

最後に、git commitでマージコミットを完了します。

1
git commit

エディタが開き、マージコミットのメッセージを編集できます。デフォルトのメッセージには、コンフリクトが発生したファイルの情報が含まれています。

1
2
3
4
Merge branch 'feature'

# Conflicts:
#       greeting.txt

保存して閉じると、マージが完了します。

1
git log --oneline --graph

実行結果:

1
2
3
4
5
6
*   d5e6f7g (HEAD -> main) Merge branch 'feature'
|\
| * b3c4d5e (feature) Change greeting to afternoon
* | a2b3c4d Make greeting more casual
|/
* 9a8b7c6 Initial commit

コンフリクト解消の便利なコマンド

git merge –abort: マージを中止する

コンフリクトの解決が難しい場合や、マージを最初からやり直したい場合は、git merge --abortでマージを中止できます。

1
git merge --abort

このコマンドを実行すると、マージ開始前の状態に戻ります。

git checkout –ours / –theirs: 一方の変更を採用する

コンフリクトが発生した際、特定のファイルについて一方のブランチの変更のみを採用したい場合は、以下のコマンドが便利です。

1
2
3
4
5
6
7
# 現在のブランチ(マージ先)の変更を採用
git checkout --ours greeting.txt
git add greeting.txt

# マージ元ブランチの変更を採用
git checkout --theirs greeting.txt
git add greeting.txt

git diff: コンフリクトの詳細を確認する

git diffコマンドで、コンフリクトの詳細を確認できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# コンフリクトの状態を確認
git diff

# 共通祖先との差分を確認
git diff --base

# 現在のブランチとの差分を確認
git diff --ours

# マージ元ブランチとの差分を確認
git diff --theirs

VS Codeでのコンフリクト解消

VS Codeでは、コンフリクトマーカーの上にボタンが表示され、ワンクリックで解決方法を選択できます。

  • Accept Current Change: 現在のブランチの変更を採用
  • Accept Incoming Change: マージ元ブランチの変更を採用
  • Accept Both Changes: 両方の変更を順番に採用
  • Compare Changes: 変更を並べて比較

VS Codeのソース管理パネルからも、マージコンフリクトの状態を視覚的に確認できます。

マージのベストプラクティス

マージ前の準備

マージを実行する前に、以下の点を確認しましょう。

  1. 作業中の変更をコミットまたはスタッシュする
1
2
3
4
5
6
# 変更をコミット
git add .
git commit -m "WIP: 作業中の変更を保存"

# または一時的に退避
git stash
  1. 最新の状態を取得する
1
2
3
4
5
6
# リモートの変更を取得
git fetch origin

# mainブランチを最新に更新
git switch main
git pull origin main
  1. マージ対象のブランチを確認する
1
2
# ブランチの一覧と履歴を確認
git log --oneline --graph --all

マージ戦略の選択

プロジェクトやチームの方針に応じて、適切なマージ戦略を選択しましょう。

戦略 コマンド 使用場面
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 と表示される

1
Already up to date.

このメッセージは、マージ対象のブランチの変更がすでに現在のブランチに含まれていることを意味します。

1
2
# 履歴を確認
git log --oneline --graph --all

マージを取り消したい

マージコミットを取り消すには、git resetまたはgit revertを使用します。

1
2
3
4
5
# マージコミットを直前の状態に戻す(履歴を書き換える)
git reset --hard HEAD~1

# マージを打ち消すコミットを作成(履歴を保持)
git revert -m 1 HEAD

-m 1オプションは、マージの最初の親(マージ先のブランチ)の状態に戻すことを指定します。

コンフリクトマーカーが残ったままコミットしてしまった

コンフリクトマーカーが残ったままコミットした場合は、ファイルを修正して新しいコミットを作成します。

1
2
3
4
5
6
# ファイルを修正してコンフリクトマーカーを削除
vim greeting.txt

# 修正をコミット
git add greeting.txt
git commit -m "Fix: コンフリクトマーカーを削除"

まとめ

本記事では、Gitのマージ機能について体系的に解説しました。

  • git mergeの基本: git merge <ブランチ名>で現在のブランチに変更を統合する
  • Fast-forwardマージ: 履歴が一直線の場合、ポインタを早送りするだけで完了する
  • 3-wayマージ: 両方のブランチに変更がある場合、共通祖先を基準に統合しマージコミットを作成する
  • マージコミット: 複数の親を持つ特別なコミットで、ブランチの統合履歴を表現する
  • コンフリクトの発生: 同じファイルの同じ箇所を異なる内容に変更した場合に発生する
  • コンフリクトの解消: コンフリクトマーカーを確認し、手動で修正後git addgit commitで完了する

マージは日常的に使用するGitの重要な操作です。Fast-forwardマージと3-wayマージの違いを理解し、コンフリクトが発生しても落ち着いて対処できるようになれば、チーム開発がよりスムーズに進められるようになります。

次のステップとして、git rebaseを使った履歴の整理方法を学ぶことで、より高度なブランチ管理ができるようになります。

参考リンク