GitHub Stacked PRs
GitHub 堆叠式 PR
一款用于管理堆叠分支和拉取请求的 GitHub CLI 扩展。
堆叠式 PR 将大型变更拆分为一系列小型、可评审的拉取请求,这些请求相互依赖、层层递进。gh stack 能自动处理那些繁琐的工作——创建分支、保持分支的变基状态、设置正确的 PR 基础分支,以及在各层级之间导航。
Note
堆叠式 PR 目前处于私人预览阶段。除非你的仓库已启用该功能,否则此 CLI 及所提及的功能将无法使用。 你可以通过 gh.io/stacksbeta 注册加入等待名单。
安装
gh extension install github/gh-stack
需要 GitHub CLI(gh)v2.0 或更高版本。
AI 代理集成
安装 gh-stack 技能,以便您的 AI 编码代理能够了解如何处理堆叠 PR 和 gh stack CLI:
gh skill install github/gh-stack
快速开始
# Start a new stack (creates and checks out the first branch)
gh stack init
# ... make commits on the first branch ...
# Add another branch on top
gh stack add api-endpoints
# ... make commits ...
# Push all branches
gh stack push
# View the stack
gh stack view
# Open a stack of PRs
gh stack submit
工作原理
栈(stack) 是分支的有序列表,其中每个分支都基于其下方的分支构建。栈的底部(bottom) 基于主干(trunk) 分支(通常为 main)。
frontend → PR #3 (base: api-endpoints) ← top
api-endpoints → PR #2 (base: auth-layer)
auth-layer → PR #1 (base: main) ← bottom
─────────────
main (trunk)
栈底是距离主干最近的分支,栈顶是距离主干最远的分支。每个分支都继承自其下方的分支。导航命令(up、down、top、bottom)遵循此模型:up 远离主干,down 靠近主干。
提交时,gh stack 会为每个分支创建一个 PR,并在 GitHub 上将它们链接为一个栈(Stack)。每个 PR 的基准分支都设置为栈中其下方的分支,因此审阅者只能看到该层的差异。
本地跟踪
栈元数据存储在 .git/gh-stack(一个 JSON 文件,不会提交到仓库)中。它会跟踪哪些分支属于哪个栈以及它们的顺序。中断变基期间的变基状态单独存储在 .git/gh-stack-rebase-state 中。
命令
gh stack init
在当前仓库中初始化一个新栈。
gh stack init [flags] [branches...]
在本地初始化新的堆栈。在交互模式下(不带参数),会提示输入分支名称,并提供将当前分支用作第一层的选项。
当指定明确的分支名称时,会自动采用现有分支,并创建任何缺失的分支。除非使用 --base 覆盖,否则主干默认为仓库的默认分支。
自动启用 git rerere,以便在变基过程中记住冲突解决方案。
| 标志 | 描述 |
|---|---|
-b, --base <branch> |
堆栈的主干分支(默认为仓库的默认分支) |
示例:
# Interactive — prompts for branch names
gh stack init
# Non-interactive — specify branches upfront
gh stack init feature-auth feature-api feature-ui
# Use a different trunk branch
gh stack init --base develop feature-auth
# Adopt existing branches into a stack
gh stack init feature-auth feature-api
gh stack add
在当前堆栈顶部添加一个新分支。
gh stack add [flags] [branch]
在当前 HEAD 处创建一个新分支,将其添加到栈顶,并检出该分支。必须在栈的最顶层分支上运行此命令。如果未指定分支名称,将提示输入。
您可以选择在 add 流程中暂存更改并创建提交。当提供 -m 但未明确指定分支名称时,分支名称将以日期+短标题格式自动生成(例如,03-24-add_login)。
| 标志 | 描述 |
|---|---|
-A, --all |
暂存所有更改(包括未跟踪的文件);需要 -m |
-u, --update |
仅暂存对已跟踪文件的更改;需要 -m |
-m, --message <string> |
在创建分支前使用此消息创建提交 |
注意:
-A和-u互斥。
示例:
# Create a branch by name
gh stack add api-routes
# Prompt for a branch name interactively
gh stack add
# Stage all changes, commit, and auto-generate the branch name
gh stack add -Am "Add login endpoint"
# Stage only tracked files, commit, and auto-generate the branch name
gh stack add -um "Fix auth bug"
# Commit already-staged changes and auto-generate the branch name
gh stack add -m "Add user model"
# Stage all changes, commit, and use an explicit branch name
gh stack add -Am "Add tests" test-layer
# Stage only tracked files, commit, and use an explicit branch name
gh stack add -um "Update docs" docs-layer
# Commit already-staged changes and use an explicit branch name
gh stack add -m "Refactor utils" cleanup-layer
gh stack checkout
通过堆叠编号、拉取请求编号、PR URL 或分支名称检出堆叠。
gh stack checkout [<stack-number> | <pr-number> | <pr-url> | <branch>]
纯数字会首先被解读为栈编号或 PR 编号(GitHub 界面中显示的仓库范围标识符)。如果没有匹配的编号,会尝试将其作为分支名称。
当引用远程栈时,该命令会拉取 GitHub 上的栈,拉取分支并在本地设置好栈。如果该栈已在本地存在且匹配,会切换到相应分支。如果本地和远程栈的构成不同,系统会提示您解决冲突。
当提供分支名称时,该命令仅会根据本地跟踪的栈进行解析。
在交互式终端中不带参数运行时,会打开一个可搜索的选择器,列出您可用的所有栈 — 包括本地跟踪的栈和仅存在于 GitHub 上的栈。每一行显示栈编号、其底部和顶部分支、基础分支、一个状态栏(总结有多少拉取请求已合并、打开、关闭或尚未推送),以及该栈是在本地可用还是仅在远程可用。可通过“全部”/“本地”/“远程”选项卡进行筛选,或输入“/”进行搜索;已完全合并的栈会被省略。选择仅远程存在的栈会先将其克隆到本地,然后再切换到该栈。
示例:
# Check out a stack by its stack number
gh stack checkout 7
# Check out a stack by PR number
gh stack checkout 42
# Check out a stack by PR URL
gh stack checkout https://github.com/owner/repo/pull/42
# Check out a stack by branch name (local only)
gh stack checkout feature-auth
# Interactive — pick from all available stacks (local and remote)
gh stack checkout
gh stack rebase
从远程拉取代码,并对整个堆叠进行级联变基。
gh stack rebase [flags] [branch]
从 origin 获取最新更改,然后确保堆栈中的每个分支的提交历史都包含上一层分支的最新提交。按从主干向上的顺序对分支进行变基。如果某个分支的 PR 已合并,变基会自动切换到 --onto 模式,以便在合并目标之上正确重放提交。
如果变基过程中发生冲突,操作会暂停并打印带有行号的冲突文件。解决冲突后,使用 git add 暂存文件,然后通过 --continue 继续变基。要撤销整个变基操作,可使用 --abort 将所有分支恢复到变基前的状态。
| 标志 | 说明 |
|---|---|
--downstack |
仅从主干到当前分支对分支进行变基 |
--upstack |
仅从当前分支到顶层对分支进行变基 |
--no-trunk |
跳过主干 — 仅将堆栈分支相互变基(不获取,不主干变基) |
--continue |
解决冲突后继续变基 |
--abort |
中止变基并将所有分支恢复到变基前的状态 |
--remote <name> |
要从中获取的远程仓库(默认为自动检测的远程仓库) |
--committer-date-is-author-date |
变基期间将提交者日期设置为作者日期。别名:--preserve-dates |
| 参数 | 说明 |
|---|---|
[branch] |
目标分支(默认为当前分支) |
示例:
# Rebase the entire stack
gh stack rebase
# Only rebase branches below the current one
gh stack rebase --downstack
# Only rebase branches above the current one
gh stack rebase --upstack
# Rebase stack branches without pulling from or rebasing with trunk
gh stack rebase --no-trunk
# After resolving a conflict
gh stack rebase --continue
# Abort rebase and restore everything
gh stack rebase --abort
# Rebase and preserve committer date as author date
gh stack rebase --committer-date-is-author-date
gh stack modify
以交互方式调整当前堆栈结构。
gh stack modify [flags]
打开终端界面以重构堆叠。您可以删除、合并、插入、重命名和重新排序分支。所有更改会在预览阶段暂存,并在保存时一次性应用。
如果 PR 堆叠已在 GitHub 上创建,请在之后运行 gh stack submit 以推送更改并重新创建堆叠。
| 标志 | 描述 |
|---|---|
--continue |
解决冲突后继续 |
--abort |
中止修改会话并将堆叠恢复到修改前的状态 |
操作:
- 删除 (
x): 从堆叠中移除分支及其提交。本地分支和关联的 PR 会保留。 - 向下合并 (
d): 将分支的提交合并到下方(靠近主干)的分支中。被合并的分支将从堆叠中移除。 - 向上合并 (
u): 将分支的提交合并到上方(远离主干)的分支中。被合并的分支将从堆叠中移除。 - 插入 (
i/I): 在堆叠中插入新的空分支。i在光标下方插入;I在光标上方插入。 - 重新排序 (
Shift+↑/Shift+↓): 在堆叠中向上(远离主干)或向下(靠近主干)移动分支。 - 重命名 (
r): 在本地和堆叠元数据中重命名分支。 - 撤销 (
z): 撤销上一个暂存的操作。
按键绑定:
| 按键 | 操作 |
|---|---|
↑/↓ |
导航分支列表 |
f |
查看更改的文件 |
c |
查看提交 |
x |
删除分支 |
r |
重命名分支 |
i/I |
在下方/上方插入分支 |
d/u |
向下/向上合并分支 |
Shift+↑/Shift+↓ |
向上/向下移动分支 |
z |
撤销上一个操作 |
Ctrl+S |
应用所有更改 |
q/Esc |
取消并退出 |
? |
帮助 |
前提条件:
- 必须在本地检出活动的堆叠
- 工作区必须干净
- 没有进行中的变基
- 堆叠中没有 PR 处于待合并队列
- 提交历史必须是线性的
示例:
# Open the modify TUI
gh stack modify
# Continue after resolving a conflict
gh stack modify --continue
# Abort and restore to the previous state
gh stack modify --abort
gh stack sync
通过单个命令完成拉取、变基、推送及同步 PR 状态。
gh stack sync [flags]
执行整个堆栈的同步:
- 拉取(Fetch)——从
origin拉取最新更改。 - 协调远程堆栈——将 GitHub 堆栈本地镜像。当 GitHub 上的堆栈已添加 PR(远程领先于本地堆栈)时,其分支会被拉取下来并自动追加到本地堆栈。当本地和远程堆栈确实出现分歧时(例如,你在本地添加了一个分支,而 GitHub 上的堆栈同时添加了不同的 PR),系统会提示你进行解决(请参阅下方的分歧堆栈)。在非交互式终端中,出现分歧会中止同步(不会推送或更新任何内容)。
- 快进主干——将主干分支快进到与远程匹配(若存在分歧则跳过)。
- 级联变基——将所有堆栈分支变基到其更新后的父分支上(仅在主干移动时执行)。如果检测到冲突,所有分支都将恢复到原始状态,并建议你运行
gh stack rebase以交互方式解决冲突。 - 推送(Push)——推送所有分支(若发生变基,则使用
--force-with-lease)。 - 同步 PR——从 GitHub 同步 PR 状态并报告每个 PR 的状态。
- 同步堆栈——将堆栈中打开的 PR 链接到 GitHub 上的堆栈,若远程堆栈对象不存在则创建,若部分形成则更新。此操作仅在存在两个或更多 PR 时发生;同步操作从不打开 PR(请使用
gh stack submit执行此操作)。 - 清理(Prune)——在交互式终端中,提示删除已合并 PR 的本地分支。使用
--prune可自动清理。
远程领先的干净更新(在本地堆栈顶部添加的 PR)会自动拉取,无需提示,因此 sync 在自动化流程中运行是安全的。仅当堆栈确实出现分歧时,同步才会提示。
分歧堆栈
当两个堆栈都不是对方的干净前缀时(例如,你在本地添加了一个分支,而 GitHub 上的同一堆栈同时添加了其他 PR),同步无法自动合并两者。在交互式终端中,会提供三个选项:
- 以远程堆栈为事实来源——用远程堆栈的构成替换本地堆栈,拉取所有缺失的分支。如果你当前所在的分支不再包含在远程堆栈中,你将被移动到最近的保留分支。此操作要求工作区处于干净状态,没有未提交的更改。
- 删除 GitHub 上的堆栈——删除 GitHub 上的堆栈对象并停止同步。你的 PR 和本地分支不受影响(仅删除 GitHub 上的堆栈);可使用
gh stack submit重新创建堆栈(如果你想更改其结构,请先运行gh stack modify)。这是使 GitHub 与本地堆栈匹配的方法,因为submit与sync不同,它还会为所有尚未提交的分支创建 PR。 - 取消(Cancel)——中止同步,不推送分支或更新任何 PR。
在非交互式终端中,出现分歧会中止同步(成功退出),不推送分支或更新任何 PR;需通过拆堆并重新创建堆栈来解决。
| 标志 | 描述 |
|---|---|
--remote <name> |
要拉取和推送的远程仓库(默认为自动检测的远程仓库) |
--prune |
删除已合并 PR 的本地分支 |
示例:
gh stack sync
# Sync and automatically prune merged branches
gh stack sync --prune
gh stack push
将当前堆栈中的所有分支推送到远程仓库。
gh stack push [flags]
使用 --force-with-lease --atomic 将每个分支推送到远程。这是一个轻量级的 git push 包装器,能够识别堆栈中的所有分支。此命令不会创建或更新拉取请求 —— 请使用 gh stack submit 执行该操作。
| 标志 | 描述 |
|---|---|
--remote <name> |
要推送至的远程仓库(默认为自动检测的远程仓库) |
示例:
gh stack push
gh stack push --remote upstream
gh stack submit
推送所有分支并在 GitHub 上创建/更新 PR 及堆叠。
gh stack submit [flags]
为堆栈中的每个分支创建一个 Stacked PR,并将分支推送到远程仓库。
创建 PR 后,submit 会自动在 GitHub 上创建一个Stack,将这些 PR 链接在一起。如果该堆栈已存在于 GitHub(例如,来自之前的提交),新的 PR 将被添加到堆栈顶部。
如果堆栈中的所有 PR 均已合并,则该堆栈已完成且无法扩展——此时在顶部创建新 PR 将直接以主干为目标,而非链接到已合并的 PR。在这种情况下,submit 会自动为未合并的分支启动一个以主干为根的新堆栈,并在 GitHub 上创建它,同时保持已合并的堆栈不变。
在交互式终端中,submit 会打开一个全屏的、支持鼠标和键盘操作的编辑器,所有内容一目了然。默认情况下,所有尚未创建 PR 的分支都会被包含在内——可在左侧面板中取消选择不需要的分支(Ctrl+X)。由于每个 PR 都基于其下方的分支构建,取消选择某个分支也会同时取消选择其上方的所有分支;而重新包含某个分支时,其下方的分支也会被重新包含。在右侧为每个 PR 草拟标题、描述(带有 Markdown 预览和 $EDITOR 退出功能),并选择“准备审核”或“草稿”状态,然后按 Ctrl+S 一次性提交所有 PR。传递 --auto(或在 CI 中运行)可跳过编辑器,使用自动生成的标题。
如果分支已存在打开的 PR,但 GitHub 上尚未创建堆栈,您可以选择通过 Ctrl+B 将这些 PR 链接到一个堆栈中。
在编辑器中,新 PR 默认处于“准备审核”状态;可通过“准备 ↔ 草稿”切换按钮将任意 PR 切换为草稿状态。使用 --auto 时,新 PR 将创建为草稿,除非您传递 --open 参数。
| 标志 | 描述 |
|---|---|
--auto |
跳过编辑器,使用自动生成的 PR 标题 |
--open |
将新 PR 和现有 PR 标记为“准备审核” |
--remote <name> |
要推送的远程仓库(默认为自动检测的远程仓库) |
示例:
gh stack submit
gh stack submit --auto
gh stack submit --open
gh stack link
无需本地跟踪,即可在 GitHub 上将 PR 链接为一个堆叠。
gh stack link [flags] <branch-or-pr> <branch-or-pr> [...]
从分支名称或 PR 编号/URL 在 GitHub 上创建或更新堆叠。此命令不存储或修改任何 gh stack 本地跟踪状态。它专为使用其他本地工具(例如 jj、Sapling、git-town)管理分支并希望简单地打开 PR 堆叠的用户而设计。
参数按堆叠顺序(从下到上)提供。分支参数在创建或查找 PR 之前会自动推送到远程。对于已存在打开 PR 的分支,将使用这些 PR。对于没有 PR 的分支,会自动创建新的 PR,并正确设置基础分支的链式关系。对于基础分支与预期链式关系不匹配的现有 PR,会自动进行修正。
如果 PR 尚未形成堆叠,则创建新堆叠。如果部分 PR 已在堆叠中,则更新现有堆叠以包含新 PR。现有 PR 永远不会从堆叠中移除——更新仅为添加操作。
| 标志 | 描述 |
|---|---|
--base <branch> |
堆叠底部的基础分支(默认为仓库的默认分支) |
--open |
将新 PR 和现有 PR 标记为可供审核 |
--remote <name> |
要推送的远程仓库(默认为自动检测的远程仓库) |
示例:
# Link branches into a stack (pushes branches, creates PRs, creates stack)
gh stack link feature-auth feature-api feature-ui
# Link existing PRs by number
gh stack link 10 20 30
# Link existing PRs by URL
gh stack link https://github.com/owner/repo/pull/10 https://github.com/owner/repo/pull/20
# Add branches to an existing stack of PRs
gh stack link 42 43 feature-auth feature-ui
# Use a different base branch and mark PRs as ready for review
gh stack link --base develop --open feat-a feat-b feat-c
gh stack view
查看当前堆叠。
gh stack view [flags]
显示堆栈中的所有分支、它们的顺序、PR 链接以及带有相对时间戳的最新提交。输出会通过分页器传输(遵循 GIT_PAGER、PAGER,默认为 less -R)。
| 标志 | 描述 |
|---|---|
-s, --short |
精简输出(仅显示分支名称) |
--json |
以 JSON 格式输出堆栈数据 |
示例:
gh stack view
gh stack view --short
gh stack view --json
gh stack unstack
从本地跟踪中移除堆栈,并在 GitHub 上解除堆栈。也可使用 gh stack delete。
gh stack unstack [<stack-number>] [flags]
不带参数时,该命令以活动栈(即包含当前检出分支的栈)为目标,在 GitHub 上取消堆叠该栈并移除本地跟踪。
提供栈编号(github.com 栈界面中显示的标识符)可在 GitHub 上取消特定栈的堆叠。此操作可在仓库中的任意位置执行,无论该栈是否已在本地检出——编号直接通过 GitHub API 进行取消堆叠。如果该栈也在本地被跟踪,其本地跟踪也会被移除。
使用 --local 仅移除本地跟踪,而不与 GitHub 进行通信。
GitHub 会决定哪些拉取请求可以取消堆叠:已排队等待合并或已启用自动合并的 PR 会保持堆叠状态。当部分拉取请求仍处于堆叠状态时,该栈会被保留(且本地跟踪——如果存在的话——保持不变)。
| 标志 | 描述 |
|---|---|
--local |
仅在本地移除栈(在 GitHub 上保留该栈) |
示例:
# Remove the current stack from local tracking and GitHub
gh stack unstack
# Unstack a specific stack by its number
gh stack unstack 7
# Only remove local tracking
gh stack unstack --local
导航
无需记住分支名称,即可在当前堆栈的分支之间切换。
gh stack up [n] # Move up n branches (default 1)
gh stack down [n] # Move down n branches (default 1)
gh stack top # Jump to the top of the stack
gh stack bottom # Jump to the bottom of the stack
gh stack trunk # Jump to the trunk branch
gh stack switch # Interactively pick a branch to switch to
导航命令会限制在堆栈范围内——从顶部向上移动或从底部向下移动时不会执行任何操作,并会显示一条消息。如果您处于主干分支,up 命令会移动到第一个堆栈分支。
示例:
gh stack up # move up one layer
gh stack up 3 # move up three layers
gh stack down
gh stack top
gh stack bottom
gh stack trunk # jump to the trunk branch (e.g., main)
gh stack switch # shows an interactive picker
gh stack feedback
分享关于 gh-stack 的反馈。
gh stack feedback [title]
在 gh-stack 仓库 中开启一个 GitHub Discussion 以提交反馈。可选择为讨论帖提供标题。
示例:
gh stack feedback
gh stack feedback "Support for reordering branches"
gh stack alias
创建一个简短的命令别名,让你可以少输入一些内容。
gh stack alias [flags] [name]
会在 ~/.local/bin/ 中安装一个小型包装脚本,该脚本会将所有参数转发给 gh stack。默认的别名是 gs,但你可以通过传入参数来选择任何名称。设置完成后,你可以运行 gs push 来代替 gh stack push。
在 Windows 系统上,不支持自动创建别名 —— 该命令会打印手动创建批处理文件或 PowerShell 函数的说明。
| 标志 | 描述 |
|---|---|
--remove |
删除之前创建的别名 |
示例:
# Create the default alias (gs)
gh stack alias
# → now "gs push", "gs view", etc. all work
# Create a custom alias
gh stack alias gst
# Remove an alias
gh stack alias --remove
gh stack alias gst --remove
典型工作流程
# 1. Start a stack (creates and checks out the first branch)
gh stack init
# 2. Work on the first layer
# ... write code, make commits ...
# 3. Add the next layer
gh stack add api-routes
# ... write code, make commits ...
# 4. Push everything and create Stacked PRs
gh stack submit
# 5. Reviewer requests changes on the first PR
gh stack bottom
# ... make changes, commit ...
# 6. Rebase the rest of the stack on top of your fix
gh stack rebase
# 7. Push the updated branches
gh stack push
# 8. When the first PR is merged, sync the stack
gh stack sync
# → prompts to prune merged branches (or use --prune to prune automatically and avoid the prompt)
简化工作流程
若希望减少操作步骤,可使用 -Am 标志将暂存、提交和分支创建合并为单个命令。未指定分支名称时,系统会根据提交消息自动生成日期+短标题格式的分支名(例如 03-24-auth_middleware)。
当分支尚无提交记录时(例如执行 init 命令后),add -Am 会直接在该分支上暂存并提交,而非创建新分支。一旦分支已有提交记录,add -Am 将创建新分支、切换至该分支并在其上提交。
# 1. Start a stack
gh stack init auth
# → creates auth and checks it out
# 2. Write code for the first layer
# ... write code ...
# 3. Stage and commit on the current branch
gh stack add -Am "Auth middleware"
# → auth has no commits yet, so the commit lands here
# (no new branch is created)
# 4. Write code for the next layer
# ... write code ...
# 5. Create the next branch and commit
gh stack add -Am "API routes"
# → auth already has commits, so a new branch is created from the
# commit message, checked out, and the commit lands there
# 6. Keep going
# ... write code ...
gh stack add -Am "Frontend components"
# → creates another branch and commits there
# 7. Push everything and create PRs
gh stack submit
与常规工作流相比,无需单独命名分支、运行 git add 或 git commit。每个 gh stack add -Am "..." 命令即可完成所有操作。若需控制分支名称,可随时传入明确的分支名:gh stack add -Am "API routes" api-routes。
终端主题
交互界面(submit、modify 和 view)以及所有彩色命令输出(状态消息、提示)会自动根据终端背景调整颜色,确保在深色和浅色主题下均清晰可读。背景颜色通过终端检测;若终端未上报背景信息(某些 SSH 或 tmux 配置),则使用深色调色板。
若检测结果有误,可设置 GH_STACK_THEME 强制指定调色板:
| 值 | 行为 |
|---|---|
auto(默认) |
根据终端背景自动检测 |
light |
强制使用浅色调色板 |
dark |
强制使用深色调色板 |
# Force the light palette
export GH_STACK_THEME=light && gh stack view
退出代码
| 代码 | 含义 |
|---|---|
| 0 | 成功 |
| 1 | 一般错误 |
| 2 | 不在堆栈中 / 未找到堆栈 |
| 3 | 变基冲突 |
| 4 | GitHub API 调用失败 |
| 5 | 参数或标志无效 |
| 6 | 需要消除歧义(分支属于多个堆栈) |
| 7 | 变基已在进行中 |
| 8 | 堆栈被其他进程锁定 |
许可证
本项目根据 MIT 开源许可证的条款进行许可。请参阅 LICENSE 文件了解完整条款。
维护者
参见 CODEOWNERS
支持
参见 SUPPORT.md
请注意,Stacked PRs 功能目前处于私人预览阶段,gh-stack 在未启用该功能的情况下无法使用。