AIが競合を解決した後の確認:消えた変更と混ざった変更を見つける方法
AIを使いこなすClaudeで執筆し、GPTで自動校閲・修正しています。

結論から言うと、<<<<<<< が消えただけでは確認になりません。AIは競合を「片方を採用する」「両方を適当につなぐ」で解消することがあり、その場合もマーカーは消え、ビルドも通ることがあります。確かめるべきなのは次の3点です。 1. マーカーや空白エラーが残っていないか 2. 自分側と相手側が、それぞれ分岐後に加えた変更が結果に残っているか(消えた変更) 3. 結果に、どちら側にも存在しない行がないか(混ざった変更) 最後に、変更ごとに実際に動かして確認します。この記事では、1〜3を行うGitコマンドと小さなスクリプト、そして動作確認表を示します。例はすべて説明用で、本記事の執筆時に実行したものではありません。お使いのリポジトリで試す前提で読んでください。 コマンドの仕様は、2026年10月29日の自動校閲で確認した Git 公式リファレンス(git merge、git diff)に基づきます。
この記事の目次
前提:確認はコミットの前に行う
AIに任せる範囲は「競合の解消案づくり」までにし、コミットは確認後にします。マージ前の変更はコミットまたは退避しておきます。取り込みをコミット前で止めるには、次のようにします。
git merge --no-ff --no-commit feature/other--no-commit を付けると、マージコミットの作成前に結果を確認できます。早送り更新はこの指定だけでは止まらないため、--no-ff も付けています(git merge)。以下は、2つのブランチのマージがコミット前で止まっている状態を前提に、次の呼び方を使います。取り込み済みでマージが始まらなかった場合は対象外です。
- HEAD:今いるブランチ側(以下「自分側」)
- MERGE_HEAD:取り込む側(以下「相手側」)
すでにAIがコミットまで進めた場合は、最後の節の方法を使います。
手順1:マーカーと対象ファイルを確認する
git diff --check # インデックスと作業ツリーとの差が対象
git diff --cached --check # git add 済みならこちら
git diff HEAD --name-statusgit diff --check は、変更がコンフリクトマーカーや空白エラーを持ち込んでいないかを警告し、見つかると終了コードが0以外になります(git diff)。--name-status はファイルごとの状態だけを一覧にします。削除や名前変更が意図せず混ざっていないかを、ここで見ておきます。
手順2:両側の変更を取り出して結果と照合する
三点リーダー A...B は、AとBの共通の祖先からBまでの差分です(git diff)。分岐後に各側が何をしたかを、これで取り出せます。対象ファイルの競合を解消したら、git add -- path/to/file で解決済みとして登録してから、次の比較を行います。これはコミットではありません。未登録の競合ファイルでは、結果との差分が結合差分になり、通常の + 行として読めない場合があります。
# 相手側が分岐後に加えた変更
git diff HEAD...MERGE_HEAD -- path/to/file
# 自分側が分岐後に加えた変更
git diff MERGE_HEAD...HEAD -- path/to/file
# 結果(作業ツリー)と自分側の差
git diff HEAD -- path/to/file見方は単純です。相手側の + 行が、結果のファイルにあるか。自分側の + 行も同様にあるか。ない行が「消えた変更」の候補です。目で追うと見落とすので、ファイル単位で次のスクリプトを使うと候補を機械的に出せます。
照合スクリプト(説明用・未実行)
Bash、grep、awk、mktemp が使える環境で、リポジトリのルートからマージ途中に実行する想定です。対象は、両側と作業ツリーに同じパスで存在する通常のテキストファイルです。追加・削除・名前変更・バイナリファイルは手順2の差分で個別に確認してください。行単位の完全一致で比べるので、結果は「確認対象の候補」であり、不具合の断定ではありません。エラーが表示された場合は判定を中止してください。
#!/usr/bin/env bash
# usage: bash check-merge.sh path/to/file
f="${1:?対象ファイルを指定してください}"
[ -f "$f" ] && [ -r "$f" ] || exit 1
t=$(mktemp -d) || exit 1
trap 'rm -rf "$t"' EXIT
added() { awk '/^@@ / { hunk=1; next } hunk && /^\+/ { print substr($0, 2) }'; }
git diff --no-color --no-ext-diff --no-textconv -U0 HEAD...MERGE_HEAD -- "$f" > "$t/theirs_diff" || exit 1
git diff --no-color --no-ext-diff --no-textconv -U0 MERGE_HEAD...HEAD -- "$f" > "$t/ours_diff" || exit 1
added < "$t/theirs_diff" > "$t/theirs_added"
added < "$t/ours_diff" > "$t/ours_added"
git show "HEAD:./$f" > "$t/ours_full" || exit 1
git show "MERGE_HEAD:./$f" > "$t/theirs_full" || exit 1
echo "== 相手側が足した行のうち、結果にない行"
grep -Fxv -f "$f" "$t/theirs_added"
echo "== 自分側が足した行のうち、結果にない行"
grep -Fxv -f "$f" "$t/ours_added"
echo "== 結果にあるが、相手側のファイルにも元の自分側にもない行"
grep -Fxv -f "$t/ours_full" -- "$f" | grep -Fxv -f "$t/theirs_full"出力の判断表
- 1つ目に行が出る、考えられる状況:相手側の追加が落ちた、または両側が同じ行を別の形に直した、次の一手:該当行を相手側の差分で確認し、意図的に捨てたかを決める
- 2つ目に行が出る、考えられる状況:自分側の追加が落ちた、または別の形に直された、次の一手:同上。AIが「どちらかを採用」にしていないかを見る
- 3つ目に行が出る、考えられる状況:AIが両側を一行に統合した、または新しく書き足した、次の一手:内容を読み、根拠のない行なら削除を依頼する
- エラーも候補行も出ない、考えられる状況:この照合では候補が見つからない、次の一手:順序、重複、削除の残りは手順3と確認表で見る
この方法は行の有無しか見ません。片方が削除した行を結果が残している場合や、同じ関数が二重に入った場合は検出できないので、次へ進みます。
手順3:混ざった結果を読む
まだ git add していない競合ファイルなら、git diff が両側と作業ツリーを比較する結合差分を出します。共通祖先の本文を併記する形式ではありません。git checkout -m -- path/to/file で競合状態を作り直せますが、対象ファイルの現在の解決内容を上書きします。試す場合は先に解決結果をリポジトリ外へコピーして保存してください(git checkout)。git checkout --conflict=diff3 -- path/to/file も同様に上書きし、共通祖先を含む競合マーカーを再作成します。手順2で登録済みのファイルは、次の方法で読み比べます。
既定の ort 戦略で競合が起きた場合、AUTO_MERGE に競合マーカーを含む自動マージ時点のツリーが記録されます。マージ途中の git diff AUTO_MERGE は、その時点から作業ツリーまでの変更を示します(git merge)。解決のために変えた部分を読めますが、人やAIが行った他の編集も含まれます。AUTO_MERGE が存在しない場合は、比較元が異なることに注意して、手順2の3つ目の出力と両側の差分を見比べてください。
手順4:動かして確かめる確認表
次の表を、PRやメモに貼って埋めます。「変更ごとに1行」を作るのが肝心です。
- 1、確認項目:ビルド・型チェック、出どころ:全体、実行方法:プロジェクト標準のコマンド、合格の目安:エラーなし、結果:
- 2、確認項目:既存テスト、出どころ:全体、実行方法:プロジェクト標準のテスト、合格の目安:競合前と同じ結果、結果:
- 3、確認項目:相手側の変更A、出どころ:手順2の相手側差分、実行方法:その機能を実際に操作、合格の目安:期待どおり動く、結果:
- 4、確認項目:自分側の変更B、出どころ:手順2の自分側差分、実行方法:同上、合格の目安:期待どおり動く、結果:
- 5、確認項目:AとBの併用、出どころ:両側、実行方法:両方が関わる操作、合格の目安:一方が他方を壊さない、結果:
- 6、確認項目:重複・二重定義、出どころ:結果、実行方法:関数名や設定キーを検索、合格の目安:同じスコープに意図しない重複がない、結果:
- 7、確認項目:設定ファイルの構文、出どころ:結果、実行方法:パーサや整形ツールで読込、合格の目安:読み込める、結果:
3〜5を確かめる既存のテストがない場合は、追加を検討します。テストを書く場合はAIに任せられますが、期待値は人が決めます。AIの解決結果から期待値を作ると、落ちた変更を正解と見なすテストになってしまいます。
AIへの依頼文の例
照合を終えてから、修正を依頼します。渡すのは差分だけで、認証情報や顧客データなどを含む部分は伏せた説明用のデータに置き換えてください。
次の3つの差分を渡します。
(1) 相手側の変更(git diff HEAD...MERGE_HEAD)
(2) 自分側の変更(git diff MERGE_HEAD...HEAD)
(3) 現在の解決結果(git diff HEAD)
やってほしいこと:
- (1)と(2)の変更を1件ずつ番号付きで列挙し、(3)に残っているか表にする
- 残っていない変更は「意図的に捨てたか」を推測せず「不明」と書く
- (1)(2)に由来しない行が(3)にあれば、その行を引用する
- 私の確認前にファイルを書き換えない出てきた表は、手順2の出力と突き合わせます。AIの表だけで合格にはしません。
すでにコミットした場合:マージ後と rebase 後
親が2つのマージコミットなら、git show --remerge-diff <コミット> が使えます。2つの親を再マージして一時的なツリー(競合マーカー入りのこともある)を作り、実際のコミットとの差を出すため、解決で人やAIが手を入れた部分が見えます(git show)。--cc は、片方の親の内容をそのまま選んだだけの箇所を省いた、結合差分を出します。
rebase 直後は、git range-diff '@{u}' '@{1}' @ で、rebase 前後の各コミットの差を比べられます(git range-diff)。これは、現在のブランチに上流が設定され、@{u} が比較の基点となる上流、@{1} が rebase 前の先端を指す場合の例です。条件が異なる場合は、基点・rebase 前・rebase 後のコミットを明示して比較してください。= は対応するパッチに変化なし、! はパッチやコミットメッセージなどに差があることを示します。! の詳細に加え、追加・削除として表示されたコミットも確認します。なお rebase 中の ours は載せ替え先、theirs は載せ替える自分の変更を指します(git checkout)。

