Claude Codeでgit rebase -iを使ってコミットを整理する手順
git rebase -iのtodoをGIT_SEQUENCE_EDITORで差し込み、fixupとsquashを非対話で実行する手順です。バックアップブランチの切り方と、許可ルールに引っかかる点もまとめます。
git rebase -iは、コミットの並びを書いたtodoファイルをエディタで開いて、人が書き換える前提のコマンドです。Claude Codeに「WIPコミットを整理して」と頼むときは、このエディタの部分を別の仕組みに置き換えます。使うのは環境変数GIT_SEQUENCE_EDITORで、todoを書き換えるコマンドをgitに渡せます。
置き換えれば、fixupやsquashは非対話で走ります。ただし許可ルールの書き方次第では、環境変数の前置きで確認プロンプトが出ます。この記事では、実際に動かした結果と、バックアップの取り方、許可ルールの注意点をまとめます。
なぜgit rebase -iをそのまま実行させないのか
git rebase -iはtodoを開くエディタを起動します。エディタが閉じられるまでrebaseは進みません。エディタを使えない環境では、gitがエラーで止まります。
エディタが失敗したときの挙動を、空のリポジトリで確かめました(git 2.50.1)。エディタに終了コード1を返すfalseを指定した例です。
GIT_EDITOR=false GIT_SEQUENCE_EDITOR=false \
git rebase -i main出力はerror: there was a problem with the editor 'false'でした。git status -sbの先頭は## featのままで、履歴は動いていません。失敗しても状態は壊れないので、まず試す価値はあります。
gitの設定リファレンスによると、git rebase -iがtodoを編集するエディタは、sequence.editorの設定か環境変数GIT_SEQUENCE_EDITORで決まります。環境変数のほうが優先されます。未設定なら、コミットメッセージ用のエディタが使われます。値はシェルが解釈するので、コマンドやスクリプトのパスをそのまま書けます。
つまり対話を避ける近道は、todoを開く代わりに「todoを書き換えて終わるコマンド」を渡すことです。
fixup!コミットの吸収はGIT_SEQUENCE_EDITOR=:で足りる
直す対象が決まっているなら、手順は最短です。修正コミットをgit commit --fixup=<対象>で作っておき、--autosquash付きでrebaseします。--autosquashは、タイトルがfixup! で始まるコミットを、対象の直後に並べ替えてfixupに変えます。
todoの編集は自動で済んでいるので、エディタには何もさせません。何もしないコマンド:を指定します。
git branch backup/feat-pre-rebase
GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash main
git log --oneline検証用の履歴は、add a・add b・add cにfixup! add aとwip cを足した6件です。rebase後は次の5件になりました。
151b9f7 wip c
c78ee52 add c
e9f8f17 add b
137fb1a add a
cc45ab6 basefixup! add aはadd aに吸収され、コミットは1件減っています。rebase.autoSquashをtrueにしておけば、--autosquashの指定は省けます。
任意の整理は「todoを書き換えるスクリプト」で渡す
fixup!の形になっていないコミットを畳みたいときは、todoの該当行を直接書き換えます。gitはスクリプトの第1引数にtodoファイルのパスを渡すので、sedで行頭のpickをfixupに直せます。
cat > ./squash-wip.sh <<'EOF'
#!/bin/sh
sed -i.bak '/wip c/s/^pick/fixup/' "$1"
EOF
chmod +x ./squash-wip.sh
GIT_SEQUENCE_EDITOR=./squash-wip.sh git rebase -i mainsed -i.bakの書き方はGNU版とmacOSのBSD版のどちらでも通ります。Claude Codeが書くスクリプトでも、この形に揃えておくと環境差で落ちません。
todoの中身は、GIT_SEQUENCE_EDITOR=catを付けると確認できます。先頭はpick <hash> # add aの形で、末尾のコメントには、# Rebase <範囲> onto <土台> (N commands)の形で対象の行数が出ます。Claude Codeには、書き換える前にこのtodoを見せて対象行を確かめさせると安全です。
検証の結果、wip c行をfixupにしたrebaseでは、wip cがadd cに吸収され、コミットが1件減りました。add cにはwip cの変更がそのまま入り、メッセージはadd cのままです。
squashは少し事情が違います。fixupと異なり、結合したメッセージを編集するためにコミットメッセージ用のエディタが開きます。ここでも止まらないように、GIT_EDITOR=trueを併せて指定します。メッセージはgitが並べたものがそのまま使われます。
衝突で止まったら--abortで戻す
fixupの対象と同じファイルを、途中のコミットが触っていると、並べ替えで衝突することがあります。検証でも、b.txtを中間のコミットが変更していた履歴でCould not applyと表示されて止まりました。
対処は2つです。
git rebase --abortでrebase開始前の状態に戻す- 戻したうえで、そのコミットだけ手作業で解消する方針に切り替える
Claude Codeに衝突の解消まで任せるなら、「衝突したら中断して報告する。勝手に解消しない」と指示に入れておくほうが安全です。履歴の書き換えは、結果を確かめにくい作業だからです。
rebaseの前にバックアップブランチを切る
履歴を書き換えると、元のコミットはブランチから外れます。そこで、rebaseの直前に同じ位置へブランチを1本切っておきます。git branch backup/feat-pre-rebaseは、今のコミットに名前を付けるだけの操作です。
検証では、rebaseの後にgit reset --hard backup/feat-pre-rebaseを実行すると、元の6件に戻りました。git diff backup/feat-pre-rebaseの出力も空で、内容も一致しています。確認が済んだら、バックアップのブランチは削除して構いません。
reflogにもrebase (finish)などの記録が残るので、バックアップを忘れたときの保険にはなります。ただ、reflogは位置の番号で探す作業になります。名前の付いたブランチのほうが、頼む側も頼まれる側も迷いません。
リモートに公開済みのブランチで書き換える場合は、git push --force-with-leaseが必要です。この操作は、プロンプトで人が確かめる段階に残すのが無難です(許可ルールは後述)。
許可ルールの落とし穴は、環境変数の前置き
ここからが、Claude Code側の設定です。permissionsのページによると、Bashのルールは、コマンド文字列を*付きのパターンと照合します。Bash(git rebase *)のようにサブコマンドの後ろに*を置くと、git rebaseで始まるコマンドが対象になります。
そしてGIT_SEQUENCE_EDITOR=: git rebase -i mainは、gitの前に変数の代入が付いています。同ページは、先頭の代入のうち「既知の安全な環境変数」だけを取り除いてからルールと照合する、と書いています。例に出ているのはNODE_ENV=test npm testです。それ以外の変数の代入が付くと、allowルールは一致しません。
denyとaskは逆で、先頭の代入があっても一致します。たとえばBash(rm *)のdenyは、FOO=bar rm -rf tmp/にも効きます。
GIT_SEQUENCE_EDITORが安全な変数に入っているかどうかは、このページには書かれていません。そのため、最初に一度実行して、確認プロンプトが出るかを見るのが確実です。出る場合の対策は3つあります。
- 毎回プロンプトで承認する。履歴の書き換えを人が見るなら、これが一番素直です
- 環境変数を中に隠した専用スクリプトを用意し、
Bash(./scripts/squash-wip.sh *)だけをallowする - 完全一致のルール(
*なし)で、決まったコマンド1本だけをallowする
2は手軽ですが、スクリプトの中身を後から書き換えられると何でも通ってしまいます。スクリプト自体をdenyか確認付きの編集対象にして、レビューのある場所に置いてください。
複合コマンドにも注意が要ります。同ページの説明では、&&や;でつないだコマンドは、全部の部分が許可されていないと確認なしで走りません。git branch backup/x && GIT_SEQUENCE_EDITOR=: git rebase ...と1行にすると、git branchとgit rebaseの両方にルールが必要です。git branchを広くallowすると、ブランチ削除のgit branch -Dまで通る点も、ルールを書く前に考えておきます。
推奨する構成の例
履歴の書き換えは確認を残し、公開に当たる操作は止める構成です。askはallowより先に評価されるので、git rebaseを確認付きにしつつ、周辺を許可する書き方もできます。
{
"permissions": {
"allow": [
"Bash(git log *)",
"Bash(git commit *)"
],
"ask": [
"Bash(git rebase *)"
],
"deny": [
"Bash(git push *)"
]
}
}評価の順は、denyが先、askがその次、allowが最後です。先に一致したものが結果を決め、具体的なルールが優先されるわけではありません。
Bash(git push *)のdenyは、git -C . pushのような書き方は止められません。同ページがこの例を挙げています。文字列照合のルールは、強制力のある境界ではないということです。確実に止めたい場合は、サンドボックスやPreToolUseフックで検査します。
秘密情報を含むコミットの流出を防ぎたいなら、gitleaksをhookに組み込む手順と組み合わせると、整理前の履歴からの混入も検査できます。
CLAUDE.mdに書いておく運用ルール
毎回の指示を省くために、手順をClaude CodeのCLAUDE.mdに書いておけます。ただし、CLAUDE.mdは許可の判断を変えません。permissionsのページが、プロンプトやCLAUDE.mdの指示は「Claudeが何を試みるか」を形作るだけで、許可の判定には影響しないと明記しています。
書く内容は、手順の固定が中心です。
## コミット整理(git rebase -i)
- rebase前に必ず `git branch backup/<現在のブランチ>-pre-rebase` を作る
- todoの編集は GIT_SEQUENCE_EDITOR にスクリプトかコマンドを渡す。エディタは開かない
- fixup! コミットは `GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash <土台>`
- 衝突したら `git rebase --abort` で戻し、報告する。勝手に解消しない
- push は行わない。force push が必要なときは理由を添えて確認を取る履歴を調べる側の運用はgit履歴から経緯を調べる手順にまとめています。整理の前に、どのコミットが何のために入ったかを調べておくと、畳む対象を間違えにくくなります。
rebaseの後にテストを走らせる--exec
rebaseでコミットを並べ替えると、途中のコミット単体ではビルドが通らなくなることがあります。--execは、各コミットの後ろにコマンドを差し込んで確かめる仕組みです。gitのドキュメントによると、失敗したコマンドがあればrebaseは終了コード1で中断します。--autosquashと併用したときは、fixupの束の最後にだけexec行が付きます。
GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash \
--exec 'npm test' main検証では、--exec 'test -f a.txt'を付けたrebaseが成功しました。実際のプロジェクトでは、テストコマンドを入れます。ここでも許可ルールの話は変わらず、npm testが許可されていないとプロンプトが出ます。
ほかにも--update-refsがあります。rebase対象のコミットを指している別のブランチを、一緒に付け替えるオプションです。ワークツリーでチェックアウト中のブランチは更新されない、とドキュメントにあります。積み上げ型のブランチを整理するときに使えます。
まとめ
非対話のrebaseは、todoを書き換える部分だけをコマンドに置き換えれば成立します。fixup!の吸収はGIT_SEQUENCE_EDITOR=:と--autosquashの組み合わせ、それ以外の整理はsedなどでtodoを直すスクリプトです。
難所は履歴よりも許可ルールにあります。環境変数の前置きはallowの一致を外すので、実行前に1回プロンプトの出方を確かめ、git rebaseはaskに残す構成から始めると、承認の位置を決めやすくなります。公開済みのブランチは、git push --force-with-leaseの前に人が差分を見ます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。