LASSIC Media らしくメディア
Gitのコンフリクト解消手順|起きる原因と防ぎ方
監修・編集責任者:牛尾 昭昌(株式会社LASSIC 執行役員)
この記事の結論
- gitのコンフリクトは、2つのブランチが同じ箇所を別々に変え、Gitが片方を選べないときに起きます。
- 解消は、マーカーを読んで残す内容を決め、書き直して git add し、コミットする流れです。
- rebase 中の ours と theirs の入れ替わりと、マーカーの消し忘れに注意します。
※ 本記事は2026年10月時点の公式情報(省庁・公的機関、および製品やサービスを提供する事業者が公開している資料)に基づきます。
複数人で開発していると、git merge や git pull の途中で CONFLICT と表示され、ファイルに <<<<<<< のような記号が書き込まれることがあります。これがgitのコンフリクト(競合)です。Gitが自動で統合できなかった箇所について、人の判断を待って止まった状態を指します。
本記事では、システムの開発・保守の担当者に向けて、コンフリクトが起きる仕組み、マーカーの読み方、解消の手順、つまずきやすい点と減らし方を、Git 2.55で実際に再現した出力とともに整理します。
目次
gitのコンフリクトとは
コンフリクトは、2つのブランチが同じファイルの同じ箇所を別々に変更していて、Gitがどちらを残すべきか決められないときに起きます。GitHubのドキュメントは、同じファイルの同じ行に異なる変更を加えたとき、または一方がファイルを編集し、もう一方が同じファイルを削除したときに起きやすいと説明しています。*2 変更が別の行や別のファイルにあれば、Gitは人の手を借りずに統合します。
コンフリクトは故障ではなく、Gitが片方を勝手に選ばず、判断を人に返している状態です。GitHubでは、プルリクエストにコンフリクトがある間はマージのボタンが無効になり、解消するまでマージできません。*2
コンフリクトが起きる仕組み
Gitのマージは、2つのブランチの先端と、両者が分かれる前の共通の祖先の3つを比べる3方向マージです。祖先から見て片方だけが変えた箇所はそのまま取り込まれ、両方が同じように変えた箇所も問題になりません。両方が同じ箇所を違う内容に変えたときだけ、コンフリクトとして残ります。
コンフリクトが起きると、Gitは途中の状態を残して止まります。HEADは今いるブランチのまま動かず、MERGE_HEADが取り込もうとしたブランチの先端を指します。衝突したファイルについて、インデックスには版が3つまで記録されます。ステージ1が共通の祖先、ステージ2がHEAD、ステージ3がMERGE_HEADの版です。*1
この3つの版は git ls-files -u で一覧でき、git show :1:ファイル名 のように番号を付けると中身を取り出せます。次の節の例では、:1: が timeout = 30、:2: が timeout = 60、:3: が timeout = 45 を返しました。
コンフリクトマーカーの読み方
実際にコンフリクトを起こしてみます。設定ファイル app.ini の timeout = 30 を、main ブランチでは 60 に、feature ブランチでは 45 に変え、どちらも同じ位置に keepalive = on を加えました。main で feature をマージした結果が次のとおりです。実行環境は Git for Windows 2.55.0 の Git Bash です。
$ git merge feature
Auto-merging app.ini
CONFLICT (content): Merge conflict in app.ini
Automatic merge failed; fix conflicts and then commit the result.
$ git status --short
UU app.ini
$ git ls-files -u
100644 28dc16fa09b478f23c402d0f9d8ab1b94d0a0169 1 app.ini
100644 fb38dc3c6e8483848bc72829c32fdfefba6bcb13 2 app.ini
100644 84cf935d55f57aa6b18d82c12f8c6d325335eeb9 3 app.ini
$ cat app.ini
[server]
host = 0.0.0.0
<<<<<<< HEAD
timeout = 60
=======
timeout = 45
>>>>>>> feature
keepalive = on
retries = 3
git merge は CONFLICT (content) と表示し、終了コード1で止まりました。git status –short の UU は、両方が変更したまま未解決(unmerged, both modified)であることを表します。*11 git ls-files -u の行末の1・2・3がステージ番号です。
ファイルの中では、<<<<<<< から ======= までが自分の側(HEAD)、======= から >>>>>>> までが相手の側(feature)の内容です。両側で同じだった keepalive = on は、マーカーで囲まれていません。この既定の形式では、衝突した箇所がもともと何だったかは表示されません。例でいえば、元の値が30だったことはこのファイルからは分かりません。
diff3とzdiff3で元の内容を見る
元の内容を見たいときは、設定 merge.conflictStyle を diff3 か zdiff3 にします。diff3 は ======= の前に ||||||| マーカーと元の内容を加えます。zdiff3 は diff3 に近い形式で、衝突した範囲の先頭や末尾の近くにある、両側で一致した行を範囲から外します。*4 同じマージを diff3 でやり直すと、次のようになりました。
$ git merge --abort
$ git config merge.conflictStyle diff3
$ git merge feature > /dev/null
$ cat app.ini
[server]
host = 0.0.0.0
<<<<<<< HEAD
timeout = 60
keepalive = on
||||||| 8b07868
timeout = 30
=======
timeout = 45
keepalive = on
>>>>>>> feature
retries = 3
||||||| の後ろの 8b07868 は共通の祖先のコミットで、その下の timeout = 30 が元の内容です。元が30で、main は60に、feature は45に変えた、という3つの版の関係が1か所で読めます。一方で diff3 では、両側で同じだった keepalive = on も衝突の範囲に入り、範囲が広がりました。zdiff3 にすると、keepalive = on は再び衝突の範囲から外れ、衝突の範囲は timeout の行だけになりました。
元の内容が見えて範囲も絞れる zdiff3 は、日常の設定として使いやすい形式です。すでにコンフリクトしているファイルは、git checkout –conflict=zdiff3 ファイル名 で表示の形式だけを作り直せます。*5
コンフリクトの解消手順
コンフリクトに気づいたら、取れる道は2つです。マージをやめるなら、git merge –abort でマージ前の状態に戻そうとします。ただし、マージを始める前にコミットしていない変更があった場合は、元の状態に戻せないことがあります。*1 マージの前には、作業中の変更をコミットするか退避しておきます。解消するときの流れは次のとおりです。
- git status で、コンフリクトしたファイルを確かめる
- ファイルを開いて <<<<<<< を探し、片方だけ・もう片方だけ・両方を合わせた新しい内容のどれを残すか決める
- マーカーの行を消し、最終的な内容に書き直す
- git add で解消済みとしてインデックスに登録する
- git commit か git merge –continue でマージを完了する
例では、timeout を main の60に決め、keepalive = on を残す形に書き直して git add しました。片方の版をそのまま採るなら、git checkout –ours ファイル名 か –theirs ファイル名 で、ステージ2かステージ3の版に置き換えられます。*5 置き換えた後も、git add で解消済みにする手順は同じです。
一方のブランチでファイルが削除され、もう一方で編集された場合は、マーカーは付きません。git status に deleted by us などと表示されるので、ファイルを残すなら git add、消すなら git rm で決着させます。*3 衝突したファイルが多いときは、git mergetool で、設定したマージツールをファイルごとに起動できます。既定では、マーカー付きの元のファイルが .orig の拡張子で残ります。*8
つまずきやすい点
1つ目は、rebase のときに ours と theirs が入れ替わることです。rebase は作業中のブランチのコミットを、取り込む先のブランチの上に1つずつ適用し直します。そのため、コンフリクトで ours として示されるのは取り込む先の側で、theirs が自分の作業ブランチです。*6 次の例は、feature を main の上に rebase したときのものです。
# feature を main の上に rebase する(出力は一部を抜粋)
$ git switch feature
$ git rebase main
CONFLICT (content): Merge conflict in app.ini
error: could not apply ba92a9a... feature
$ git checkout --ours app.ini && grep timeout app.ini
Updated 1 path from the index
timeout = 60
$ git checkout --theirs app.ini && grep timeout app.ini
Updated 1 path from the index
timeout = 45
# マーカーを残したまま git add してしまった場合
$ git diff --cached --check
app.ini:3: leftover conflict marker
app.ini:5: leftover conflict marker
app.ini:7: leftover conflict marker
feature で作業していても、–ours で取り出されたのは main の60でした。merge のつもりで –ours を使うと、自分の変更を捨てることになります。
2つ目は、マーカーの消し忘れです。例のとおり、マーカーが残ったファイルでも git add はそのまま通り、解消済みとして扱われます。git diff –check はコンフリクトマーカーや空白の誤りを警告し、問題があれば0以外の終了コードで終わります。*7 例では3つのマーカーの行が検出され、終了コードは2でした。コミット前のフックやCIで走らせておくと、混入を止められます。
3つ目は、改行コードの設定が人によって違うと1行の変更がファイル全体の差分になり、広い範囲でコンフリクトが起きることです。4つ目は、.gitattributes の merge 属性の使い方です。組み込みの union ドライバーはマーカーを残さず両側の行をすべて取り込みますが、追加した行の順序が不定になりやすく、公式ドキュメントも意味を理解せずに使わないよう注意しています。binary ドライバーは自分の側の版を作業ディレクトリに残し、ファイルをコンフリクトの状態のままにします。*10 画像のように手で合わせられないファイルに向きます。
コンフリクトを減らす運用
コンフリクトの回数と規模は、運用で小さくできます。基本は、ブランチを長く分けたままにしないことです。ブランチの運用の考え方は「トランクベース開発へブランチ戦略を移行する外注」で扱っています。
ほかに役立つのは、コードの整形ツールの設定を全員でそろえて整形だけの差分を出さないこと、ファイルの移動や大きな書き換えを機能の変更と別のコミットに分けることです。長く続くブランチで同じコンフリクトを何度も解く場合は、git rerere が役立ちます。最初に手で解いた結果を記録し、同じコンフリクトに再び出会うと、記録した解消を当てはめます。使うには設定 rerere.enabled を有効にします。*9
例のリポジトリで rerere.enabled を true にしてマージを解くと、Recorded resolution for ‘app.ini’. と表示され、そのマージを取り消してやり直すと、Resolved ‘app.ini’ using previous resolution. と表示され、ファイルは前回と同じ内容に直っていました。ただし git status は UU のままでした。設定 rerere.autoUpdate の既定は false で、当てはめた結果をインデックスに登録しないためです。*4 直った内容を確かめてから git add します。
外部に委託するときに確認しておきたい点
開発や保守を外部に頼むと、社内と委託先が同じリポジトリへ並行してコミットする場面が増えます。どちらの変更を残すかはツールでは決まらないため、次の点を先に確かめておきます。
- ブランチの生存期間や、main へ取り込む頻度が決められているか
- コンフリクトが起きたとき、どちらの変更を優先するかを誰が判断するか
- 解消した結果を、元の変更の作成者やレビューの担当者が確かめる手順があるか
- CIで git diff –check などを走らせ、マーカーの混入を検出しているか
- merge.conflictStyle、改行コード、整形ツールの設定をチームでそろえているか
解消の判断を1人に任せきりにすると、相手の意図を知らないまま片方の変更を消してしまう事故が起きやすくなります。衝突した箇所が業務の仕様にかかわるときは、両方の変更の作成者が結果を確かめる運用にしておきます。古いバージョン管理からGitへ移る途中なら「SourceSafeからGit移行」、Gitの基盤を自前で持つなら「GitLab/Gitea セルフホストGit基盤の外注構築」も参考になります。
まとめ:gitのコンフリクトで確かめたい3点
gitのコンフリクトを扱ううえで確かめておきたい点は、3つに整理できます。第一に、コンフリクトは同じ箇所への別々の変更を人に判断させる仕組みで、インデックスには祖先・HEAD・相手の3つの版が残ること。第二に、merge.conflictStyle を zdiff3 などにして元の内容も見ながら残す内容を決め、git add とコミットで完了させること。第三に、rebase 中の ours と theirs の入れ替わり、マーカーの消し忘れ、長く分かれたままのブランチに気をつけることです。
よくある質問
解消の途中で、やり直すことはできますか
マージをコミットする前なら、git merge –abort でマージ前の状態に戻そうとできます。1つのファイルだけ解消をやり直したいときは、git checkout -m ファイル名 で、衝突した直後のマーカー付きの状態に戻せます。
ours と theirs は、どちらが自分の変更ですか
merge では、ours が今いるブランチ(ステージ2)、theirs が取り込むブランチ(ステージ3)です。rebase では入れ替わり、ours が取り込む先のブランチ、theirs が自分の作業ブランチになります。
GitHubの画面でコンフリクトを解消できますか
GitHubのドキュメントによると、同じ行の競合のような単純なものは、GitHubの画面で解消できます。複雑なものは手元のリポジトリで解消し、プルリクエストのブランチへプッシュし直します。
Gitの運用とコンフリクト対策のご相談
元請(プライムベンダー)として、リポジトリの運用ルールの整備からシステムの保守・運用までご提案します。
Remoguとリラシクなら、開発プロセスやCIの整備に加わるITエンジニアも探せます。
Remoguは、リモート前提で全国から即戦力のITプロ人材を調達するサービスです。リラシクは、扱う求人がすべてリモートワークのITエンジニア専門転職エージェントです。どちらもLASSICが運営しています。
出典
- *1 参考:Git「git-merge Documentation」(https://git-scm.com/docs/git-merge)。出典:HOW CONFLICTS ARE PRESENTED(両側が同じ箇所を変えたときの扱い、マーカーの意味、既定の形式では元の内容が表示されないこと)、衝突時のHEAD・MERGE_HEAD・ステージ1〜3、git merge –abort と –continue、HOW TO RESOLVE CONFLICTS を参照(2026年10月確認)
- *2 参考:GitHub Docs「Merge conflicts」(https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/about-merge-conflicts)。出典:コンフリクトが起きやすい2つの場合、コンフリクトがある間はプルリクエストのマージボタンが無効になること、単純な競合はGitHub上で・複雑なものはローカルで解消することを参照(2026年10月確認)
- *3 参考:GitHub Docs「Resolving a merge conflict using the command line」(https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-using-the-command-line)。出典:競合した行の解消手順(git status、マーカーの削除、git add、git commit)と、削除されたファイルの競合を git add か git rm で解消する手順を参照(2026年10月確認)
- *4 参考:Git「git-config Documentation」(https://git-scm.com/docs/git-config)。出典:merge.conflictStyle(merge・diff3・zdiff3 の違い)、rerere.enabled、rerere.autoUpdate(既定は false)を参照(2026年10月確認)
- *5 参考:Git「git-checkout Documentation」(https://git-scm.com/docs/git-checkout)。出典:–ours と –theirs(未解決のパスでステージ2か3を取り出す)、-m、–conflict=