Claude Codeでgit range-diffを使いrebase後のPR差分を確かめる手順
rebaseやforce push前後の2つのコミット列をgit range-diffで突き合わせ、レビュー済みの内容が変わっていないかをClaude Codeに読ませて確かめる手順です。
rebaseしたPRが「中身は同じはず」かどうかは、git diff main...featを眺めても分かりません。土台が動いた分の変更と、履歴を書き換える途中で入った変更が、同じ差分に混ざるからです。git range-diffは、rebase前とrebase後の2つのコミット列を1コミットずつ対応づけ、変わったコミットだけを「差分の差分」として出します。
この記事では、git range-diffの読み方、Claude Codeに実行と要約を任せる流れ、許可ルールの書き方、そして手元で確かめた出力例を扱います。PRを作る側の流れは公式の「Create pull requests」にあるとおり、Claude Codeにcreate a prと頼む形が基本です。そのPRを後から整える段階で、範囲を絞って確認するのがここでの主題です。
git range-diffは何を比べるコマンドか
git range-diffは、パッチ列の2つの版、より一般には2つのコミット範囲(マージコミットは無視)の違いを示します。コミットの対応づけには、作者情報・コミットメッセージ・コミットの差分を合わせた「パッチ同士の差」が小さいかどうかを使います。
引数の取り方は3通りです。
| 書き方 | 意味 |
|---|---|
<range1> <range2> | 意味前者が古い版、後者が新しい版 |
<rev1>...<rev2> | 意味<rev2>..<rev1> <rev1>..<rev2>と同じ |
<base> <rev1> <rev2> | 意味<base>..<rev1> <base>..<rev2>と同じ |
rebase後の確認では3つ目の形が最も扱いやすく、<base>は分岐点そのものでなくても構いません。マニュアルには、rebase直後にその差分を見る例として次の行があります。
git range-diff @{u} @{1} @@{u}は上流ブランチ、@{1}はreflogで一つ前のブランチ先端、@は現在の先端です。マージ競合を解消したrebaseの直後に、競合解消で何が加わったかを見る用途が想定されています。
出力記号の読み方
出力の各行は、左の番号が旧版、右の番号が新版です。マニュアルの例では、記号が次の意味で使われています。
range-diffの対応記号
=(同一)
旧版と新版でパッチが一致しています。中身は変わっておらず、rebaseで土台だけが動いたコミットです。
!(変更あり)
対応は取れたが、差分かコミットメッセージが変わっています。直下に差分の差分が続きます。
削除と追加
記号
<は旧版にだけ、>は新版にだけあるコミットです。対応が取れなかったか、並び替えで消えて増えたコミットが該当します。
出力は新版の順に並びます。レビュー済みのコミットが=で並んでいれば、そのコミットの内容は据え置きと読めます。
Claude Codeに任せる範囲と、人が決めること
git range-diff自体は読み取りだけのコマンドで、ワーキングツリーも履歴も変えません。Claude Codeに頼むのは「どの2つの版を比べるか」を揃えることと、出力の読み下しです。
公式の使い方ガイドは、Claude Codeを標準入出力でつなぐ例としてgit log --oneline -20 | claude -p "summarize these recent commits"を挙げています。同じ形で、範囲差分を渡せます。
git range-diff main backup/feat-before feat \
| claude -p "!の行だけ、差分の差分を読んで何が変わったか説明して。=の行は触れない"記号の意味を毎回説明したくなければ、CLAUDE.mdに一節を置いておく方法があります。例えば次のような形です。
## rebase後の確認
- rebase前に必ず `git branch backup/<ブランチ名>-before` で旧先端を残す
- 確認は `git range-diff <base> backup/<ブランチ名>-before <ブランチ名>`
- `=` は据え置き、`!` は差分の差分を読む、`<` `>` は消えたコミットと増えたコミットとして報告する
- 競合解消で入った変更と、意図しない追加を区別して列挙する許可ルールを先に決めておく
確認のたびに実行許可を聞かれると、手順が止まります。公式の権限ドキュメントでは、Bash(git log *)のように、サブコマンドの後ろに*を置く書き方が案内されています。Bash(git *)は全てのgitコマンドを許可してしまうため、範囲を絞ります。
{
"permissions": {
"allow": [
"Bash(git range-diff *)",
"Bash(git branch backup/*)"
],
"deny": [
"Bash(git push *)"
]
}
}同じドキュメントに、Bash(git push *)をdenyに置くとgit pushで始まるコマンドが拒否される例があります。ただしgit -C . pushのように書き方を変えたpushはこのルールに一致しません。force pushまでの安全策をこのルールだけに頼らず、pushは自分の手で打つ運用が堅実です。
手元で確かめた出力例
git 2.50.1の空の一時リポジトリで、mainから切ったfeat(2コミット)を用意し、main側に別の行を変更するコミットを足してからrebaseしました。rebase前のfeatはbackup/feat-beforeに残しています。
土台が動いただけのrebase
git range-diff main backup/feat-before feat1: cd91e3d = 1: b647e3d tweak l2
2: d8c7af6 = 2: 7f4da24 add retryコミットのハッシュは変わりましたが、2件とも=です。git diffで見るとmain側の変更が差分に混ざる場面でも、range-diffは「レビュー済みの中身は同じ」と一目で示します。
rebase後に1行を混ぜたケース
rebase後、add retryのコミットに設定行を1つ足して--amendしました。
1: cd91e3d = 1: b647e3d tweak l2
2: d8c7af6 < -: ------- add retry
-: ------- > 2: 03b49ba add retry!ではなく、削除と追加の組になりました。このコミットは追加が1行だけと小さく、1行足すと対応づけの閾値を超えたためと見られます。マニュアルの--creation-factor(既定は60)の説明は、大きな変更が「全面書き換え」と誤判定されるときは値を大きくする、と案内しています。同じ内容のコミットが<と>に割れて出たときは、中身が変わったという合図として読み、git showで旧版と新版を見比べます。
十分に大きいコミットを変えたケース
6行を追加するコミットを作り、その先端をbackup/feat-before2に残してから、rebase後に1行足して--amendしました。
1: 1f80273 = 1: 1f80273 tweak l2
2: 324f618 = 2: 324f618 add retry
3: 417e83a ! 3: 130033a add extra
@@ extra.txt (new)
+d=4
+e=5
+f=6
++debug=true対応が取れて!が付き、足されたdebug=trueだけが差分の差分として出ました。こうなれば、Claude Codeに渡す対象は!の行の下に出た数行に絞れます。
rebase前後を残す手順
range-diffは、旧版を指す参照が残っていないと使えません。流れは次のとおりです。
rebaseの前後で残すもの
- 1
旧先端を名前で残す
git branch backup/feat-beforeを、rebaseの前に打ちます。reflogの@{1}でも指せますが、rebaseやresetを重ねると指す先がずれるため、名前を付けたほうが取り違えません。 - 2
rebaseして競合を解消する
履歴を書き換える作業はここまでです。Claude Codeに競合解消を頼んだ場合も、この段階の変更は次の確認の対象になります。
- 3
range-diffで対応を確かめる
git range-diff main backup/feat-before featを実行し、=が並ぶことを確かめます。!と<>の行は、Claude Codeに差分の差分を説明させたうえで、自分の目でも読みます。 - 4
問題がなければ送る
force pushは
--force-with-leaseを付けた形が使えます。pushのマニュアルにこのオプションの項があり、リモートが想定と違うときに上書きを止める仕組みです。pushは自分で打ちます。
レビュワー側でも同じ確認ができます。force pushされたPRを取り込む前に、手元の旧先端へ名前を付けておきます。
git branch review/pr-v1 origin/feat
git fetch origin
git range-diff origin/main review/pr-v1 origin/feat最初の行で名前を付けないままfetchすると、origin/featは新しい先端に進みます。旧版を指す参照が残るよう、取り込みの前に打つ順序が要点です。
fixupを畳んだrebaseでは何が出るか
レビュー指摘への対応をfixup!コミットで積み、git rebase -i --autosquashで畳んでから再pushする運用があります。この場合、!と<が同時に出るのが正常です。
add a(a.txtの3行目を変更)、add b、fixup! add a(a.txtの9行目を変更)の3コミットを用意し、--autosquashで畳んだ結果が次です。
1: b5146a5 ! 1: b59fc0d add a
@@ a.txt
4
5
6
+ 7
+ 8
+-9
++nine
+ 10
+ 11
+ 12
2: 9c0780d = 2: 59719a2 add b
3: e8e2ab7 < -: ------- fixup! add afixup! add aが旧版にだけ残る<として出て、その内容は新版のadd aに吸収されています。add aが!になり、9行目のnineが差分の差分に現れています。add bは=のままです。つまり、<が出たこと自体は異常ではなく、<のコミットの内容が新版のどこへ入ったかを確かめるのが読み手の仕事になります。
Claude Codeへ頼むときは、この対応を明示した指示にすると要約がぶれません。
旧版(backup/feat-before)と新版(feat)のrange-diffを読んで、次の3つに分けて報告して。
1. =のコミット(据え置き)
2. !のコミット: 差分の差分を引用し、fixupの吸収か、それ以外の変更かを区別する
3. <または>のコミット: 内容が新版のどこへ移ったかを、git showで確認してから述べる
報告に使わない根拠(推測)は書かない。競合解消で入った変更を切り分ける
競合を解消したrebaseの直後は、前述のgit range-diff @{u} @{1} @が使えます。ここで出る!の差分の差分は、競合解消で手を入れた箇所です。Claude Codeに競合解消を任せたなら、この!が解消の中身そのものになります。レビュー済みのコミットで競合解消の変更が出ていれば、その行だけを重点的に読めます。
複数回rebaseした後の注意
@{1}はreflogの一つ前を指します。rebaseを続けて2回行うと、一つ前は1回目の結果になり、最初のレビュー済みの版ではなくなります。レビューを受けた時点の版を比べたいなら、レビューを受けた時点でbackup/feat-reviewedのように名前を残し、その名前を旧版に使います。
確認の限界
マニュアルの「OUTPUT STABILITY」の節は、range-diffの出力が将来のバージョンで変わりうる人間向けの表示で、機械可読ではないと明記しています。スクリプトで=の数を数えて合否判定するような使い方は想定されていません。claude -pに渡して要約させる使い方は、人が読む前提のため、この性質と矛盾しません。
また、対象はコミットの内容の差です。マージコミットは既定で無視されるため、マージを含むブランチでは--diff-merges=remergeのように形式を指定します(マニュアルが推奨する形式はremergeです)。変更の割合が大きいコミットは対応が取れず< >に割れることがあります。その場合は--creation-factorを大きくして再実行するか、git showで突き合わせます。
テスト結果や動作の変化は、range-diffでは分かりません。「差分が同じなら動作も同じ」とは限らないため、rebase後にCIを回す工程は残ります。PRを自動でレビューさせる仕組みはClaude CodeでGitHub PR自動レビューを設定・運用するに、セキュリティ修正のようにレビューの粒度が重要な差分の見方はClaude Codeでセキュリティ修正のPRをレビューする手順にあります。別の文脈で差分を検証させる設計は敵対的レビューで扱っています。
まとめ
rebase後の確認は、git diffで全体を見直すより、range-diffで旧版と新版のコミットを対応づけるほうが的を絞れます。=が並べば据え置き、!なら差分の差分、< >なら中身が割れた合図です。その読み下しをClaude Codeに任せ、force pushの判断は人が持つ分担が現実的です。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。