ウェブエンジニア問題集

コンフリクトを恐れない — 発生原因と解決手順

コンフリクト(conflict、競合)は、Git初心者がもっとも怖がる瞬間です。エディタに <<<<<<< なんて記号が並び、「何か壊した気がする」と焦ってしまう。

実際には、コンフリクトは壊れているわけではなく、Gitが「ここは自動判断できないから、人間が決めて」と頼んでいるだけです。落ち着いて読めば必ず解決できます。

コンフリクトが起きる仕組み

コンフリクトは、同じファイルの同じ行を、2つのブランチで違う内容に変更したときに発生します。

たとえば以下のような状況です。

main ブランチ:
  title = "こんにちは";

feature ブランチ:
  title = "Hello";

両方のブランチが、元は title = "Hi"; だった1行をそれぞれ違う内容に変更しました。Gitはこれをmergeするとき、「どちらが正解か判断できない」のでコンフリクトにします。

コンフリクトが起きない変更もある

同じファイルでも、違う行を編集していればコンフリクトしません。Gitは行単位で差分を管理しているので、以下のような場合は自動マージされます。

  • main が1行目を変更、feature が10行目を変更 → コンフリクトしない
  • main が新しい関数を追加、feature が別の関数を修正 → コンフリクトしない

なので「このファイルを両方で触ったからコンフリクトする」とは限りません。実際にやってみるまで分からないのが普通です。

コンフリクトマーカーの読み方

コンフリクトが起きると、Gitはファイルに特殊な記号を書き込んで「ここが衝突しているよ」と教えてくれます。

<<<<<<< HEAD
const title = "こんにちは";
=======
const title = "Hello";
>>>>>>> feature

各記号の意味は以下の通りです。

記号意味
<<<<<<< HEADここから現在のブランチの内容
=======区切り線
>>>>>>> featureここまでが取り込もうとしたブランチ(feature)の内容

つまり、======= の上がHEAD側、下が取り込み側です。

解決の手順

コンフリクトを解決する流れは以下の4ステップです。

ステップ1: どのファイルがコンフリクトしているか確認

git status
On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)
	both modified:   src/app.js

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

ステップ2: ファイルを開いて手動で編集

対象のファイルを開き、コンフリクトマーカーを見ながら正しい内容に直します。マーカーも一緒に削除するのを忘れないでください。

// Before(コンフリクト状態)
<<<<<<< HEAD
const title = "こんにちは";
=======
const title = "Hello";
>>>>>>> feature
 
// After(解決後)
const title = "Hello";

「両方採用したい」ケースもあります。その場合は両方残してマーカーだけ消します。

const titleJa = "こんにちは";
const titleEn = "Hello";

ステップ3: 解決を宣言する

編集が終わったら、そのファイルを git add します。これが「解決しました」の合図です。

git add src/app.js

複数ファイルがコンフリクトしている場合は、全部のファイルを編集して git add します。

ステップ4: コミットしてマージを完了

すべて解決したらコミットします。

# mergeの場合
git commit
 
# rebaseの場合
git rebase --continue

git commit を引数なしで実行すると、Gitが自動で Merge branch 'feature' のようなメッセージを提案してくれます。そのまま保存すればOKです。

解決を間違えたらやり直せる

「編集ミスった、最初からやり直したい」というときは、マージ/rebaseを中止できます。

# mergeを中止
git merge --abort
 
# rebaseを中止
git rebase --abort

これでコンフリクト発生前の状態に戻ります。安心してやり直しましょう。

VS Codeでコンフリクトを解決する

エディタ上でコンフリクトマーカーをいちいち削除するのは面倒です。VS Codeはコンフリクトを自動で検出して、ボタンで選択できるUIを提供してくれます。

コンフリクトしたファイルを開くと、以下のような選択肢が表示されます。

  • Accept Current Change — HEAD側を採用
  • Accept Incoming Change — 取り込み側を採用
  • Accept Both Changes — 両方採用
  • Compare Changes — 差分をサイドバイサイドで比較

ボタンを押すだけでマーカーごと自動で書き換えてくれるので、慣れないうちはこれを使うのが楽です。

どちらを採用すべきか迷ったら

実務では「どっちを採用するのが正しいか」の判断に悩むことが多いです。以下のような手順で考えます。

  1. git log で両側のコミットの意図を確認する
    git log --oneline HEAD..feature   # featureのみにあるコミット
    git log --oneline feature..HEAD   # HEADのみにあるコミット
  2. 両方の変更を理解してから、マージ後に期待される挙動を考える
  3. 判断できなければ、変更した本人に聞く

「コンフリクトしたから片方消しちゃえ」は一番危険です。相手の変更を意図せず消してしまうと、バグを埋め込む原因になります。

コンフリクトを減らすコツ

コンフリクトは完全には避けられませんが、頻度と規模を減らすことはできます。

  1. こまめにmainを取り込む — 長期間feature branchを放置するとコンフリクトが巨大化する
  2. 小さなPRを心がける — 1つのPRで多くのファイルを触らない
  3. チーム内で作業領域を分ける — 同じファイルを同時に触らないよう調整
  4. リファクタリングと機能追加を同じPRに混ぜない — 両方やると広範囲でコンフリクトしやすい

よくあるハマりどころ

1. コンフリクトマーカーを消し忘れてコミットしてしまった

<<<<<<< がコードに残ったままコミットすると、構文エラーや動作不良の原因になります。コミット前に必ず git diff --cached で確認する癖を付けましょう。

エディタやエディタ拡張で、保存時にマーカーを検出する仕組みを入れておくのも有効です。

2. 大量のコンフリクトに圧倒される

数十ファイルが一気にコンフリクトすると心が折れます。そういうときは:

  • 1ファイルずつ解決する
  • 中止して、もっと小さい単位でmergeし直す(ブランチをこまめにリベースする運用に切り替える)
  • 最悪、ファイルごとに「片方を完全に採用」する
# featureの内容を丸ごと採用
git checkout --theirs path/to/file
 
# HEADの内容を丸ごと採用
git checkout --ours path/to/file

3. コンフリクトしたファイルを誤って削除してしまった

解決中のファイルを誤削除した場合、git checkout -m path/to/file でコンフリクト状態に戻せます。

ちゃんと使うためのポイント

  • コンフリクトは壊れたわけではない、Gitが人間の判断を求めているだけ
  • <<<<<<< ======= >>>>>>> の意味を読める
  • 解決は「編集 → git addgit commit」の流れ
  • 迷ったら git merge --abort / git rebase --abort でやり直せる
  • こまめに取り込むことでコンフリクトの規模を小さく保つ

次の章では、コミットしたあとに「取り消したい」「修正したい」となったときの方法、reset・revert・restore の使い分けを学びます。