rebase-stack
スタックした PR (下段の上に上段を積む構成) を、下段の変更に追随させて上段へ rebase 伝播し、各段を検証して push する。下段がマージされた・衝突した・リモートが消えた等の分岐を判定しながら段を上がる。ユーザーが「スタックを積み直して」「rebase を伝播して」「/rebase-stack」等を指示したときに使う。
SKILL.mdhome/.claude/skills/rebase-stack/SKILL.md
GitHubスタックの各段を下から順に rebase して push します。単なる繰り返しではなく、段ごとに分岐の判定が要るのがこの作業の本体です。
進め方
1. スタックの並びを確定する
- 引数に並びがあればそれを使う(下段 → 上段の順)。渡された段の作成者を1つずつ確かめる
(
gh pr view <PR> --json author -q '.author.login')。ユーザー本人でない段があれば、その段から上は処理せず報告する (no_operating_on_others_prs.md)。各段はgit push --force-with-leaseで履歴を書き換えるため、他人の段が混ざると取り返しがつかない - 無ければ
gh pr list --author "@me" --state open --json number,headRefName,baseRefNameで base の連鎖から組み立てる (--author "@me"が入っているので、この経路では他人の PR は拾わない) - 作業前に
git statusがクリーンであることを確認する。 未コミット変更があれば止めてユーザーに見せる(stash で移送する羽目になる) - 確定した並びを先に提示してから着手する。段数と順序を取り違えたまま進むのがいちばん高くつく
2. 各段を下から処理する
段ごとに以下を順に行う。上段から始めない。 下段が固まっていないと、上段の rebase をやり直すことになる。
git switch <その段のブランチ>git rebase <ひとつ下の段>- 検証(Step 3)
git push --force-with-lease
現在いるブランチを毎回確認する。 段を上がるたびに git branch --show-current で確かめ、意図した段にいることを確認してから編集・コミットする。
3. 分岐の判定
各段で以下を疑う。判定を飛ばして次の段へ進まない。
| 症状 | 疑うこと | 対応 |
|---|---|---|
| rebase が0件で終わった | その段は既に下段へ取り込まれている(マージ済み) | スタックが縮んだ可能性。PR の状態を確認し、ユーザーに報告してから並びを組み直す |
--force-with-lease が stale info で拒否された |
リモートブランチが消えているか、他所から push された | 勝手に --force へ切り替えない。リモートの状態を確認してユーザーに報告 |
| 衝突した | 下段の変更と上段の変更が同じ箇所に当たった | 自動解決しない。 git rebase --abort するか衝突を残すかを含めてユーザーに判断を仰ぐ |
| push 後に PR の差分が想定より大きい | base の付け替えが漏れている | gh pr view <PR> --json baseRefName を確認 |
3-1. 上段の差分が壊れたら作り直す
下段の履歴が入れ替わると(force push・rebase・squash マージ)、上段の PR の差分に下段の内容まで混ざります( split_and_stack_prs.md )。上段に他の人のレビューが付いていれば作り直さず、git merge で取り込みます。
復旧は履歴ベースの手段では通りません。git merge は衝突が大量に出て、片側を採ると主題か修正の
どちらかが消える。cherry-pick は選別基準が立たない(下表)。3点比較( A...B )は merge-base が遡りすぎて
下段の内容を拾う。ツリー同士を直接比較して作り直すのが唯一通った手段です(手順の記録は無い。次に踏んだら、実際に打ったコマンドを残す)。
cherry-pick の選別は3通り試して全部駄目でした。どれもやってみるまで駄目と分かりません。
| 選び方 | 期待すること | 駄目な理由 |
|---|---|---|
| 件名で照合 | 下段に無い件名 = 上段固有 | マージ済みの分も「下段に無い」ので固有に見える |
| 起点を指定 | 上段の本体が入ったコミット以降 | 他人のコミットが base から流れ込んでいる |
| ファイル単位 | 上段が触ったファイルだけ | merge-base が遡りすぎる(14本のはずが58本出た) |
作り直すとき、下段で入った修正を打ち消すファイルは除外する。上段はその修正を知らないので、 差分に巻き戻しが混ざります。
4. 検証の粒度
全段でフルテストを回すと時間がかかりすぎる。段ごとは軽い検証、最上段で通しの検証にする。
- 各段: ビルドと型チェック、リンタ(プロジェクトの規定コマンド。Go なら
go vet、TS ならtsc --noEmit等) - 最上段: テスト一式。
run_in_background: trueで流し、待つ間に完了報告をまとめる
各段の検証が落ちたらそこで止める。落ちたまま次の段へ進まない(原因が下段か上段か切り分けられなくなる)。
5. 完了報告
- 段ごとに ブランチ / rebase の結果(何コミット乗ったか)/ 検証 / push の可否 を1行で並べる
- スタックが縮んだ場合は必ず明示する(マージ済みの段があった等)
- 止めた場合は、どの段のどの分岐で止めたかと、次にユーザーが判断すべきことを書く
出力の目安
- 全段成功: 段ごと1行の表 + 「全段 push 済み」。冗長な rebase ログは貼らない
- 途中で止めた: 止めた段・症状・判定・ユーザーに求める判断を簡潔に
してはいけないこと
- 作業ツリーが汚れたまま始めること。stash での移送が必要になり、ブランチを取り違える事故に直結する
- 今いるブランチを確認せずに編集・コミットすること。上段の修正を下段のブランチで書くと、あとで移送する羽目になる
- rebase が0件で終わったのを流すこと。マージ済みのサインで、気づかないとスタックの構成を誤ったまま進む
- 衝突を自動解決すること。下段と上段のどちらの意図を残すかは実装者が決める
--force-with-leaseを--forceに切り替えること。lease が拒否したのには理由がある- 上段から処理すること、途中の段を飛ばすこと
- 各段の検証が落ちたまま次へ進むこと
- main / master への直接 push
例外
- ユーザーが「衝突はこう解決して」と方針を明示した場合は、その方針で解決してよい
- 段数が1(スタックでない)の場合は、通常の rebase として扱ってよい