Claude Media
Claude Codeでrebaseのコンフリクトを解消 — oursとtheirsの逆転とrerere

Claude Codeでrebaseのコンフリクトを解消 — oursとtheirsの逆転とrerere

rebase中の衝突では、oursがmain側、theirsが自分のコミットになります。逆転の仕組みと、1コミットずつ解いてテストで確かめる流れ、rerereで同じ衝突を繰り返さない設定を、実機の出力つきで示します。

rebase中の衝突では、oursが自分のコミットではなくなる

rebaseの衝突解消でClaude Codeに最初に伝えておきたいのは、「ours」と「theirs」の向きです。mergeでは、いま居るブランチがours、取り込む側がtheirsです。rebaseではこれが逆になります。mergeバックエンドでのrebaseについて、gitのマニュアルは次のように書いています。

the side reported as ours is the so-far rebased series, starting with <upstream>, and theirs is the working branch. In other words, the sides are swapped.

rebaseは、自分のブランチのコミットを1つずつ、新しい土台(upstream)の上に積み直す操作です。積み直しの途中では、土台と、すでに積み終えたコミットがoursにあたり、これから積むコミットがtheirsです。「自分の変更」がtheirsに回ります。

操作ourstheirs
git merge main(feature上)oursfeatureの内容theirsmainの内容
git rebase main(feature上)oursmain(+積み終えたコミット)theirsこれから積むfeatureのコミット

この向きを取り違えると、「自分の変更を残してほしい」という依頼が、逆の結果を生みます。-X ours や -X theirs のようなオプションを使うときも、この逆転が同じように効きます。ここで間違えると、自分の変更を捨てる結果になります。

実機で見た衝突マーカー

向きを言葉だけで覚えるのは難しいので、使い捨てのリポジトリで再現しました(git 2.50.1)。mainで timeout=30 を 45 に、featureの1つ目のコミットで同じ行を 60 に変えています。featureの上で git rebase main を実行した結果です。

git rebase main
# CONFLICT (content): Merge conflict in app.conf
# error: could not apply 03a8925... feature: timeout 60
cat app.conf
<<<<<<< HEAD
timeout=45
=======
timeout=60
>>>>>>> 03a8925 (feature: timeout 60)
retries=3

HEAD と書かれた上側が timeout=45 で、これはmainの値です。下側の timeout=60 が、積み直し中のfeatureのコミットです。git status -sb の先頭行は ## HEAD (no branch) で、ブランチ名ではなくdetached HEADと表示されました。rebase中は、いま居る場所が「積み終えたところ」であって、featureブランチそのものではないためです。

この出力は、マーカーの上側がmain、下側が自分のコミットであることの確認に使えます。Claudeに頼むときも、この対応を一言添えておくと取り違えを防げます。

衝突が起きたら、rebaseは途中で止まっている

衝突したrebaseは、失敗して終わったのではなく、そのコミットの手前で止まっています。再開の手順は次のとおりです。

手順

衝突した後の再開手順

  1. 1

    衝突マーカーを直す

    git diff でマーカーを探し、ファイルを編集します。

  2. 2

    直したファイルをステージする

    git add <file> で、解決したことをgitに伝えます。

  3. 3

    rebaseを再開する

    git rebase --continue で、次のコミットの積み直しに進みます。

次のコミットでまた衝突すれば、同じ手順を繰り返します。実機では、2コミットのうち1つ目だけが衝突し、解決して --continue すると Successfully rebased and updated refs/heads/feature. で終わりました。

止まっている間は、git rebase --show-current-patch で、いま積もうとしているコミットの中身を確認できます。中身は git show REBASE_HEAD と同じです。「自分のコミットは何をしようとしていたのか」を、Claudeにも見せたいときに使えます。

Claudeには「このコミットの衝突だけ」を頼む

rebaseの衝突解消をClaude Codeに任せるときは、依頼の範囲を1コミット分に区切ります。複数のコミットがまとめて衝突を起こすのではなく、1つ解いて --continue するたびに、次の衝突が出てくる構造だからです。

依頼文の例を示します。

いまfeatureブランチをmainにrebaseしていて、衝突で止まっています。
次の順で進めてください。
 
1. git status で衝突しているファイルを列挙する
2. git rebase --show-current-patch で、積もうとしているコミットの意図を確認する
3. 衝突マーカーを解消する。HEAD側(上側)はmain、下側が自分のコミットです
4. git add で解決済みにするところまで。--continue は私の確認後に実行する

最後の指示で、--continue を人の確認の後に回しています。1つ解くたびにdiffを見たいのであれば、この区切りが役に立ちます。

勝手に進めさせない権限設定

Claude Codeの権限ルールでは、Bash(git commit *) のように、プログラムとサブコマンドの後に * を置いて許可や確認を書けます。allow に読み取り系を、ask に履歴を書き換える操作を並べる例です。

{
  "permissions": {
    "allow": [
      "Bash(git status)",
      "Bash(git diff *)",
      "Bash(git add *)"
    ],
    "ask": [
      "Bash(git rebase *)",
      "Bash(git push *)"
    ]
  }
}

Bash(git rebase *) を ask に置くと、--continue も --abort も確認が入ります。確認を省きたくなったら、Bash(git rebase --continue) のように末尾の * を外した完全一致の許可を足す選択肢があります。ただし、ルールの照合には癖があります。git -C . push のように書き方を変えた呼び出しは Bash(git push *) に一致しません。ルールに頼る場合は、pushの確認を別の経路でも確かめておくと安全です。

1コミットずつ解いて、コミットごとにテストを回す

rebaseの後でまとめてテストすると、失敗しても「どのコミットの解消が悪かったか」が分かりません。gitには、積み直した各コミットの直後にコマンドを実行する --exec(-x)があります。exec <cmd> が最終履歴の各コミットの後に入り、コマンドが失敗するとrebaseはそこで止まります。

git rebase main --exec "npm test"

テストが落ちて止まったら、その時点のコミットが原因です。直前の衝突解消を見直し、直して git add し、git rebase --continue で再開します。Claudeには、次のように頼めます。

rebaseが --exec の npm test で止まりました。
失敗しているテストを読み、直前の衝突解消が原因かを調べてください。
直す範囲は、そのコミットで私が触ったファイルに限ります。

hooksでテストを自動化する方法はClaude Code hooksでテストを自動実行するにあります。ただし、rebase中は --exec がコミット単位の確認を担うので、hooksと重ねる必要はありません。

rerereで、同じ衝突を2回解かない

長く続くブランチを何度もrebaseすると、同じ衝突が毎回出ます。git rerere(reuse recorded resolution)は、その解決を記録して再利用する仕組みです。手動で解決した結果を記録し、同じ衝突が出たときに適用するコマンドです。有効にするには、設定変数 rerere.enabled が必要です。

git config rerere.enabled true

設定しなくても、.git の下に rr-cache ディレクトリがあるリポジトリ(過去にrerereを使ったもの)では、既定で有効になります。

有効にすると、git merge が衝突で終わったときに自動で rerere が呼ばれ、git commit もマージ結果をコミットするときに呼びます。rebaseでも、記録された解決が使われます。

実機で見た記録と再利用

先ほどの使い捨てリポジトリで、rerere.enabled を有効にして試しました。1回目は手で解決して git add し、git rebase --continue を実行します。

Recorded resolution for 'app.conf'.
[detached HEAD 8d7162c] feature: timeout 60

次に、rebase前の状態へ戻して同じrebaseをやり直すと、衝突は起きるものの、次のメッセージが出ます。

CONFLICT (content): Merge conflict in app.conf
Resolved 'app.conf' using previous resolution.

app.conf の中身は、前回の解決どおり timeout=60 になっていました。注意点は、git status が UU app.conf のままだったことです。rerere はインデックスに触れません。そのため、内容を git diff で確かめてから git add する手順は、省けません。

自動で更新させない選択肢

--rerere-autoupdate を付けると、再利用した解決をインデックスにまで反映します。逆に --no-rerere-autoupdate は、rerere が何をしたかを確認してから別に git add するための指定です。Claudeに任せる場合は、後者のほうが、誤った再利用を git diff で拾えます。

常に自動で更新したい場合の設定変数は rerere.autoUpdate で、既定はfalseです。何も設定していなければ、再利用された解決はインデックスに載りません。記録の保持期間も設定で変えられ、gc.rerereResolved(既定60日)と gc.rerereUnresolved(既定15日)が git rerere gc の基準になります。

rerereの記録を消す・整理する

コマンド働き
git rerere status働き解決を記録する対象の衝突ファイルを表示
git rerere remaining働きrerereが自動解決できなかったパスを表示
git rerere forget <pathspec>働き現在の衝突に対して記録された解決を破棄
git rerere gc働き古い記録を整理(未解決は15日、解決済みは60日が既定)

誤った解決を記録してしまったときは forget で消します。git rebase --skip や --abort を実行すると、rerere clear が自動で呼ばれます。

試しマージを捨てる使い方もある

git-rerereのマニュアルは、もう一つの使い方を例に挙げています。長く続くトピックブランチでは、mainを取り込む「試しマージ」を何度も作ると、履歴に不要なマージコミットが積もります。そこで、試しにmainをマージして衝突を解き、その結果を git reset --hard HEAD^ で捨てます。解決だけがrerereに残るので、後で本番のマージやrebaseをするとき、同じ衝突が自動で解かれます。

この手は、Claudeに衝突を解かせる場面でも役に立ちます。試しマージで一度レビューした解決が、本番のrebaseに再利用されるからです。ただし、再利用できるのは「記録された解決がまだ当てはまる」間だけです。mainが先に進んで衝突の形が変わると、rerereは解決を適用しません。

詰まったときの出口は3つある

衝突を直しきれないときは、rebaseの状態そのものを戻せます。

コマンド結果
git rebase --abort結果rebase前の状態に戻る
git rebase --skip結果衝突したコミットを飛ばして次へ進む
git rebase --quit結果rebaseを止めるが、HEADは元に戻さず、作業ツリーとインデックスもそのまま

Claudeに「うまくいかなければ戻して」と頼むなら、--abort を指名します。--skip はそのコミットの変更が履歴から消えるため、頼むなら「どのコミットを捨ててよいか」を人が決めてからです。

並行する作業はworktreeに分ける

rebaseは作業ツリーを書き換えるので、同じディレクトリでほかのClaudeセッションが動いていると、中間状態を踏まれます。rebaseする作業ブランチ専用のworktreeを作り、その中でClaudeを起動すると、衝突解消が1か所に閉じます。手順はClaude Code Worktree実践ガイドに、cherry-pick側の同型の流れはClaude Codeでcherry-pickのバックポートを進めるにあります。cherry-pickは1つのコミットを持ち込む操作で、rebaseはブランチ全体を積み直す操作ですが、衝突を1か所ずつ解く流れは共通です。

まとめ

rebaseの衝突解消でClaudeに頼む前に、決めておきたいのは3点です。マーカー上側(HEAD)がmainであること、解くのは1コミット分で --continue は人が確認してから進めること、同じ衝突が続くなら rerere を有効にしてインデックスの確認を挟むことです。向きを知らないまま「oursを残して」と頼むと、自分の変更を捨てる指示になります。この1点だけは、依頼文に書いておくと安全です。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. git-scm.com/docs/git-rebase
  2. git-scm.com/docs/git-rerere
  3. git-scm.com/docs/git-config
  4. code.claude.com/docs/en/permissions
  5. code.claude.com/docs/en/common-workflows
この記事を共有:XはてブLinkedIn