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に回ります。
| 操作 | ours | theirs |
|---|---|---|
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=3HEAD と書かれた上側が timeout=45 で、これはmainの値です。下側の timeout=60 が、積み直し中のfeatureのコミットです。git status -sb の先頭行は ## HEAD (no branch) で、ブランチ名ではなくdetached HEADと表示されました。rebase中は、いま居る場所が「積み終えたところ」であって、featureブランチそのものではないためです。
この出力は、マーカーの上側がmain、下側が自分のコミットであることの確認に使えます。Claudeに頼むときも、この対応を一言添えておくと取り違えを防げます。
衝突が起きたら、rebaseは途中で止まっている
衝突したrebaseは、失敗して終わったのではなく、そのコミットの手前で止まっています。再開の手順は次のとおりです。
衝突した後の再開手順
- 1
衝突マーカーを直す
git diffでマーカーを探し、ファイルを編集します。 - 2
直したファイルをステージする
git add <file>で、解決したことをgitに伝えます。 - 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点だけは、依頼文に書いておくと安全です。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。