Gitのマージコンフリクトを解決する:手動対応からツール活用まで完全ガイド

Git tutorial - IT technology blog
Git tutorial - IT technology blog

マージコンフリクトに初めて遭遇したとき、自分が何かを壊してしまったのかと思った。CONFLICT (content): Merge conflict in app.pyという赤い文字が表示された瞬間、ターミナルを閉じてコーヒーを淹れに行って気持ちを落ち着かせた。

実際のところ、マージコンフリクトはまったく普通のことだ。同じファイルの同じ箇所を2人が同時に編集し、両方ともメインブランチにマージしようとすると発生する。Gitはどちらのコードを残すべきか判断できないため、手動で対処するよう「助けを求めて」くる。

マージコンフリクトはどう見える?

Gitは競合している箇所を3種類のマーカーでファイルに直接書き込む。最初は少し混乱するが、理解してしまえば数秒で読めるようになる:

<<<<<<< HEAD
def tinh_tong(a, b):
    return a + b  # 自分のバージョン
=======
def tinh_tong(a, b):
    result = a + b
    return result  # 同僚のバージョン
>>>>>>> feature/refactor-math

<<<<<<< HEAD=======の間が自分のコード(現在のブランチ)、残りが相手のブランチからのコードだ。どちらを残すか、あるいは両方を組み合わせるかを決めて、マーカーをすべて削除するのが作業内容となる。

マージコンフリクトの3つの対処法

方法1:テキストエディタで手動修正

最もシンプルな方法——ファイルを開き、マーカーを削除して正しいコードだけを残し、コミットし直す。

# マージ後にコンフリクトが発生した場合
git merge feature/refactor-math

# コンフリクトしたファイルを開いて手動修正後:
git add app.py
git commit -m "fix: resolve merge conflict in tinh_tong function"

メリット:追加インストール不要で、各行のコードを完全にコントロールできる。
デメリット:マーカーを見落としやすい。特にコンフリクトが多い長いファイルでは要注意。以前、>>>>>>> feature/xyzという行を消し忘れたままプッシュしてしまい、ビルドサーバーがすぐにエラーを報告した——かなり恥ずかしかった。

方法2:マージツールを使う(VS Code、vimdiff、IntelliJ)

VS Code、IntelliJ、vimdiffはいずれも同じことをしてくれる:2つのバージョンを並べて表示し、どちらを残すか選べるようにする——散らかったマーカーを直接見る必要がなくなる。

# VS Codeをデフォルトのマージツールに設定する
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'

# マージツールを起動する
git mergetool

GUIのないSSHサーバー上で作業している場合は、vimdiffを使う:

git config --global merge.tool vimdiff
git mergetool
# vimdiff内の操作:
# ]c  — 次のコンフリクトにジャンプ
# :diffg LOCAL  — 自分のバージョンを採用
# :diffg REMOTE — 相手のブランチのバージョンを採用

メリット:視覚的に分かりやすく、見落としが少ない。多くのツールはシンタックスハイライトもあるのでコードが読みやすい。
デメリット:初期設定が必要。vimdiffはvimに慣れていない場合は学習コストがかかる。

方法3:ours/theirs戦略

どちらかのバージョンが常に正しいと確信できる場合もある——たとえばmainのバージョンだけ残せばよい設定ファイルで、相手のブランチの変更はすべて破棄したいケースなどだ。

# 現在のブランチのコードをすべて採用する
git checkout --ours package.json
git add package.json

# 逆に:相手のブランチのコードをすべて採用する
git checkout --theirs package.json
git add package.json

メリット:素早く、判断に迷わない。
デメリット:片方のコードが完全に失われる——使い方を誤ると、復元できない重要な変更を消してしまう可能性がある。

分析:どの方法をいつ使う?

チームでGitを使って数年経験を積んだ後、こうまとめられる:

  • 設定ファイル(package.json、requirements.txt、.env.example):ours/theirsを使う——ロジックが少なく、どちらのバージョンが正しいか決めるだけでよい。
  • 複雑なロジックを含むコードファイル:手動修正かマージツールが必須——どちらの変更を残すか決める前に、両方の変更を理解する必要がある。
  • 1ファイルに多数のコンフリクト:VS Codeなどのマージツールを使う——視覚的に分かりやすく、マーカーの見落としが少ない。
  • SSHサーバー上:nano/vimで手動修正するか、vimdiffを使う。

実践ワークフロー:コンフリクトを順を追って解決する

コンフリクトが発生したときに毎回使っているワークフロー——ステップごとに確認して見落としなし:

# ステップ1:コンフリクトしているファイルの一覧を確認
git status
# コンフリクトしたファイルは「both modified」の状態で表示される

# ステップ2:各ファイルのコンフリクト詳細を確認
git diff

# ステップ3:VS Codeで解決する(または手動修正)
code .

# ステップ4:修正完了後、解決済みとしてマークする
git add app.py

# ステップ5:まだコンフリクトが残っていないか確認
git status

# ステップ6:マージを完了させる
git commit
# またはリベース中の場合:
git rebase --continue

プッシュ前に以下のコマンドも実行して、マーカーが残っていないことを確認する——少なくとも数回は恥ずかしいビルド失敗から救ってくれた:

grep -r "<<<<<<< " --include="*.py" --include="*.js" .
# 出力がなければプッシュしても安全

コンフリクトを減らすための予防策

これは痛い目を見て学んだ教訓だ——コンフリクト解消に疲弊してようやくワークフローを変える気になった。8人のチームで以下の4つのルールを適用したところ、コンフリクトの発生頻度が1日3〜4回から週1〜2回にまで減少した:

  1. 作業開始前にプルする:新しいコードを書き始める前に、常に最新のコードを同期する。
  2. ブランチは小さく、短命に:ブランチが長く存在するほどmainから乖離し、コンフリクトが増えて解決も難しくなる。自分のルールとして、ブランチの寿命は3日以内と決めている。
  3. 担当範囲を明確に分ける:可能であれば、同じスプリント中に同じファイルを2人が同時に編集しないようにする。
  4. 定期的にmainをブランチにマージする:毎朝作業中のブランチにmainを同期することで、コンフリクトを小さく保ち、スプリント末まで溜め込まずすぐに対処できる。
# 朝のルーティン
git checkout main
git pull origin main
git checkout feature/my-task
git rebase main
# 小さなコンフリクトをその場で解決する——スプリント末まで放置するより遥かに楽

MergeかRebaseか——どちらを選ぶ?

最もよく聞かれる質問だ。簡潔にまとめると:

  • Merge:マージコミットを作成し、実際の履歴をそのまま残す。フィーチャーブランチをmainに統合するときに使う。
  • Rebase:履歴を書き換え、コミットを直線的にきれいに並べる。mergeする前にフィーチャーブランチをmainで更新するときに使う。

最もシンプルなルール:複数人が共有しているブランチは絶対にrebaseしない。rebaseするのは自分だけが使っているブランチに限る。

まとめ

最初にターミナルを閉じてコーヒーを淹れに行ったあの瞬間を振り返ると——実際にやるべきことはファイルを開き、数行のマーカーを削除してコミットするだけだった。それだけのことだ。この記事で紹介した3つの方法をしっかり理解すれば、赤い文字を見るたびに気持ちを落ち着かせるためにコーヒーは必要なくなるはずだ。

長期的には、チームの習慣こそがコンフリクトを本当に減らす鍵となる。小さいブランチ、早めのマージ、毎朝の同期——シンプルに聞こえるが、自分が習慣として定着させるまでに約6ヶ月かかった。まず一つだけ始めてみよう:毎朝コードを書く前にgit rebase mainを実行する。それだけでも確実に違いを感じられるはずだ。

Share: