rebase-stack

スタックした PR (下段の上に上段を積む構成) を、下段の変更に追随させて上段へ rebase 伝播し、各段を検証して push する。下段がマージされた・衝突した・リモートが消えた等の分岐を判定しながら段を上がる。ユーザーが「スタックを積み直して」「rebase を伝播して」「/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 をやり直すことになる。

  1. git switch <その段のブランチ>
  2. git rebase <ひとつ下の段>
  3. 検証(Step 3)
  4. 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 として扱ってよい