エディタに並んだGitのコンフリクトマーカーを前に、どこから手をつけるか落ち着いて考えている開発者

AIにGitのコンフリクト解消を手伝わせる|壊さないマージの確認手順

金曜の夕方、そろそろ帰ろうかというタイミングで git pull。返ってきたのは「CONFLICT」の文字。ファイルを開くと <<<<<<<======= が並んでいて、どこから触ればいいのか一瞬固まる——あの感じ、ありますよね。

こういうとき、AIに丸ごと貼って「直して」とお願いしたくなります。実際、AIはとても上手に片づけてくれます。マーカーはきれいに消えて、コードは通る。ただ、そこで消えているものがあることに、あとから気づくことがあります。この記事では、いま何が起きているのかを共有したうえで、AIに手伝ってもらいながら「片側の変更を落とさない」ための確認手順を整理します。

結論:Gitのコンフリクト解消をAIに手伝わせるときの要点は3つです。①解消の前に「自分側と相手側が、それぞれ何をしたかったか」を1行で言えるようにする②AIにはマーカーの中身だけでなく「もとの状態(分かれる前の共通の内容)」も渡す——git config --global merge.conflictStyle zdiff3 を一度設定しておくと、これが自動で見えるようになります。③解消したあとは「マーカーが消えたか」ではなく「片側の変更が消えていないか」で確認する。AIが得意なのは「マーカーのない、それらしいコードを作ること」であって、「両方の意図を守ること」ではありません。そこだけ人が持てば、あとは任せられます。

いきなり全部は要りません。 まずは②の設定を1行入れるところからで大丈夫です。

何が起きているのか——AIには「意図」が渡っていない

分かれる前のもとの内容から、自分の変更と相手の変更の2つに枝分かれした様子を示した図
「もと」が見えていると、どちらが何を足して何を消したのかが読める。既定の表示では、この上段が隠れている

まず、なぜここだけ気を張る必要があるのかを共有させてください。理由は3つあります。

1つ目は、コンフリクトマーカーが「文字の違い」しか運んでいないこと。Gitが既定で見せてくれるのは、自分側相手側の2つだけです。分かれる前のもとの状態は表示されません。ここが抜けると、片方が「行を足した」のか、もう片方が「行を消した」のかが区別できません。人が読んでも判断が割れるものを、そのままAIに渡していることになります。

2つ目は、AIが「マーカーのないコード」を作るのが得意なこと。これは弱点ではなく、まさに得意分野です。両方の書き方を混ぜて、構文としては正しく、それらしく辻褄の合ったコードを作れます。ただ、「両方の意図を守れているか」は、渡していない情報なので判断のしようがありません。結果として、片側の変更が静かに消えても、見た目には完成した状態になります。

3つ目は、Gitがコンフリクトとして報告しない衝突があること。たとえば、自分が関数の引数を1つ増やし、相手が別のファイルからその関数を新しく呼び足した——行が離れていれば、Gitは何も言わずに自動でマージします。マーカーは1つも出ないのに、動きません。「マーカーを消す作業」だけを終わりの合図にしていると、ここが素通りになります。

この3つが重なると、「コンフリクトは解消できたのに、あとで壊れている」が起きます。逆に言えば、もとの状態を見せて、意図を言葉で添えて、解消後に両側から差分を見る——これだけで、かなり防げるということです。

具体例——こんなふうに、片側が消える

実際に起きやすいのは、次のような形です。一つでも心当たりがあれば、この記事はそのまま使えます。

どれも、手を抜いた結果ではありません。コンフリクトが出たということは、同じ場所で二人とも仕事をしたということです。そこは責める場所ではなく、丁寧に扱う場所です。

影響——気づくのが、だいぶあとになる

コンフリクトの取り違えが厄介なのは、その場で失敗として現れにくいところです。

ただ、ここは強調しておきたいところなのですが、マージはやり直せます。コミットする前なら git merge --abort で、着手前の状態にまるごと戻せます。コミットしたあとでも、git reflog を見ればマージ前の位置がそのまま残っています。「間違えても戻れる」と分かっているだけで、判断はかなり落ち着きます。焦って混ぜるより、一度戻して仕切り直す方が、たいてい速いです。

なお、戻すために使うコマンドの中には、手元の変更ごと消えるものもあります。実行前の確認の考え方はAIにコマンドを書かせて事故らない|破壊的コマンドを弾く確認の型にまとめてあります。

明日からやること(3ステップ)

大きな運用変更は要りません。この順番で、今日から小さく始められます。

  1. もとの状態が見えるようにする(設定1行)git config --global merge.conflictStyle zdiff3 を一度実行しておきます(Git 2.35以降で使えます。それより前のバージョンなら diff3 を指定してください)。以後、コンフリクトの表示に ||||||| で区切られたもとの状態が入るようになります。自分側・相手側・もとの3つが揃うと、「どちらが足したのか、どちらが消したのか」が読めるようになります。人が読むときにも、AIに貼るときにも、同じだけ効きます。この記事で一番効く一手は、これです。
  2. AIに渡す前に、両側の意図を1行ずつ書く:コンフリクト中に git log --merge -p -- <ファイル名> を実行すると、そのファイルに触れた両側のコミットだけを差分つきで見られます。読んだら、「自分側=〇〇をしたかった」「相手側=〇〇をしたかった」と1行ずつメモします。この2行が、この作業でいちばん価値のある成果物です。そのうえでAIには、①もと・自分・相手の3つを含むコンフリクト箇所、②この意図2行、③周辺のコードを渡します。ファイルは1つずつで。文脈の渡し方はAIコーディングは小さく切る|関数1個から始めるスコープ設計と同じ考え方です。

そのまま使える頼み方の例:

> 以下はGitのコンフリクト箇所です。||||||| の上が自分側、その下がもとの状態、======= の下が相手側です。 > 自分側の意図:(1行) > 相手側の意図:(1行) > 両方の意図を満たす解消案を出してください。両立できない場合は、勝手にどちらかへ寄せず、両立できない理由を先に説明してください。 最後に「どちらの変更をどう扱ったか」を箇条書きで示してください。

「両立できないときは言って」と先に伝えておくのが、地味ですがよく効きます。AIは黙って辻褄を合わせられてしまうので、判断が要る場所を報告させる約束にしておきます。

  1. コミットする前に、両側から差分を見る:解消したら、まだコミットせずに次の2つを見ます。git diff HEAD -- <ファイル名> は自分側から見た差分、git diff MERGE_HEAD -- <ファイル名>相手側から見た差分です。相手の変更が落ちていれば、後者に「元に戻した形」でくっきり出ます。ここで、②で書いた意図2行が両方とも満たされているかを確かめます。そのあとでビルドとテストへ。マーカーが出なかった意味的な衝突は、ここでしか見つかりません。既存テストの活かし方はAIに直させてもリグレッションを出さない既存テストの渡し方が下敷きになります。

補足として、rebaseなどで同じコンフリクトを何度も解く場合は、git config --global rerere.enabled true を入れておくと、一度解いた解消をGitが覚えて再利用してくれます。便利ですが、1回目の解消が正しかったことが前提になるので、最初の1回だけは、いつもより丁寧に見ておくのがおすすめです。

3つとも今日やる必要はありません。1だけでも、次のコンフリクトの読みやすさがはっきり変わります。

コンフリクト解消のチェックリスト

全部に○が要るわけではありません。気になった行だけで十分です。

① 解消の前に

② AIに渡すとき

③ コミットする前に

この記事のまとめ

AIはコンフリクトの解消がとても得意です。ただ、得意なのは「マーカーのない、それらしいコードを作ること」であって、「両方の意図を守ること」ではありません。意図は、こちらが渡さない限り届かないからです。

やることは3つだけです。merge.conflictStylezdiff3 にして、もとの状態を見えるようにする/②両側の意図を1行ずつ書いてから渡す/③コミット前に git diff MERGE_HEAD で相手側から見直す。この3つがあると、コンフリクトは「地雷を踏まないよう祈る作業」から、「読んで決める作業」に変わります。

コンフリクトを解消し終えて両側の差分を確認し、安心した表情で顔を上げている開発者
両方の意図が残っていることを確かめられたら、その解消はもう安心して送り出せる

コンフリクトが出るのは、同じ場所を二人が本気で触ったからです。それを片方に寄せず、両方の仕事を残そうとして手を止められる人は、チームの中でかなり信頼される人だと思います。今日その手間をかけた分は、誰かの数日を確実に助けています。

よければ、こちらも

関連用語