こんにちは、エンジニアのオオバです。

お悩みさん
お悩みさん
  • Pullが「Your local changes would be overwritten」で失敗する
  • Pushが「先にPull」と言われて詰む
  • gitの競合をAIに直させて大丈夫か不安
  • オオバ
    オオバ
    本記事ではこれらの悩みを解決します。

    Mac mini で常駐させている自作のマスター管理画面には、git の状態を見る「gitバー」があります。

    そこに Pull / Push ボタンを置いているんですが、これがたびたび詰んでいました。

    Pull を押すと失敗する。

    Push を押すと「origin より遅れているので先に Pull してください」と止められる。

    つまり画面からは何もできなくなるということ。

    Pullをstash経由の取り込みに変え、ぶつかり方が決まった競合だけ自動で解消し、残りは「AIで解決」ボタンでClaude Codeに直させた

    先に結論をまとめます。

    PullもPushも通らない詰みの抜け方

    ①Pullは未コミットをstashしてから、fast-forwardかrebaseで取り込み、stashを戻す流れに変える

    ②ぶつかったら、型が決まっているものだけ自動で解消する。解消できなければ元の状態まで完全に巻き戻して失敗にする

    ③それでもダメなときは「AIで解決」ボタンで claude -p に直させる。許可する道具を絞り、終わったら機械で検証する

    ここから、原因と解決を順番に書いていきます。

    →11万文字で徹底解説した「DOTweenの教科書」Unityアニメーションの超効率化ツールはこちら

    起きたこと:PullもPushも通らない

    詰んでいたクローンの状態は、develop が 6 コミット遅れていて、未コミットのファイルが 8 つという状況でした。

    管理画面は、普段から人間(僕)が画面上で編集する場所です。

    そのため、未コミットの編集がたまっている状態は日常です。

    ここに、別の場所から develop へ入ってきた更新を取り込もうとすると、冒頭の詰みが起きます。

    原因:Pullの作りとPushの拒否が噛み合っていた

    原因は2つに分かれていました。

    PullがFast-forward専用だった

    もともとの Pull は、次のコマンドでした。

    git merge --ff-only @{u}  
    

    取り込む更新と同じファイルを、手元で未コミットのまま編集していると、git は「Your local changes would be overwritten」と言って拒否します。

    そりゃそうですよね。

    未コミットの編集は git が守ってくれないので、上書きを避けるために止まるしかないんです。

    Pushは遅れていると拒否される

    一方の Push は、リモートより遅れていると拒否されます。

    Pull が通らないので遅れは解消されず、Push も通らない。

    この2つが噛み合って、画面からは打つ手がなくなっていました。

    「たびたび」の正体は翻訳CSVのid衝突

    今回ぶつかっていたのは、翻訳の CSV です。

    新しいキーを足すとき、id を「いまの最大値 + 1」で振る仕組みになっています。

    そのため、管理画面側と develop 側が同じ時期に新しいキーを足すと、どちらも同じ id 603 になります。

    diff3 形式で競合を見ると、base(共通の祖先)が空で、両方が末尾に追記した形でした。

    これが、たびたび詰んでいた正体です。

    ちなみに、片方が足した行が消えるわけではないので、本来は「両方残す」が正解。

    ただし id だけは重複してはいけないので、振り直しが要ります。

    解決1:Pullをstash経由の取り込みに変える

    Pull の流れを、次のように変えました。

    1. 未コミットの変更を stash する(あとで確実に戻せるよう、SHA で識別する)
    2. fast-forward か rebase で上流を取り込む
    3. stash を apply する

    stash を名前でなく SHA で扱うのがポイントで、ほかの stash が積まれていても自分の分だけを確実に戻せます。

    解決2:ぶつかったら決まった型だけ自動で解消する

    stash を戻したときに競合が出たら、型が決まっているものだけ自動で解消します。

    最後の CRLF は地味ですが、効きます。

    改行コードを意図せず変えてしまうと、全行が差分になってしまうからです。

    型に当てはまらないときは、無理に解消しません。

    HEAD と未コミットの編集を完全に巻き戻して、失敗として返します。

    マーカー(<<<<<<< など)が入った中途半端な状態を、残さないためです。

    解決3:Pushは遅れていたら取り込んでから送る

    Push は、遅れていたら同じ取り込み処理を走らせてから push するようにしました。

    Pull と Push で別々のロジックを持たないので、詰みの片方だけが残ることがありません。

    それでもダメなときの「AIで解決」ボタン

    型にはまる競合は自動で解消できるようになりました。

    とはいえ、現実のgitの状態は型にはまらないことがあります。

    そこで、最後の逃げ道として「AIで解決」ボタンを足しました。

    gitバーのブランチ名の左にあるボタンです。

    Pull / Push が失敗した直後は、紫に光ります。

    管理画面のgitバーに追加した「AIで解決」ボタン

    管理画面のgitバーに追加した「AIで解決」ボタン

    押すと、Mac mini の Claude Code(claude -p、サブスクの枠)が git の状態を直します。

    とはいえ、AI に git を直接触らせるのは怖いですよね。

    「git push --force されたらどうしよう」と思うかも知れません。

    なので、安全のための仕掛けを3つ入れました。

    事前にバックアップを取る

    AI を呼ぶ前に、未コミットの差分と未追跡ファイルをバックアップします。

    最悪、何が起きても手元の編集は戻せます。

    使える道具を絞る

    --allowedTools で、使えるのは git / python3 / Edit だけにしました。

    さらに --disallowedTools で、次の操作を道具ごと禁止しています。

    お願いで止めるのでなく、呼べない状態にしておくのがコツです。

    終わったら機械で検証する

    AI が「直りました」と言っても、そのまま信じません。

    次の項目を、コードで機械的に確かめます。

    試してダメだったこと

    最初の試走では、2つ詰まりました。

    index.lockを消す許可が効かなかった

    Bash(rm -f .git/index.lock) を許可しておいたのに、Claude Code が .git の中の rm を拒否しました。

    すると AI は「拒否されたものを別の方法で消すのは控えます」と言って止まったんです。

    偉い。

    正直、ここは感心しました。

    ただ、これでは直らないので、ロックの削除は AI に頼まず、ボタンを押したときの呼び出し側で決定的に行う形に変えました。

    条件は「60 秒以上古く、かつ git のプロセスが動いていないとき」だけです。

    安全のためのstashが残った

    AI が安全のために作った stash が、そのまま残ってしまいました。

    git stash drop を禁止しているので、AI 自身は消せないからです。

    そこで、指示を「git stash pop で戻すこと」に変えました。

    結果:3秒で取り込み、AIは130秒で解決

    実データで試した結果です。

    自動マージで済む分は3秒、型に外れた分は AI に2分ちょっと。

    この使い分けが、いまのところちょうどいい感触です。

    学び:決まった型は機械に、残りはAIに

    今回の整理を一言でいうと、型が決まっている競合はコードで、決まっていない競合は AI にということです。

    しかも、AI には「何でもできる」権限を渡さず、道具を絞ったうえで、結果を機械で検証する。

    これだけで、管理画面から git を触るのが怖くなくなりました。

    まとめ

    同じように「画面から git が詰む」ことがある人は、まず Pull を stash 経由にしてみてください。

    一緒に、詰みのない管理画面を作っていきましょう。

    オススメ記事
    検証環境