GitHub stacked pull requestを実際に動かしてみた
こんちには。製造ビジネステクノロジー部の中村( @nokomoro3 )です。
今回はPublic Previewとなった「Stacked pull requests」についてご紹介します。
今回はこの機能を実際にGitHub上とCLIで試してみたので、その内容をご紹介します。
そもそも何を解決する機能なのか
大規模な変更を1つのPull Requestにまとめると、差分が大きくなりレビューに時間がかかるという問題があります。
一方で変更を複数のPull Requestに分割すると依存するブランチのrebaseやbaseブランチの変更を手作業で管理する必要がありました。
Stacked pull requestsは、依存関係のある複数のPull Requestを、順序を持った1つのスタックとして管理する機能です。
たとえば、1つの機能開発を次のように分割できます。
develop
└─ feature/auth: 認証基盤の修正 (PR1)
└─ feature/api: APIの修正 (PR2)
└─ feature/ui: UIの修正 (PR3)
PR2はPR1のブランチをbaseとし、PR3はPR2のブランチをbaseとします。
これにより、それぞれのPull Requestにはそのレイヤーで追加した差分だけが表示されるため、大きな変更を小さく、目的の明確な単位でレビューできます。
主なメリットは次のとおりです。
- 大規模な変更を、レビューしやすい小さなPull Requestに分割できる
- 依存関係を保ったまま、複数のPull Requestを並行してレビューできる
- 各Pull Requestで既存のチェックやブランチ保護ルールを利用できる
- スタック全体、または下位のPull Requestだけをまとめてマージできる
- CLIを使って連鎖的なrebaseやPRのbase設定を自動化できる
つまり、Stacked pull requestsは大きな変更を小さなPull Requestに分割しつつ、ブランチ間の依存関係をGitHubに管理してもらうための機能です。
GitHubの公式発表でも、レビュー単位を小さくしながら大きな変更を進められる点が主な目的として紹介されています。
(決して大きなPull Requestをまとめてさらに大きくする機能ではありません!!!)
本記事では、Stacked pull requestsをGitHub上とCLIの両方から実際に操作し、それぞれの機能や基本的なワークフローをご紹介します。
GitHub上でスタックを作成する
以下のようなスタック化したPull Requestがある状態からスタートします。

GitHub上で当該Pull Requestのいずれかを開くと、以下のような画面となります。

Preview stackを押下すると、以下のような画面となります。

Create stackを押下すると、以下のように管理されます。

CLIによるスタック操作
GitHub上での見た目はそこまで変わらないのですが、CLIと組み合わせることで強力な機能が使えます。
インストール
GitHub CLIがインストールされている場合は、以下のように拡張機能をインストールするだけとなります。
gh extension install github/gh-stack
インストール後の確認です。
gh extension list
# NAME REPO VERSION
# gh stack github/gh-stack v0.1.0
よく使う基本フロー
gh stackコマンドの詳細は後述しますが、以下を基本フローとして押さえておけば良さそうです。
# スタックの1段目を開始(スタック管理の監視)
gh stack init feature/auth
# ベースを指定したい場合は --base オプションを使用
gh stack init --base develop feature/auth
# スタックの2段目、3段目を追加
gh stack add feature/api
gh stack add feature/ui
# 状態確認
gh stack view
# スタックの変更をGitHubへ公開、PRの作成やstackの作成をまとめて行う
gh stack submit
# スタックをローカルとリモートで同期したり、スタック内のブランチ構成を整合させる処理を行う
gh stack sync
# スタックをまとめてマージ
gh stack merge
ローカル側の操作
CLIによるスタック操作について、まずはローカル側の操作コマンドは以下のようになります。
| コマンド | 役割 |
|---|---|
| init | 新しいスタックを作る/既存ブランチをスタックとして登録する |
| add | 現在のスタックの一番上に、新しいブランチを追加する |
| checkout | スタック番号・PR番号などから、そのスタックを取得してチェックアウトする |
| modify | ブランチの並べ替え、名前変更、統合、除外などを対話的に行う |
| unstack | ローカル追跡とGitHub上のスタック関係を解除する |
| view | 現在のスタック、ブランチ、PR状態を表示する |
init: スタック管理の開始
init でスタック管理を開始します。
gh stack init feature/auth
複数ブランチの場合、以下でまとめて指定することも可能です。
gh stack init feature/auth feature/api feature/ui
gh stack init では既存ブランチであれば取り込み、同名ブランチが存在しなければ新しいブランチを作成します。
またベースを指定しない場合はデフォルトブランチがベースとなります。
ベースを指定したい場合は --base オプションを使用します。
gh stack init --base develop feature/auth
add: スタック管理への追加
add でスタックの一番上(後続)に新しいブランチを追加します。
gh stack add feature/ui
checkout: スタックのチェックアウト
checkout で別のスタックをローカルに持ってくることができます。
引数を指定しなければ、ローカル/GitHub上のスタックを対話的に選択できます。
gh stack checkout
# Checkout a stack
# All Local Remote
#
# # Branches Base Status Type Created
# › 446 feature/auth...feature/ui develop ▆▆▆▆▆ Remote 1h ago
#↑↓ navigate · ←→ tabs · / search · enter select · esc quit
上記の 446 という番号は、既存のpull requestには割り当てられていない番号のようでした。
そのためGitstack用の採番が行われているようです。
引数で指定する場合はstackの番号を指定する必要はなく、いずれかのpull requestの番号を指定すればチェックアウトが可能でした。
gh stack checkout {PRの番号}
modify: スタック構造の変更
modify でスタック構造を対話画面で編集します。
gh stack modify
できることは、ブランチの並べ替え・リネーム・追加・除外・隣接ブランチへの統合などです。
競合が起きた場合は次を使います。
gh stack modify --continue
gh stack modify --abort
編集後、GitHub側にも反映するには gh stack submit が必要です。
unstack: スタックの関連付けの削除
unstack でスタックとしての管理・関連付けを解除します。
gh stack unstack
ローカルの追跡情報だけを外す場合は以下のように実行します。
gh stack unstack --local
これは基本的にスタック関係の解除で、ブランチやPRそのものは削除されません。
view:スタックの表示
view で現在のスタックを表示します。引数未指定の場合は対話的な表示となります。
gh stack view

単純なテキストまたはJSON形式で表示する場合は以下のように実行します。
gh stack view --short
gh stack view --json
リモート操作
リモート側の操作コマンドは以下のようになります。
| コマンド | 役割 |
|---|---|
| push | スタック内のブランチだけをリモートへpushする |
| submit | push、PR作成・更新、GitHub上のスタック作成までまとめて行う |
| sync | fetch、rebase、push、PR・スタック状態の同期をまとめて行う |
| rebase | trunkから上へ、スタック全体を順番にrebaseする |
| link | ローカル追跡を使わず、既存ブランチやPRをGitHub上でスタックにする |
| merge | スタックをGitHub上でまとめてマージする |
push: スタック内のブランチをリモートへpush
push でスタック内のブランチをリモートへpushします。
gh stack push
PRの作成はしません。つまりまだリモート側にstackは作成されません。
submit: スタックの変更をGitHubへ公開
submit は普段スタックの変更をGitHubへ公開するときの中心的なコマンドとなります。
gh stack submit
引数を指定しない場合、以下のような対話画面でPRの文面を記入することになります。

submit は次をまとめて行うことができます。
- 全ブランチをpushする
- 必要なPRを作成する
- 既存PRのbaseを更新する
- GitHub上のstackを作成・更新する(ただしPRが2個以上でないとスタックは作成されない)
重要な注意点は最後の部分で、PRが1この場合はローカルではスタックが作成されていても、GitHub上ではスタックは作成されません。
複数のブランチをスタックに入れている場合のみ、GitHub上でスタックが作成されます。
対話画面を使わず自動生成する場合は以下を実行します。(PRの本文には何も記載されません)
gh stack submit --auto
--auto を指定した場合は、新規PRがデフォルトでDraftになります。
そのため、すぐレビュー可能にする場合は以下のように指定します。
gh stack submit --auto --open
sync: スタックの同期と構成の整理
sync はスタックをローカルとリモートで同期したり、スタック内のブランチ構成を整合させる処理を行います。
gh stack sync
例えば、以下のようなユースケースで使います。
- trunk(スタックのベース)の最新変更を取り込み、スタック全体へ反映したい
- スタック内の下位ブランチの変更を、依存する上位ブランチへ反映したい
- リモートで更新されたスタックブランチを取り込みたい
- 既存PRとGitHub上のスタックとの関連付けを整合させたい
基本的に sync は新しいPRを作成しないところは注意が必要です。
新規PRの作成も含めて公開する場合は submit を使います。また modify でスタック構成を変更した場合も submit を使います。
rebase: スタック全体を順番にrebase
rebase はtrunk(stackのベースとなるブランチ)の最新状態を取り込み、スタック内の後続のブランチまで順番にrebaseします。
gh stack rebase
通常の全体同期であれば rebase を単独で実行せず sync だけで足ります。
rebase を使うのは主に以下のような場合となります。
#----------------------------------
# 一部分だけrebaseしたいケース
#-----------------------------
# 現在ブランチからtopまでrebaseする
gh stack rebase --upstack
# trunkから現在ブランチまでrebaseする
gh stack rebase --downstack
# trunkを取り込まず、ブランチ間だけrebaseする
gh stack rebase --no-trunk
#-----------------------------------
# ローカル履歴だけ整えてpushしたいケース
#-----------------------------------
gh stack rebase
# その後push
gh stack push
link: リモート側のみで既存ブランチ/PRをスタックにする
link はGitHub上だけで既存ブランチ/PRをスタック化します。
これはgit-town、jj、Saplingなど、別ツールでローカルブランチを管理しており、GitHubのStacked PR機能だけを使いたい場合に向いています。
gh stack link feature/auth feature/api feature/ui
merge: スタックをまとめてマージ
merge はスタックをまとめてマージします。
gh stack merge
PRを指定すると、そのPRとそれより下のPRをまとめてマージします。
処理はatomicで、どれか1つでもマージできなければ全部マージされません。
マージ方式は --merge、--squash、--rebase から選ぶことができます。
ナビゲーション(スタック内のブランチ移動)
基本的にはブランチの移動は git checkout で行いますが、それとは別にスタック内を相対的に移動するコマンドが用意されています。
| コマンド | 移動先 |
|---|---|
| bottom | trunkに最も近いブランチ |
| top | trunkから最も遠いブランチ |
| up | top方向へ移動 |
| down | trunk方向へ移動 |
| trunk | main や develop などの基底ブランチ |
| switch | スタック内のブランチを対話的に選択 |
# bottom: trunkに最も近いブランチ
gh stack bottom
# top: trunkから最も遠いブランチ
gh stack top
# up: top方向へ移動
gh stack up
# up: top方向へ移動(3段目)
gh stack up 3
# down: trunk方向へ移動
gh stack down
# down: trunk方向へ移動(2段目)
gh stack down 2
# trunk: main や develop などの基底ブランチ
gh stack trunk
# switch: スタック内のブランチを対話的に選択
gh stack switch
移動の際にマージ済みブランチは自動的にスキップされる点は注意が必要です。
まとめ
いかがでしたでしょうか。
GitHub上のUIはそこまで大きく変わっていませんが、CLIによる操作はかなり複雑なことができるようになっていることがわかりました。
かなりGitHubのコアに関わるアップデートで、開発の苦労についてもXのポストから伝わってきました。
ぜひ活用されてください。
本記事がみなさまのご参考になれば幸いです。



