💥

Git コンフリクトの仕組みを理解する

に公開

はじめに

git のコンフリクトについて、「なんとなくは理解している」という人(何を隠そうこの私である)という方々を対象に抽象的な理解から具体的にマージとコンフリクトの仕組み・動きを学べる記事となっています。

ゴール

  • gitコンフリクトの発生メカニズムを理解する
  • マージ時の差分比較と共通祖先の概念を理解する
  • 確認用のコマンドを習得する

読んでほしい層

  • git の基本操作はできるが、マージの仕組みに不安がある初中級者
  • 「なぜコンフリクトが起こるのか」を理論的に理解したいエンジニア

サンプルブランチの構成について

今回はサンプルファイルとしてREADME.mdファイルを複数のブランチで編集という想定で説明します。
登場するブランチは2つです。

$ git branch

develop
feature/a

developブランチ

統合用のブランチ

$ git log --graph --decorate --oneline

* 625b3ed (HEAD -> develop) D-6: develop に機能 D-6 を追加
* 5f722b9 D-5: develop に機能 D-5 を追加
* 91f979f D-4: develop に機能 D-4 を追加
* 12f19b7 D-3: develop に機能 D-3 を追加
* 49f01be D-2: develop に機能 D-2 を追加
* 66310fc D-1: develop に機能 D-1 を追加
* 0a159da (main) 初期コミット

初期コミット → D-1 → D-2 → D-3の順番でコミットが存在しています。

$ cat README.md

初期コミット
追加: develop ブランチの変更 D-1
追加: develop ブランチの変更 D-2
追加: develop ブランチの変更 D-3
追加: develop ブランチの変更 D-4
追加: develop ブランチの変更 D-5
追加: develop ブランチの変更 D-6

feature/aブランチ

作業用のブランチ

$ git log --graph --decorate --oneline

* e38e3bc (HEAD -> feature/a) A-3: feature/a に機能 A-3 を追加
* 319e844 A-2: feature/a に機能 A-2 を追加
* 8ee465c A-1: feature/a に機能 A-1 を追加
* 12f19b7 D-3: develop に機能 D-3 を追加
* 49f01be D-2: develop に機能 D-2 を追加
* 66310fc D-1: develop に機能 D-1 を追加
* 0a159da (main) 初期コミット

developがD3の状態で作成されたブランチでfeature/aでA1 → A2 → A3の順番でコミットが存在しています。

$ cat README.md 
初期コミット
追加: develop ブランチの変更 D-1
追加: develop ブランチの変更 D-2
追加: develop ブランチの変更 D-3
追加: feature/a ブランチの変更 A-1
追加: feature/a ブランチの変更 A-2
追加: feature/a ブランチの変更 A-3

全てのコミットを確認するには次のコマンドを実行

git log --graph --oneline --decorate --all

* 625b3ed (HEAD -> develop) D-6: develop に機能 D-6 を追加
* 5f722b9 D-5: develop に機能 D-5 を追加
* 91f979f D-4: develop に機能 D-4 を追加
| * e38e3bc (feature/a) A-3: feature/a に機能 A-3 を追加
| * 319e844 A-2: feature/a に機能 A-2 を追加
| * 8ee465c A-1: feature/a に機能 A-1 を追加
|/  
* 12f19b7 D-3: develop に機能 D-3 を追加
* 49f01be D-2: develop に機能 D-2 を追加
* 66310fc D-1: develop に機能 D-1 を追加
* 0a159da (main) 初期コミット

コンフリクトについておさらい

コンフリクト(衝突)とは、マージ対象のコミット同士で変更箇所が重複している、あるいは削除操作と編集操作の両方が同じファイルや行に対して行われ、git側でどちらの操作(差分)を採用すべきか自動的に判断できない場合に発生します。

マージの仕組み

git がマージを行う際、どのような差分を基に変更を取り込むかを判断するために、次の 3 つのスナップショット(状態)を参照します。これにより、変更点の比較と統合を正確に行うことができます。

  • 共通祖先 (merge base)
  • マージ先ブランチの先端
  • マージ元ブランチの先端

1.共通祖先 (merge base)

$ git merge-base develop feature/a

12f19b7017d6557e31dad665af7d713898b41bbc

コミットのハッシュ値が表示されます。コミットのハッシュ値は次のコマンドで確認することができます。
git show <hash> --stat

$ git show 12f19b7017d6557e31dad665af7d713898b41bbc --stat

commit 12f19b7017d6557e31dad665af7d713898b41bbc
Author: xxxx <xxxxx@mail.com>
Date:   Sun Jun 1 20:14:22 2025 +0900

    D-3: develop に機能 D-3 を追加

README.md | 1 +
1 file changed, 1 insertion(+)

2. マージ先ブランチの先端

次のコマンドで確認することができます。

$ git rev-parse develop
$ git log -1 develop

3. マージ元ブランチの先端

git rev-parse feature/a
git log -1 feature/a

マージ対象コミットの一覧表示
共通祖先を除くコミットの差異がマージ対象となります。次のコマンドでそれを確認することができます。
develop側を確認する

$ git log --oneline $(git merge-base develop feature/a)..develop

625b3ed (HEAD -> develop) D-6: develop に機能 D-6 を追加
5f722b9 D-5: develop に機能 D-5 を追加
91f979f D-4: develop に機能 D-4 を追加

feature/a側を確認する

git log --oneline $(git merge-base develop feature/a)..feature/a

e38e3bc (feature/a) A-3: feature/a に機能 A-3 を追加
319e844 A-2: feature/a に機能 A-2 を追加
8ee465c A-1: feature/a に機能 A-1 を追加

表示されている6つのコミットがマージ対象のコミットとなります。

次のコマンドでマージの際の具体的な突き合わせを行う差分を確認することができます。

$ git diff $(git merge-base develop feature/a) feature/a
diff --git a/README.md b/README.md
index 3059e22..8a9da34 100644
--- a/README.md
+++ b/README.md
@@ -2,3 +2,6 @@
 追加: develop ブランチの変更 D-1
 追加: develop ブランチの変更 D-2
 追加: develop ブランチの変更 D-3
+追加: feature/a ブランチの変更 A-1
+追加: feature/a ブランチの変更 A-2
+追加: feature/a ブランチの変更 A-3

この内容をgitがまずは自動マージを行い、解決できなかった場合にコンフリクトが発生します。

マージ・コンフリクト実行

2つのブランチ間のマージ対象のコミットが分かったところで実際にマージを実行してみましょう。

$ git merge feature/a

Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result

先述した2と3の差分により、README.mdでコンフリクトが発生したことが分かります。

$ cat README.md

初期コミット
追加: develop ブランチの変更 D-1
追加: develop ブランチの変更 D-2
追加: develop ブランチの変更 D-3
<<<<<<< HEAD
追加: develop ブランチの変更 D-4
追加: develop ブランチの変更 D-5
追加: develop ブランチの変更 D-6
=======
追加: feature/a ブランチの変更 A-1
追加: feature/a ブランチの変更 A-2
追加: feature/a ブランチの変更 A-3
>>>>>>> feature/a

<<<<<<< ======= >>>>>>>を削除し解決しましょう。
解決が完了するとコミットしましょう。

git add README.md
git commit -m "マージ"

マージを含めたコミット履歴を確認してみましょう。

$ git log --graph --oneline --decorate --all

*   23bef4a (HEAD -> develop) マージ
|\  
| * e38e3bc (feature/a) A-3: feature/a に機能 A-3 を追加
| * 319e844 A-2: feature/a に機能 A-2 を追加
| * 8ee465c A-1: feature/a に機能 A-1 を追加
* | 625b3ed D-6: develop に機能 D-6 を追加
* | 5f722b9 D-5: develop に機能 D-5 を追加
* | 91f979f D-4: develop に機能 D-4 を追加
|/  
* 12f19b7 D-3: develop に機能 D-3 を追加
* 49f01be D-2: develop に機能 D-2 を追加
* 66310fc D-1: develop に機能 D-1 を追加
* 0a159da (main) 初期コミット

まとめ

マージとは、2つのブランチ間の3つのスナップショット(状態)を参照して行われます。
スナップショット間でgitが自動的に重複差分のどちらを採用すべきか判断できない時にコンフリクトが発生します。git merge-base等のコマンドでそれぞれ対象となる差分を確認することができる。

最後に

基本的なマージ・コンフリクトについて今回学習できました。
これを機にrebase周りもぜひマスターしていきたいと思います!

Discussion