巨大PRのせいでレビューが1週間も遅れる時に使いたい、スタックPRの実務移行法
AIツールを導入し、コード生成のスピードは圧倒的に速くなりました。しかし、レビューのスピードがそれに追いついていません。2024年のDORA(DevOps Research and Assessment)レポートによると、AI導入率が高いチームはPRのサイズが平均154%大きくなり、レビューに要する時間が91%も増加しました。結果として、デプロイサイクルはむしろ遅くなりました。1,000行を超えるコードを一度にレビューしろと投げ出されれば、誰でも目が疲弊します。この混乱に終止符を打つには、コードを層状に積み上げるスタックPR(Stacked PRs)のチェーン構造に移行する必要があります。
1,000行の塊を3つのブランチに分割する
むやみにコードを切り捨てると、コンパイルが壊れたり依存関係が破綻したりします。確実な基準が必要です。単一PRは変更コード200行以下、修正ファイル10個未満に制限し、レビュアーが15分で終わらせられるようにしてください。巨大な機能1つは、最大3つの子ブランチチェーンに分割します。
- Layer 1: DBスキーマ、Entity、DTO
- Layer 2: ドメインロジック、サービス実装体
- Layer 3: Controller、APIエンドポイント
この順序を徹底的に守ります。ブランチ名も feature/<機能名>/stack-<段階>-<説明> の形式で固定します。すでに絡まってしまった1,000行のブランチ(legacy-giant-branch)をチェーンに分解するプロセスはシンプルです。
まず main ブランチを最新化し、元のバックアップを取ります。
`bash
git checkout main
git pull origin main
git checkout legacy-giant-branch
git branch backup/legacy-giant-branch
`
次に main から Layer 1 ブランチを切り出し、スキーマ関連のコミットのみをチェリーピックして載せます。
`bash
git checkout -b feature/order-refactor/stack-1-schema main
git cherry-pick
git push origin feature/order-refactor/stack-1-schema
`
これで Layer 1 を親として、Layer 2 と Layer 3 を順次伸ばしていけばOKです。
`bash
git checkout -b feature/order-refactor/stack-2-service feature/order-refactor/stack-1-schema
git cherry-pick
git push origin feature/order-refactor/stack-2-service
git checkout -b feature/order-refactor/stack-3-controller feature/order-refactor/stack-2-service
git cherry-pick
git push origin feature/order-refactor/stack-3-controller
`
ツールの選択も悩みの種です。純粋な Git CLI はコストがかかりませんが手作業が多く、Graphite のような専用ツールは便利ですがコストとインフラへの依存性が生じます。
| 評価項目 |
Pure Git CLI |
GitHub CLI (gh stack) |
Graphite / External Tools |
| 依存関係の追跡 |
手動ブランチ Base 設定 |
ローカル .git および GitHub metadata |
別途リモートエンジンおよび Local DB |
| リベースの自動化 |
個別ブランチの手動作業 |
gh stack rebase コマンド実行 |
コミット変更時に下位チェーンを自動処理 |
| 学習曲線 |
高い(Git の原理熟知が必要) |
低い(CLI 拡張のインストール) |
中間(別途専用 UI への適応が必要) |
| 運用安定性 |
100% 標準 Git 互換 |
パブリックプレビュー状態 |
非常に高い |
親PRが修正された際のリベース競合を防ぐ
スタックPRを使っていると、親PRの修正リクエストが来る瞬間が最も苦痛です。コミットハッシュが変わることで、その下についている子ブランチがすべて壊れてしまうからです。そのまま git rebase を実行すると、コミットが重複して入ってしまいます。git rebase --onto 構文で切り取る範囲を指定する必要があります。
Layer 1 が修正された場合は Layer 2 に移動し、修正前の Layer 1 のコミット時点から現在の HEAD までを、新しい Layer 1 の終点の上に再配置します。
`bash
git checkout feature/order-refactor/stack-2-service
git rebase --onto feature/order-refactor/stack-1-schema feature/order-refactor/stack-2-service
git push --force-with-lease
`
Layer 3 も同様に新しい Layer 2 の上に移動させます。
`bash
git checkout feature/order-refactor/stack-3-controller
git rebase --onto feature/order-refactor/stack-2-service feature/order-refactor/stack-3-controller
git push --force-with-lease
`
この作業を手動で毎回行えば、人間は必ずミスをします。シェルスクリプトを作成し、順次リベースを自動化してください。
`bash
#!/usr/bin/env bash
set -e
STACK_BRANCHES=(
"feature/order-refactor/stack-1-schema"
"feature/order-refactor/stack-2-service"
"feature/order-refactor/stack-3-controller"
)
TARGET_BASE="main"
git fetch origin
CURRENT_PARENT="$TARGET_BASE"
for BRANCH in "STACKBRANCHES[@]";dogitcheckout"BRANCH"
OLD_BASE=(gitmerge−base"CURRENT_PARENT" "BRANCH"2>/dev/null∣∣echo"")if[−n"OLD_BASE" ]; then
git rebase --onto "CURRENTPARENT""OLD_BASE" "BRANCH"∣∣exit1figitpush−−force−with−leaseorigin"BRANCH"
CURRENT_PARENT="$BRANCH"
done
`
Git の設定に競合解決履歴の記録機能を有効にしておくことも忘れないでください。繰り返される競合を自動でスキップしてくれます。
`bash
git config --global rerere.enabled true
git config --global rerere.autoupdate true
git config --global rebase.autoStash true
git config --global rebase.updateRefs true
`
CI ランナーのコスト浪費とマージ順序の狂いを遮断する
PRを3つに分割すると、CI ランナーも3倍回るという問題が生じます。すべての PR で重い E2E テストを実行する必要はありません。下位レイヤーでは堅実な単体テストのみを実行し、全体の統合検証は最上位 PR でのみ実行するように GitHub Actions 環境を構築してください。
.github/workflows/ci.yml にスタック位置の条件文を追加します。
`yaml
name: Stack-Aware CI Pipeline
on:
pull_request:
branches: [ main ]
jobs:
fast-lint-and-unit-test:
name: Fast Validation (All Layers)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Linter & Unit Tests
run: |
npm ci
npm run test:unit
heavy-integration-test:
name: Full E2E Test (Top Layer Only)
runs-on: ubuntu-latest
if: |
github.event.pull_request.stack == null ||
github.event.pull_request.stack.position == github.event.pull_request.stack.size
steps:
- uses: actions/checkout@v4
- name: Run Heavy Integration & E2E Tests
run: |
npm ci
npm run test:e2e
`
組織レベルでのブランチ保護ルールの設定も必須です。
- GitHub Merge Queue 機能を有効にし、承認されたスタックPRが順番にマージされるように強制します。
- Repository Rulesets で 'Require Linear History' を有効にし、不要なマージコミットを防ぎます。
- 'Require Status Checks to Pass Before Merging' により、下位PRがマージされていない状態で上位PRが先に main に混ざる事故を防ぎます。
チームのレビュー文化と Squash Merge 後の UI 崩れへの対処
スタックPRを正常に機能させるには、レビュアーが全体像を把握できなければなりません。.github/PULL_REQUEST_TEMPLATE.md にスタックマップを明記してください。
`markdown
Stack Overview
この PR は Order Refactoring Stack チェーンの一部です。
- #101 - feature/order-refactor/stack-1-schema (DB Migration)
- [Current PR] #102 - feature/order-refactor/stack-2-service (Business Logic)
- #103 - feature/order-refactor/stack-3-controller (API Controller)
Current Layer Scope
- OrderService 内の決済状態変更ロジックの実装
- ドメインイベント発行処理および Repository 連携テストの追加
Dependency Status
- Base PR: #101 (Must be merged first)
`
下位PR #101 が Squash Merge で main に取り込まれた後、子PR #102 の GitHub UI Diff が急激に膨れ上がります。すでにマージされた #101 のコードまでがすべて子PRの変更事項として捉えられてしまう現象です。Git Commit Graph 上で Squash コミットの SHA と親コミットの SHA が変わってしまったために発生します。慌てずに以下の手順で子ブランチのコミットのみを main の上に引き上げればOKです。
`bash
git log --graph --oneline
git checkout feature/order-refactor/stack-2-service
git fetch origin
git rebase --onto origin/main feature/order-refactor/stack-2-service
git push --force-with-lease origin feature/order-refactor/stack-2-service
`
巨大な1,000行のPRを分割する作業は、単に Git の技術を扱う問題ではありません。チームのコード検査方式とデプロイチェーン全体を綺麗に整えるエンジニアリング作業に近いです。散らかったレガシーブランチに直面したら、まずはバックアップを作成し、3段階のレイヤーに切り分けてみてください。レビュアーの疲弊が軽減されるのはもちろん、自分自身もコードをより明確にコントロールできるようになります。