mcpField
G
gh-stack

gh-stack

github.com/arize-ai开发12.1k59 次安装今天更新
代码仓库

关于 gh-stack

用于操作 `gh stack` 的技能;该 GitHub CLI 扩展可管理相互依赖的分支和拉取请求。它指导编码 Agent 以非交互方式创建、导航、同步分支栈并处理评审流程。

主要功能

  1. 01创建和组织依赖分支栈
  2. 02在栈的各层间导航
  3. 03变基并同步分支
  4. 04提交堆叠式拉取请求
  5. 05查看状态并重组分支栈

适用场景

  1. 01将大型改动拆分为易审查的 PR
  2. 02在依赖改动下方更新低层分支
  3. 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 和交互式菜单。如果命令需要提示输入,就会无限期挂起。

  1. 始终将分支名称作为位置参数传递给 init、add 和 checkout。 不带参数运行这些命令会触发交互式提示。分支名称会按给定内容原样使用,不会添加前缀或转换,因此 gh stack add refactor/foo 会创建名为 refactor/foo 的分支。
  2. 始终在 gh stack submit 中使用 --auto,以自动生成 PR 标题。不使用 --auto 时,submit 会提示为每个新 PR 输入标题。
  3. 始终在 gh stack view 中使用 --json。 不使用 --json 时,命令会启动 Agent 无法操作的交互式 TUI。没有其他合适的标志——始终传入 --json。
  4. 配置了多个远程仓库时,使用 --remote <name>,或者预先配置 git config remote.pushDefault origin。否则,push、submit、sync、link 和 checkout 会触发交互式远程仓库选择器。
  5. 避免在多个堆栈之间共享分支。 如果一个分支属于多个堆栈,命令会以代码 6 退出。请先切换到非共享分支。
  6. 编写代码前,按依赖顺序规划堆栈层级。 基础性更改(模型、API、共享工具)放在较低层分支;依赖这些更改的内容(UI、使用方)放在较高层分支。在运行 gh stack init 前,先理清依赖链。
  7. 使用标准的 git add 和 git commit 来暂存和提交。 这样你可以完全控制每个分支包含哪些更改。虽然可以使用快捷方式 -Am,但不应将其作为默认做法——只有当每个分支都包含精心挑选、逻辑清晰的一组更改时,堆叠式 PR 才最有效。
  8. 需要更改较低层时,沿堆栈向下切换。 如果你正在前端分支上工作,发现需要更改 API,不要在当前层绕过问题。切换到合适的分支(gh stack down、gh stack checkout 或 gh stack bottom),在该分支上进行更改并提交,运行 gh stack rebase --upstack,然后再向上切换以继续工作。
  9. 外部工具工作流中使用 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
推送分支 + 创建草稿 PRgh stack submit --auto
创建可供审查的 PRgh 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 对应的本地分支

执行步骤(按顺序):

  1. 拉取远程的最新更改
  2. 协调远程堆栈 — 在本地镜像 GitHub 堆栈。如果在 GitHub 上向堆栈添加了 PR,则拉取其分支并将其追加到本地堆栈。如果本地堆栈与远程堆栈已分叉,则在非交互式终端中中止同步。在交互式终端中,会提示你解决分叉问题(用远程版本替换本地堆栈、删除 GitHub 上的堆栈以便重新创建,或取消)。
  3. 快进主干分支以与远程保持一致(如果已是最新状态则跳过;如果已分叉则发出警告)
  4. 级联变基所有堆栈分支,使其基于更新后的父分支(仅当主干分支发生变化时)。自动处理已合并的 PR。如果检测到冲突,所有分支都会恢复到变基前的状态,且命令以代码 3 退出 — 解决流程请参阅处理变基冲突
  5. 推送所有活动分支(以原子方式)
  6. 从 GitHub 同步 PR 状态并报告每个 PR 的状态
  7. 同步堆栈对象 — 在 GitHub 上将打开的 PR 关联到一个堆栈。如果这些 PR 尚未加入堆栈,则会创建一个新堆栈;如果部分 PR 已在堆栈中,则会更新该堆栈(仅添加)。只有存在两个或更多 PR 时才会执行此操作。同步绝不会打开 PR — 要打开 PR,请使用 gh stack submit
  8. 清理 — 在交互式终端中,会提示删除已合并 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 SHA
  • base — 上次同步时父分支的 HEAD SHA
  • isCurrent — 是否为当前检出的分支
  • 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
4GitHub 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 的选项

已知限制

  1. 堆栈严格采用线性结构。 不支持分支式堆栈(单个父级有多个子级)。每个分支恰好有一个父级,且至多有一个子级。如果需要并行工作流,请使用多个独立堆栈。
  2. 无法绕过堆栈歧义问题。 如果当前分支是多个堆栈的主干分支,命令会以代码 6 报错。请先检出一个非共享分支。
  3. 使用多个远程仓库时,必须指定 --remote 或进行配置。 如果配置了多个远程仓库,请传入 --remote <name>,或在运行 push、sync 或 rebase 前,在 git 配置中设置 remote.pushDefault。
  4. 合并 PR: 目前不支持通过 CLI 合并堆栈式 PR。请引导用户在浏览器中打开 PR URL 来合并 PR。
  5. 检出远程堆栈需要 PR 编号。 使用分支名称执行 checkout 仅适用于本地跟踪的堆栈。请使用 PR 编号(例如 gh stack checkout 123)从 GitHub 拉取堆栈。
  6. PR 标题和正文会自动生成。 submit 没有可用于设置自定义 PR 标题或正文的标志。标题和正文会根据提交消息及页脚生成。创建后,请使用 gh pr edit 修改 PR 标题和正文。

评论

支持 Markdown