关于 gh-stack
用于操作 `gh stack` 的技能;该 GitHub CLI 扩展可管理相互依赖的分支和拉取请求。它指导编码 Agent 以非交互方式创建、导航、同步分支栈并处理评审流程。
主要功能
- 01创建和组织依赖分支栈
- 02在栈的各层间导航
- 03变基并同步分支
- 04提交堆叠式拉取请求
- 05查看状态并重组分支栈
适用场景
- 01将大型改动拆分为易审查的 PR
- 02在依赖改动下方更新低层分支
- 03同步并提交分支栈
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ arize-ai/ phoenix --skill gh-stack安装后在 Claude Code 中输入/gh-stack确认已加载。
详细介绍
gh-stack
gh stack 是一个用于管理堆叠分支和拉取请求的 GitHub CLI 扩展。堆叠是一组有序分支,每个分支都建立在下方的分支之上,并以主干分支为根(通常是仓库的默认分支)。每个分支对应一个 PR,其目标分支是下方的分支,因此审阅者只会看到这一层的差异。
main (trunk)
└── auth-layer → PR #1 (base: main) - bottom (closest to trunk)
└── api-endpoints → PR #2 (base: auth-layer)
└── frontend → PR #3 (base: api-endpoints) - top (furthest from trunk)
堆叠的底部是最靠近主干的分支,顶部是离主干最远的分支。每个分支都基于下方的分支。导航命令(up、down、top、bottom)遵循此模型:up 表示远离主干,down 表示靠近主干。
何时使用此技能
当用户想要执行以下操作时,请使用此技能:
- 将大型变更拆分为一系列小型、易于审阅的 PR
- 创建、变基、推送或同步一组相互依赖的分支
- 在分支堆叠的各层之间导航
- 查看堆叠 PR 的状态
- 拆除并重建堆叠,以移除、重排或重命名分支
前置条件
必须安装并完成身份验证的 GitHub CLI(gh)版本为 v2.0+。使用以下命令安装扩展:
gh extension install github/gh-stack
使用 gh stack 之前,请配置 git 以防止出现交互式提示:
git config rerere.enabled true # remember conflict resolutions (skips prompt on init)
git config remote.pushDefault origin # if multiple remotes exist (skips remote picker)
Agent 规则
所有 gh stack 命令都必须以非交互方式运行。 每次调用命令都必须包含所需的标志和位置参数,以避免出现提示、TUI 和交互式菜单。如果命令需要提示输入,就会无限期挂起。
- 始终将分支名称作为位置参数传递给
init、add和checkout。 不带参数运行这些命令会触发交互式提示。分支名称会按给定内容原样使用,不会添加前缀或转换,因此gh stack add refactor/foo会创建名为refactor/foo的分支。 - 始终在
gh stack submit中使用--auto,以自动生成 PR 标题。不使用--auto时,submit会提示为每个新 PR 输入标题。 - 始终在
gh stack view中使用--json。 不使用--json时,命令会启动 Agent 无法操作的交互式 TUI。没有其他合适的标志——始终传入--json。 - 配置了多个远程仓库时,使用
--remote <name>,或者预先配置git config remote.pushDefault origin。否则,push、submit、sync、link和checkout会触发交互式远程仓库选择器。 - 避免在多个堆栈之间共享分支。 如果一个分支属于多个堆栈,命令会以代码 6 退出。请先切换到非共享分支。
- 编写代码前,按依赖顺序规划堆栈层级。 基础性更改(模型、API、共享工具)放在较低层分支;依赖这些更改的内容(UI、使用方)放在较高层分支。在运行
gh stack init前,先理清依赖链。 - 使用标准的
git add和git commit来暂存和提交。 这样你可以完全控制每个分支包含哪些更改。虽然可以使用快捷方式-Am,但不应将其作为默认做法——只有当每个分支都包含精心挑选、逻辑清晰的一组更改时,堆叠式 PR 才最有效。 - 需要更改较低层时,沿堆栈向下切换。 如果你正在前端分支上工作,发现需要更改 API,不要在当前层绕过问题。切换到合适的分支(
gh stack down、gh stack checkout或gh stack bottom),在该分支上进行更改并提交,运行gh stack rebase --upstack,然后再向上切换以继续工作。 - 外部工具工作流中使用
gh stack link。 如果分支由外部工具(jj、Sapling 等)管理,请使用gh stack link branch-a branch-b。link不依赖本地跟踪状态,旨在用于由 API 驱动的 PR 和堆栈管理。请至少提供两个分支/PR 以创建或更新堆栈;或者,提供堆栈编号,后跟要追加到现有堆栈顶部的新分支/PR(例如gh stack link 7 branch-c)。
切勿执行以下操作——每一项都会触发交互式提示或 TUI,导致命令挂起:
- ❌
gh stack view或gh stack view --short——始终使用gh stack view --json - ❌ 不带
--auto的gh stack submit——始终使用gh stack submit --auto - ❌ 不带分支参数的
gh stack init——始终提供分支名称 - ❌ 不带分支名称的
gh stack add——始终提供分支名称 - ❌ 不带参数的
gh stack checkout——始终提供 PR 编号或分支名称 - ❌ 在这些分支上已存在另一个本地堆栈时运行
gh stack checkout <pr-number>——这会触发无法绕过的冲突解决提示;请先使用gh stack unstack移除本地堆栈,然后再重试 checkout
关于堆栈结构的思考
堆栈中的每个分支都应代表一个可独立审查的独立、逻辑清晰的工作单元。分支中的更改应当彼此紧密相关——它们应当属于一起,并作为一个 PR 合情合理。
依赖链
堆叠分支会形成依赖链:每个分支都基于其下方的分支构建。这意味着,基础性更改必须放在较低(较早)的分支中,依赖这些更改的代码则放在较高(较晚)的分支中。
**编写代码前,先规划好各层。**例如,一个全栈功能可以按如下方式组织(请使用与你的实际任务相关的分支名称,而不是这些通用名称):
main (trunk)
└── data-models ← shared types, database schema
└── api-endpoints ← API routes that use the models
└── frontend-ui ← UI components that call the APIs
└── integration ← tests that exercise the full stack
这只是示例——请选择能体现你所做具体工作的分支名称和分层边界。关键原则是:如果某一层的代码依赖另一层的代码,那么依赖项必须位于同一分支或更低的分支中。
分支命名
为每一层选择清晰、能体现其所含内容的分支名称(例如,auth、api-routes、frontend)。分支名称会按你提供给 init 和 add 的原样使用——不会添加前缀或进行转换。名称中可以使用斜杠,并会作为名称的一部分处理(例如,gh stack add refactor/foo 会创建名为 refactor/foo 的分支)。
有意暂存更改
直接使用 git add 和 git commit 的主要原因是控制哪些更改进入哪个分支。如果工作树中有多个文件,你可以为当前分支暂存一部分文件并提交,然后创建新分支,在那里暂存其余文件:
# You're on data-models with several new files in your working tree.
# Stage only the model files for this branch:
git add internal/models/user.go internal/models/session.go
git commit -m "Add user and session models"
git add db/migrations/001_create_users.sql
git commit -m "Add user table migration"
# Now create a new branch for the API layer and stage the API files there:
gh stack add api-routes # created & switched to the api-routes branch
git add internal/api/routes.go internal/api/handlers.go
git commit -m "Add user API routes"
这样可以确保每个分支都专注于一个事项。每个分支可以有多次提交——关键是该分支中的所有提交都与同一个逻辑事项相关,而属于其他事项的更改应放在不同分支中。
何时创建新分支
开始处理依赖于当前成果的不同事项时,创建一个新分支(gh stack add)。以下情况表明该创建新分支了:
- 从后端工作转向前端工作
- 从核心逻辑转向测试或文档
- 下一组更改面向不同的审阅者
- 当前分支的 PR 已经大到不易审阅
一个堆栈,一个故事
从审阅者的角度看待堆栈:PR 堆栈应该围绕某项功能或项目讲述一个连贯的故事。审阅者应能按顺序阅读 PR,理解更改的演进过程,其中每个 PR 都是整体中一个小而合理的部分。
**适合使用单个堆栈的情况:**所有分支都属于同一功能、项目或密切相关的工作。即使工作涉及多个方面(模型、API、前端),它们也都是为了实现同一个目标。
**适合创建独立堆栈的情况:**这项工作与你当前的堆栈无关——例如不同的功能、无关领域的错误修复,或独立的重构。不要仅仅因为你恰好同时在处理多项工作,就把无关工作混在同一个堆栈中。对于每项独立工作,使用 gh stack init 创建新堆栈,或使用 gh stack checkout 切换到现有堆栈。
小型、顺带的修复(例如修正你注意到的拼写错误)可以放在当前堆栈中,前提是它们很简单。但如果某项更改变成了独立项目,就应该拥有自己的堆栈。
快速参考
| 任务 | 命令 |
|---|---|
| 创建堆栈 | gh stack init auth |
| 创建包含多个分支的堆栈 | gh stack init auth api frontend |
| 采用现有分支 | gh stack init existing-branch-a existing-branch-b |
| 设置自定义主干 | gh stack init --base develop branch-a |
| 向堆栈添加分支 | gh stack add api-routes |
| 添加分支 + 暂存所有更改 + 提交 | gh stack add -Am "message" api-routes |
| 将分支推送到远程 | gh stack push |
| 推送到指定远程 | gh stack push --remote origin |
| 推送分支 + 创建草稿 PR | gh stack submit --auto |
| 创建可供审查的 PR | gh stack submit --auto --open |
| 同步(获取、变基、推送) | gh stack sync |
| 与指定远程同步 | gh stack sync --remote origin |
| 同步并清理已合并的分支 | gh stack sync --prune |
| 对整个堆栈执行变基 | gh stack rebase |
| 仅对上游分支执行变基 | gh stack rebase --upstack |
| 不包括主干地执行变基 | gh stack rebase --no-trunk |
| 冲突后继续 | gh stack rebase --continue |
| 中止变基 | gh stack rebase --abort |
| 查看堆栈详情(JSON) | gh stack view --json |
| 在堆栈中向上/向下切换分支 | gh stack up [n] / gh stack down [n] |
| 切换到顶部/底部的分支 | gh stack top / gh stack bottom |
| 按堆栈编号检出 | gh stack checkout 7 |
| 按 PR 检出 | gh stack checkout 42 |
| 按分支检出(仅本地) | gh stack checkout feature-auth |
| 拆解当前堆栈以重新组织 | gh stack unstack |
| 按编号拆解指定堆栈 | gh stack unstack 7 |
工作流
端到端:从头创建堆栈
# 1. Initialize a stack with the first branch
gh stack init auth
# → creates auth and checks it out
# 2. Write code for the first layer (auth)
cat > auth.go << 'EOF'
package auth
func Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// verify token
next.ServeHTTP(w, r)
})
}
EOF
# 3. Stage and commit using standard git commands
git add auth.go
git commit -m "Add auth middleware"
# You can make multiple commits on the same branch
cat > auth_test.go << 'EOF'
package auth
func TestMiddleware(t *testing.T) {
// test auth middleware
}
EOF
git add auth_test.go
git commit -m "Add auth middleware tests"
# 4. When you're ready for a new concern, add the next branch
gh stack add api-routes
# → creates api-routes
# 5. Write code for the API layer
cat > api.go << 'EOF'
package api
func RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("/users", handleUsers)
}
EOF
git add api.go
git commit -m "Add API routes"
# 6. Add a third layer for frontend
gh stack add frontend
# → creates frontend
cat > frontend.go << 'EOF'
package frontend
func RenderDashboard(w http.ResponseWriter) {
// calls the API endpoints from the layer below
}
EOF
git add frontend.go
git commit -m "Add frontend dashboard"
# ── Stack complete: auth → api-routes → frontend ──
# 7. Push everything and create PRs (drafts by default)
gh stack submit --auto
# 8. Verify the stack
gh stack view --json
快捷方式: 如果你希望加快流程,
gh stack add -Am "message" branch-name可将暂存、提交和创建分支合并为一条命令。这适用于单次提交的层,但会跳过有意选择暂存内容的步骤。
修改堆栈中间层
这是 Agent 的关键工作流。当你在较高层工作时,发现需要修改较低层的内容(例如,你正在构建前端组件,但需要添加 API 端点),请切换到正确的分支,在那里进行修改,然后执行变基。
# You're on frontend but need to add an API endpoint
# 1. Navigate to the API branch
gh stack down
# or: gh stack checkout api-routes
# 2. Make the change where it belongs
cat > users_api.go << 'EOF'
package api
func handleGetUser(w http.ResponseWriter, r *http.Request) {
// new endpoint the frontend needs
}
EOF
git add users_api.go
git commit -m "Add get-user endpoint"
# 3. Rebase everything above to pick up the change
gh stack rebase --upstack
# 4. Navigate back to where you were working
gh stack top
# or: gh stack checkout frontend
# 5. Continue working — the API changes are now available
为何重要: 如果你在前端分支上进行 API 修改,这些修改最终会进入错误的 PR。API PR 不会包含这些修改,而前端 PR 则会混入无关的 API 差异。务必将修改放在逻辑上所属的分支中。
修改堆栈中间的分支并同步
如果需要在初次创建后重新处理某个分支(例如,回应审查反馈):
# 1. Navigate to the branch that needs changes
gh stack bottom
# or: gh stack checkout auth
# or: gh stack checkout 42 (by PR number)
# 2. Make changes and commit
cat > auth.go << 'EOF'
package auth
// updated implementation
EOF
git add auth.go
git commit -m "Fix auth token validation"
# 3. Rebase everything above this branch
gh stack rebase --upstack
# 4. Push the updated stack
gh stack push
合并后的例行同步
# Single command: fetch, rebase, push, sync PR and stack state
gh stack sync
# Sync and automatically clean up local branches for merged PRs
gh stack sync --prune
给 Agent 的说明: 在非交互环境中不会显示清理提示。请显式使用
--prune删除已合并 PR 对应的本地分支。
给 Agent 的说明:
sync还会在本地镜像 GitHub 上的堆栈。如果在 github.com 上向堆栈添加了 PR,其分支会自动拉取到本地并追加到本地堆栈。如果本地堆栈和远程堆栈发生分歧(你修改了本地堆栈,而远程堆栈也以不同方式发生了变化),只有在交互式终端中,sync 才能提示你解决分歧;在非交互环境中,它会中止同步(不会推送或更新任何内容),并以成功状态退出,同时显示ℹ Sync aborted。如需解决分歧,请拆解并重新创建堆栈。
Squash 合并后的恢复
当 PR 在 GitHub 上通过 squash 合并后,原始分支的提交将不再存在于主干历史中。gh stack 会自动检测到这一情况,并使用 git rebase --onto 正确地重新应用剩余提交。
# After PR #1 (auth) is squash-merged on GitHub:
gh stack sync
# → fetches latest, detects the merge, fast-forwards trunk
# → rebases api-routes onto updated trunk (skips merged branch)
# → rebases frontend onto api-routes
# → pushes updated branches
# → reports: "Merged: #1"
# Verify the result
gh stack view --json
# → auth shows "isMerged": true, "state": "MERGED"
# → api-routes and frontend show updated heads
如果 sync 在此过程中遇到冲突,它会将所有分支恢复到变基前的状态,并以代码 3 退出。解决流程请参阅处理变基冲突。
处理变基冲突(Agent 工作流)
# 1. Start the rebase
gh stack rebase
# 2. If exit code 3 (conflict):
# - Parse stderr for conflicted file paths
# - Read those files to find <<<<<<< / ======= / >>>>>>> markers
# - Edit files to resolve conflicts
# - Stage resolved files:
git add path/to/resolved-file.go
# 3. Continue the rebase
gh stack rebase --continue
# 4. If another conflict occurs, repeat steps 2-3
# 5. If unable to resolve, abort to restore everything
gh stack rebase --abort
解析 --json 输出
# Get stack state as JSON
output=$(gh stack view --json)
# Check if any branch needs a rebase, and rebase if so
needs_rebase=$(echo "$output" | jq '[.branches[] | select(.needsRebase == true)] | length')
if [ "$needs_rebase" -gt 0 ]; then
echo "Branches need rebase, rebasing stack..."
gh stack rebase
fi
# Get all open PR URLs
echo "$output" | jq -r '.branches[] | select(.pr.state == "OPEN") | .pr.url'
# Find merged branches
echo "$output" | jq -r '.branches[] | select(.isMerged == true) | .name'
# Get the current branch
echo "$output" | jq -r '.currentBranch'
# Check if the stack is fully merged (all branches merged)
echo "$output" | jq '[.branches[] | .isMerged] | all'
重组栈(移除分支、重新排序或重命名)
使用 unstack 拆除栈,进行结构调整,然后重新初始化:
# 1. Remove the stack (locally and on GitHub)
gh stack unstack
# 2. Make structural changes — e.g. delete a branch, reorder, rename
git branch -m old-branch-1 new-branch-1
# 3. Re-create the stack with the new structure
gh stack init --base main new-branch-1 new-branch-2 new-branch-3
命令
初始化栈 — gh stack init
创建新栈。必须至少提供一个分支名称作为位置参数——不带分支参数运行会触发交互式提示,Agent 无法使用。
gh stack init [flags] <branches...>
# Create a stack with a new branch
gh stack init auth
# → creates auth and checks it out
# Create a stack with new branches
gh stack init branch-a branch-b branch-c
# Use a different trunk branch
gh stack init --base develop branch-a branch-b
# Adopt existing branches into a stack (handled automatically if the branches exist)
gh stack init branch-a branch-b branch-c
| 标志 | 说明 |
|---|---|
-b, --base <branch> | 主干分支(默认为仓库的默认分支) |
行为:
- 分支名称会按给定内容原样创建(允许使用斜杠,并保持原样)
- 创建所有尚不存在的分支(从主干分支分出)
- 自动接管现有分支;缺少的分支会从主干分支创建
- 检出列表中的最后一个分支
- 启用
git rerere,以便在变基时记住冲突解决方案。在仓库中首次运行时,可能会触发确认提示——预先设置git config rerere.enabled true可避免此提示
添加分支 — gh stack add
在当前栈的顶部添加一个新分支。必须在最顶层分支上运行(如果栈尚无分支,则在主干分支上运行)。必须提供分支名称——不提供名称会触发交互式提示。
gh stack add [flags] <branch>
推荐工作流——创建分支,然后使用标准 git 命令:
# Create a new branch and switch to it
gh stack add api-routes
# Write code, stage deliberately, and commit
git add internal/api/routes.go internal/api/handlers.go
git commit -m "Add user API routes"
# Make more commits on the same branch as needed
git add internal/api/middleware.go
git commit -m "Add rate limiting middleware"
快捷方式——用一条命令暂存、提交并创建分支:
# Create a new branch, stage all changes, and commit
gh stack add -Am "Add API routes" api-routes
# Create a new branch, stage tracked files only, and commit
gh stack add -um "Fix auth bug" auth-fix
| 标志 | 说明 |
|---|---|
-m, --message <string> | 使用此消息创建提交 |
-A, --all | 暂存所有更改,包括未跟踪文件(需要 -m) |
-u, --update | 仅暂存已跟踪文件(需要 -m) |
行为说明:
-A和-u不能同时使用。- 如果当前分支没有提交(例如刚运行完
init),add -Am会直接在当前分支上提交,而不是创建新分支。 - 分支名称会原样使用。
gh stack add refactor/foo会创建名为refactor/foo的分支——名称绝不会被添加前缀或转换。如果提供了-m但未提供分支名称,名称会根据提交消息按日期+标识符格式自动生成(例如03-24-add_api_routes)。 - 如果在非栈顶层的分支上调用,会以代码 5 退出:
"can only add branches on top of the stack"。请先使用gh stack top切换分支。 - 未提交的更改: 使用
gh stack add branch-name(不带-Am)时,工作树中的任何未提交更改(已暂存或未暂存)都会保留并带到新分支。这是标准的 git 行为——工作树不会被修改。如果希望新分支从干净状态开始,请先在当前分支提交或暂存更改,再运行add。
将分支推送到远程 — gh stack push
将所有栈分支推送到远程。
gh stack push [flags]
# Push all branches
gh stack push
# Push to specific remote
gh stack push --remote upstream
| 标志 | 说明 |
|---|---|
--remote <name> | 要推送到的远程(存在多个远程时使用) |
行为:
- 原子性推送所有活跃(未合并)分支(
--force-with-lease --atomic) - 不会创建或更新拉取请求——请使用
gh stack submit执行此操作
输出(stderr):
Pushed N branches摘要
提交分支并创建 PR — gh stack submit
推送所有堆栈分支并在 GitHub 上创建 PR。始终传入 --auto——否则,submit 会提示为每个新分支输入 PR 标题。
# Submit and auto-title new PRs (required for non-interactive use)
gh stack submit --auto
# Submit and create PRs as ready for review (not drafts)
gh stack submit --auto --open
| 标志 | 描述 |
|---|---|
--auto | 自动生成 PR 标题,不再提示(非交互式使用时必需) |
--open | 将新建和现有 PR 标记为可供审查 |
--remote <name> | 要推送到的远程仓库(存在多个远程仓库时使用) |
行为:
- 以原子方式推送所有活动分支(未合并的分支)(
--force-with-lease --atomic) - 为每个尚无 PR 的分支创建一个新 PR(将第一个未合并的祖先分支设为基础分支)
- 创建 PR 后,在 GitHub 上将它们关联为一个堆栈(要求仓库已启用堆栈)
- 如果堆栈中的每个 PR 都已合并,则该堆栈已完成,不能再扩展。
submit会自动将未合并的分支分叉成一个以主干分支为根的新堆栈,并在 GitHub 上创建该堆栈,同时保留已合并的堆栈不变。 - 如果堆栈不可用(退出代码 9),则表示仓库未启用堆叠式 PR。交互模式下,
submit会询问是否改为创建常规(非堆叠)PR。非交互模式下,它会以代码 9 退出。 - 同步已有 PR 的分支的 PR 元数据
PR 标题自动生成(--auto):
- 分支上只有一个提交 → 使用提交主题作为 PR 标题,提交正文作为 PR 正文
- 分支上有多个提交 → 将分支名中的连字符和下划线替换为空格,作为 PR 标题
输出(stderr):
- 每个新创建的 PR 输出
Created PR #N for <branch> - 每个已有 PR 输出
PR #N for <branch> is up to date - 汇总输出
Pushed and synced N branches
将分支关联为堆栈(不进行本地跟踪)— gh stack link
在 GitHub 上将 PR 关联为堆栈,而不创建任何本地跟踪状态。如果你使用其他工具(jj、Sapling、git-town)管理堆叠分支,并希望只通过 API 创建 GitHub 堆叠式 PR,建议使用此方式。
gh stack link [flags] <stack-number | branch-or-pr> <branch-or-pr> [...]
# Link branches into a stack (pushes, creates PRs, creates stack)
gh stack link branch-a branch-b branch-c
# Use a different base branch and mark PRs as ready for review
gh stack link --base develop --open branch-a branch-b branch-c
# Link existing PRs by number
gh stack link 10 20 30
# Add branches to an existing stack of PRs
gh stack link 42 43 feature-auth feature-ui
# Append to the top of an existing stack by its stack number
# (7 is a stack number; only the new PRs/branches are listed)
gh stack link 7 48 feature-auth
当第一个参数是堆栈编号时,其余参数会追加到该堆栈的顶部,因此无需重新列出该堆栈当前包含的 PR。已在堆栈中的参数会被跳过;属于其他堆栈的参数会被拒绝。只有当数字形式的第一个参数与现有堆栈匹配时,才会将其视为堆栈;否则,它会被视为 PR 或分支。
| 标志 | 描述 |
|---|---|
--base <branch> | 堆栈底部的基础分支(默认:main) |
--open | 将新建和现有 PR 标记为可供审查 |
--remote <name> | 要推送到的远程仓库(存在多个远程仓库时使用) |
行为:
- 参数按堆栈顺序提供(从底部到顶部)
- 每个参数可以是分支名或 PR 编号。数字参数会先作为 PR 编号处理;如果不存在该编号的 PR,则会将其视为分支名
- 分支参数会自动推送到远程仓库(非强制、原子操作)
- 对于没有未关闭 PR 的分支,会创建带有自动生成标题的新 PR,并正确设置基础分支链(第一个分支使用
--base,后续分支使用前一个分支) - 如果现有 PR 的基础分支与预期链不匹配,会自动修正
- 如果这些 PR 尚未属于任何堆栈,则会创建新堆栈。如果其中部分 PR 已在堆栈中,则会更新该堆栈(仅添加——绝不会移除现有 PR)
- 不会创建或修改任何本地状态
输出(stderr):
Pushing N branches to <remote>...- 对于已有 PR 的分支,输出
Found PR #N for branch <name> - 对于新创建的 PR,输出
Created PR #N for <branch> (base: <base>) - 修正基础分支时,输出
Updated base branch for PR #N to <base> - 输出
Created stack with N PRs或Updated stack to N PRs
同步堆栈 — gh stack sync
通过一条命令完成拉取、变基、推送和 PR 状态同步。这是日常同步的推荐命令。
gh stack sync [flags]
| 标志 | 说明 |
|---|---|
--remote <name> | 要从中拉取和向其推送的远程仓库(存在多个远程仓库时使用) |
--prune | 删除已合并 PR 对应的本地分支 |
执行步骤(按顺序):
- 拉取远程的最新更改
- 协调远程堆栈 — 在本地镜像 GitHub 堆栈。如果在 GitHub 上向堆栈添加了 PR,则拉取其分支并将其追加到本地堆栈。如果本地堆栈与远程堆栈已分叉,则在非交互式终端中中止同步。在交互式终端中,会提示你解决分叉问题(用远程版本替换本地堆栈、删除 GitHub 上的堆栈以便重新创建,或取消)。
- 快进主干分支以与远程保持一致(如果已是最新状态则跳过;如果已分叉则发出警告)
- 级联变基所有堆栈分支,使其基于更新后的父分支(仅当主干分支发生变化时)。自动处理已合并的 PR。如果检测到冲突,所有分支都会恢复到变基前的状态,且命令以代码 3 退出 — 解决流程请参阅处理变基冲突
- 推送所有活动分支(以原子方式)
- 从 GitHub 同步 PR 状态并报告每个 PR 的状态
- 同步堆栈对象 — 在 GitHub 上将打开的 PR 关联到一个堆栈。如果这些 PR 尚未加入堆栈,则会创建一个新堆栈;如果部分 PR 已在堆栈中,则会更新该堆栈(仅添加)。只有存在两个或更多 PR 时才会执行此操作。同步绝不会打开 PR — 要打开 PR,请使用
gh stack submit - 清理 — 在交互式终端中,会提示删除已合并 PR 对应的本地分支。使用
--prune可跳过提示。在非交互式环境中,只有显式传入--prune才会执行清理
输出(stderr):
✓ Fetched latest changes from origin- 当远程堆栈超前时,显示
Pulling N new branches from the remote stack ...,随后显示✓ Pulled N new branches into the stack from the remote - 当堆栈已分叉时,显示
⚠ Your local stack has diverged from the stack on GitHub(并附带Local:/Remote:链) - 取消同步时显示
ℹ Sync aborted — no changes were made ✓ Trunk main fast-forwarded to <sha>或✓ Trunk main is already up to date- 每个分支(如果基分支发生变化):
✓ Rebased <branch> onto <base> ✓ Pushed N branches- 每个分支:
✓ PR #N (<branch>) — Open - 对于已合并的分支:
Merged: #N, #M - 存在两个或更多 PR 时:
✓ Stack created on GitHub with N PRs/✓ Stack updated on GitHub with N PRs/✓ Linked to the existing stack on GitHub - 清理时,对每个已清理的分支显示
✓ Pruned <branch> (merged) - 当 GitHub 上的堆栈对象已创建或更新为与本地一致时,显示
✓ Stack synced;当仅同步了分支时(PR 少于两个或无法使用堆叠式 PR),显示✓ Branches synced
变基堆栈 — gh stack rebase
从远程拉取并级联变基堆栈分支。当 sync 报告冲突,或你需要更精细的控制(例如,仅变基堆栈的一部分)时使用此命令。
gh stack rebase [flags] [branch]
# Rebase the entire stack
gh stack rebase
# Rebase only branches from trunk to current branch
gh stack rebase --downstack
# Rebase only branches from current branch to top
gh stack rebase --upstack
# Rebase stack branches without pulling from or rebasing with trunk
gh stack rebase --no-trunk
# After resolving a conflict: stage files with `git add`, then:
gh stack rebase --continue
# Abort and restore all branches to pre-rebase state
gh stack rebase --abort
| 标志 | 描述 |
|---|---|
--downstack | 仅变基从 trunk 到当前分支的分支 |
--upstack | 仅变基从当前分支到顶部的分支 |
--no-trunk | 跳过 trunk,仅将堆栈分支相互变基(不拉取,也不变基 trunk) |
--continue | 解决冲突后继续 |
--abort | 中止并还原所有分支 |
--remote <name> | 要从中拉取的远程(存在多个远程时使用) |
| 参数 | 描述 |
|---|---|
[branch] | 目标分支(默认为当前分支) |
**冲突处理:**完整的解决流程请参见“工作流”部分中的处理变基冲突。
**已合并 PR 检测:**如果某个分支的 PR 已在 GitHub 上合并,变基会自动使用 --onto 模式处理,并在合并目标之上正确重放提交。
Rerere(冲突记忆):init 会启用 git rerere,以便在后续变基时自动解决之前已解决的冲突。
**无 trunk 模式:**使用 --no-trunk 可跳过从远程拉取以及与 trunk 分支变基。此时只会执行分支间的变基(将分支 2 变基到分支 1 上、将分支 3 变基到分支 2 上,依此类推)。适用于只需让堆栈分支彼此保持一致,而无需拉取上游更改的情况。
查看堆栈 — gh stack view
显示当前堆栈的分支、PR 状态和近期提交。始终传入 --json — 如果不传入,此命令会启动交互式 TUI,Agent 无法操作。
# Always use --json
gh stack view --json
| 标志 | 描述 |
|---|---|
--json | 将堆栈数据以 JSON 格式输出到 stdout(非交互式使用时必需) |
--json 输出格式:
{
"trunk": "main",
"currentBranch": "api-routes",
"branches": [
{
"name": "auth",
"head": "abc1234...",
"base": "def5678...",
"isCurrent": false,
"isMerged": true,
"needsRebase": false,
"pr": {
"number": 42,
"url": "https://github.com/owner/repo/pull/42",
"state": "MERGED"
}
},
{
"name": "api-routes",
"head": "789abcd...",
"base": "abc1234...",
"isCurrent": true,
"isMerged": false,
"needsRebase": false,
"pr": {
"number": 43,
"url": "https://github.com/owner/repo/pull/43",
"state": "OPEN"
}
}
]
}
每个分支的字段:
name— 分支名称head— 当前 HEAD SHAbase— 上次同步时父分支的 HEAD SHAisCurrent— 是否为当前检出的分支isMerged— PR 是否已合并needsRebase— 基础分支是否不是祖先(非线性历史)pr— PR 元数据(不存在 PR 时省略)。state为"OPEN"或"MERGED"。
在堆栈中导航
无需记住分支名称即可在分支间切换。这些命令完全非交互。
gh stack up # Move up one branch (further from trunk)
gh stack up 3 # Move up three branches
gh stack down # Move down one branch (closer to trunk)
gh stack down 2 # Move down two branches
gh stack top # Jump to the top of the stack (furthest from trunk)
gh stack bottom # Jump to the bottom (first non-merged branch above trunk)
导航会限制在堆栈边界内。从活动分支导航时会跳过已合并的分支。
检出堆栈 — gh stack checkout
通过拉取请求编号或分支名称检出堆栈。始终提供参数 — 不带参数运行 gh stack checkout 会触发交互式选择菜单。
gh stack checkout <pr-number | branch>
# By PR number (pulls from GitHub)
gh stack checkout 42
# By branch name (local only)
gh stack checkout feature-auth
提供 PR 编号时(例如 123),此命令会从 GitHub 获取堆栈、拉取分支并在本地设置堆栈。如果本地已存在匹配的堆栈,则会切换到相应分支。
**⚠️ Agent 警告:**如果本地和远程堆栈的分支组成不同,此命令会触发交互式冲突解决提示,无法通过标志跳过。为避免这种情况,请先运行
gh stack unstack移除冲突的本地堆栈,然后重试gh stack checkout <pr-number>。
提供分支名称时,此命令只会在本地已跟踪的堆栈中解析该名称。这种方式始终适用于非交互式使用。
移除堆栈 — gh stack unstack
拆除堆栈,以便重新调整其结构——移除分支、重新排列分支、重命名分支或进行其他重大更改。取消堆栈后,使用 gh stack init 按所需结构重新创建堆栈。
如果未提供参数,该命令会针对活动堆栈(即包含当前检出分支的堆栈),在 GitHub 上取消堆栈并移除本地跟踪。
提供堆栈编号,可在 GitHub 上取消指定堆栈。无论当前位于仓库中的哪个位置,也无论该堆栈是否在本地检出,此操作都有效——它会直接通过 GitHub API 按编号取消堆栈(类似于 gh stack link,不需要本地跟踪)。如果该堆栈也在本地跟踪,则其本地跟踪也会被移除。
gh stack unstack [<stack-number>] [flags]
# Tear down the current stack (locally and on GitHub), then rebuild
gh stack unstack
gh stack init --base main branch-2 branch-1 branch-3 # reordered
# Unstack a specific stack by its number, from anywhere in the repo
gh stack unstack 7
# Only remove local tracking (keep the stack on GitHub)
gh stack unstack --local
| 标志 | 描述 |
|---|---|
--local | 仅在本地移除堆栈(在 GitHub 上保留);绝不联系 GitHub |
Agent 注意事项:
gh stack unstack <number>是一个远程优先的 API 封装命令——它可在仓库中的任何位置按编号在 GitHub 上取消堆栈,无论是否在本地跟踪,并且适用于非交互式使用。--local绝不联系 GitHub;如果将--local与本地未跟踪的编号搭配使用,则会报错。未知的堆栈编号会返回“在 GitHub 上未找到”错误(退出代码 2)。
输出约定
- 状态消息会输出到 stderr,并带有表情符号前缀:
✓(成功)、✗(错误)、⚠(警告)、ℹ(信息)。 - 数据输出(例如
view --json)会输出到 stdout。 - 在管道中传递输出时,如果只需要数据输出,请使用
2>/dev/null抑制状态消息。
退出代码与错误恢复
| 代码 | 含义 | Agent 操作 |
|---|---|---|
| 0 | 成功 | 正常继续 |
| 1 | 一般错误 | 查看 stderr 中的详细信息;可能表示提交或推送失败 |
| 2 | 不在堆栈中 | 先运行 gh stack init 创建堆栈 |
| 3 | 变基冲突 | 从 stderr 中解析冲突文件路径,解决冲突,然后运行 gh stack rebase --continue |
| 4 | GitHub API 失败 | 检查 gh auth status,然后重试命令 |
| 5 | 参数无效 | 修正命令调用(检查标志和参数) |
| 6 | 需要消除歧义 | 某个分支属于多个堆栈。先运行 gh stack checkout <specific-branch> 切换到一个非共享分支 |
| 7 | 变基已在进行中 | 解决冲突后运行 gh stack rebase --continue,或运行 gh stack rebase --abort 重新开始 |
| 8 | 堆栈已锁定 | 另一个 gh stack 进程正在写入堆栈文件。请等待后重试——锁会在 5 秒后超时 |
| 9 | 堆栈式 PR 不可用 | 此仓库未启用堆栈式 PR。交互模式下,submit 会提供创建常规(非堆栈式)PR 的选项 |
已知限制
- 堆栈严格采用线性结构。 不支持分支式堆栈(单个父级有多个子级)。每个分支恰好有一个父级,且至多有一个子级。如果需要并行工作流,请使用多个独立堆栈。
- 无法绕过堆栈歧义问题。 如果当前分支是多个堆栈的主干分支,命令会以代码 6 报错。请先检出一个非共享分支。
- 使用多个远程仓库时,必须指定
--remote或进行配置。 如果配置了多个远程仓库,请传入--remote <name>,或在运行push、sync或rebase前,在 git 配置中设置remote.pushDefault。 - 合并 PR: 目前不支持通过 CLI 合并堆栈式 PR。请引导用户在浏览器中打开 PR URL 来合并 PR。
- 检出远程堆栈需要 PR 编号。 使用分支名称执行
checkout仅适用于本地跟踪的堆栈。请使用 PR 编号(例如gh stack checkout 123)从 GitHub 拉取堆栈。 - PR 标题和正文会自动生成。
submit没有可用于设置自定义 PR 标题或正文的标志。标题和正文会根据提交消息及页脚生成。创建后,请使用gh pr edit修改 PR 标题和正文。