mcpField
N
n8n:gh-stack

n8n:gh-stack

github.com/n8n-io开发209.6k64 次安装今天更新
代码仓库

关于 n8n:gh-stack

用于操作 `gh stack` 的技能;该 GitHub CLI 扩展管理相互依赖的分支和拉取请求。技能强调非交互式命令,并指导组织、导航、提交、同步和合并分层变更。

主要功能

  1. 01按依赖关系规划分支层级
  2. 02创建并导航分支堆栈
  3. 03提交和同步堆叠式拉取请求
  4. 04关联由外部工具管理的分支
  5. 05按范围和方式合并堆栈
  6. 06避免交互式命令与提示

适用场景

  1. 01将大型变更拆分为易审查的拉取请求
  2. 02修改底层分支后变基依赖分支
  3. 03查看堆叠式拉取请求状态
  4. 04合并一组相关的拉取请求

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/n8n-io/n8n --skill n8n:gh-stack

安装后在 Claude Code 中输入/n8n: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 时,命令会启动交互式 TUI,Agent 无法操作。没有其他合适的标志——始终传入 --json。
  4. 处理多个远程仓库。 如果配置了多个远程仓库,请预先配置 git config remote.pushDefault origin,或为支持该选项的命令传入 --remote <name>:push、submit、sync、rebase 和 link。checkout、modify 和 trunk 会解析远程仓库,但没有 --remote 标志——它们依赖 remote.pushDefault。如果配置了多个远程仓库但没有设置默认值,这些命令在非交互模式下会报错退出。
  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,请提供堆栈编号,后接新分支/PR(例如 gh stack link 7 branch-c)。
  10. 使用 gh stack merge --yes 合并堆栈式 PR。 gh pr merge 不适用于堆栈式 PR。在非交互式终端中,gh stack merge 会在不提示的情况下从底部到顶部原子性地合并整个堆栈;传入 --yes 可明确表示确认。可以传入拉取请求编号来限定合并范围(gh stack merge 42 --yes 会合并截至 PR #42(含该 PR)的所有内容),也可以传入堆栈编号(gh stack merge 7 --yes,无需本地检出)。使用 --squash、--rebase、--merge 或 --merge-method <method> 选择合并方式;若不指定,则使用上次使用的方式。合并要么全部成功,要么全部失败——如果任何 PR 无法合并,则一个也不会合并,并会报告失败原因。合并前只会检查基本的拉取请求状态(处于打开状态且不是草稿);堆栈不支持绕过合并要求。如果基分支使用合并队列,堆栈会被加入队列,而不是直接合并:队列会选择合并方式(你传入的任何方式都会被忽略,并显示警告),这些拉取请求会一起加入队列,但会随着队列的处理分别合并,因此它们可能分批进入主线,而不是一次性全部合并。 绝不要执行以下任何操作——每项操作都会触发交互式提示或 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 --local 移除本地跟踪状态(这会保留 GitHub 上的堆栈),然后重试 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
合并当前整个堆栈gh stack merge --yes
按编号合并堆栈gh stack merge 7 --yes
合并到指定 PR 为止gh stack merge 42 --yes
使用指定方法合并gh stack merge --yes --squash

工作流

端到端:从头创建堆栈
# 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 提示。请显式使用 --prune,以删除已合并 PR 对应的本地分支。

给 Agent 的提示: sync 也会将 GitHub 上的堆栈同步到本地。如果在 github.com 上向堆栈添加了 PR,其分支会自动拉取并追加到本地堆栈。如果本地堆栈和远程堆栈发生分歧(你修改了本地堆栈,同时远程堆栈也以不同方式发生了变化),sync 只能在交互式终端中提示你解决;在非交互环境中,它会中止同步(不会推送或更新任何内容)并成功退出,显示 ℹ Sync aborted。若要解决分歧,请取消堆栈并重新创建。

Squash 合并后的恢复

在 GitHub 上对 PR 执行 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 local tracking and the GitHub stack grouping (PRs are NOT deleted)
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 但未提供分支名称,名称会根据提交消息自动生成,格式为日期+slug(例如,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 检查
  • 如果某个分支被拒绝,其他分支可能仍会更新;修复被拒绝的分支后重新运行命令
  • 不会创建或更新拉取请求——请使用 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 检查;整个提交操作不是原子的
  • 如果后续分支推送被拒绝,先前的分支推送和 PR 更新仍会保留;解决拒绝问题后,重新运行同一命令
  • 为尚无 PR 的每个分支创建一个新 PR(将第一个未合并的祖先分支设为基分支)
  • 创建 PR 后,在 GitHub 上将它们关联为一个堆栈(要求仓库已启用堆叠功能)
  • 如果堆栈中的所有 PR 均已合并,该堆栈即已完成,无法继续扩展。submit 会自动将未合并的分支分叉到一个以主干为根的新堆栈,并在 GitHub 上创建该堆栈,原有的已合并堆栈保持不变。
  • 如果堆叠功能不可用(退出代码为 9),则表示仓库未启用堆叠 PR。交互模式下,submit 会询问是否改为创建普通(非堆叠)PR;非交互模式下,则以代码 9 退出。
  • 同步已有 PR 的分支元数据

PR 标题自动生成(--auto):

  • 分支上只有一次提交 → 使用提交主题作为 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>堆栈底部的基分支(默认为仓库的默认分支)
--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 — 若不传入,此命令会启动 Agent 无法操作的交互式 TUI。

# 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,
      "isQueued": false,
      "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,
      "isQueued": 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 是否已合并
  • isQueued — PR 是否在合并队列中等待合并
  • needsRebase — 基分支是否不是祖先(历史记录非线性)
  • pr — PR 元数据(不存在 PR 时省略)。state 为 "OPEN"、"MERGED" 或 "QUEUED"。

在栈中导航

在分支间切换,无需记住分支名称。这些命令完全以非交互方式运行。

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 trunk       # Jump to the trunk branch (e.g. main)

导航会限制在栈的边界内。从活跃分支导航时会跳过已合并的分支。


检出栈 — gh stack checkout

通过栈编号、拉取请求编号、PR URL 或分支名称检出栈。务必提供参数 — 不带参数运行 gh stack checkout 会触发交互式选择菜单。

gh stack checkout <stack-number | pr-number | pr-url | branch>
# By stack number (the identifier shown in the GitHub stack UI)
gh stack checkout 7

# By PR number (pulls from GitHub)
gh stack checkout 42

# By PR URL
gh stack checkout https://github.com/owner/repo/pull/42

# By branch name (local only)
gh stack checkout feature-auth

纯数字会优先解析为栈编号(GitHub 栈界面中显示的标识符);如果没有对应编号的栈,则会尝试将其解析为 PR 编号,最后尝试作为分支名称。如果提供了栈编号或 PR 编号(或 PR URL),命令会从 GitHub 获取栈、拉取分支,并在本地设置栈。如果该栈已存在于本地且匹配,则会切换到对应分支。

⚠️ Agent 警告: 如果本地栈与远程栈的分支组成不同,此命令会触发无法通过标志绕过的交互式冲突解决提示。为避免此情况:先运行 gh stack unstack --local,移除冲突的本地跟踪状态(这不会影响 GitHub 上的栈),然后重试 gh stack checkout <pr-number>。

提供分支名称时,命令只会在本地跟踪的栈中解析该名称。这种用法始终适合非交互式使用。

移除堆栈 — gh stack unstack

拆除堆栈,以便重新调整其结构 — 移除分支、调整分支顺序、重命名分支,或进行其他重大更改。取消堆栈后,使用 gh stack init 按所需结构重新创建堆栈。

取消堆栈只会移除堆栈分组(在 GitHub 和/或本地);绝不会删除底层的拉取请求或分支。

不提供参数时,此命令会针对当前活动堆栈 — 即包含当前检出分支的堆栈 — 在 GitHub 上取消堆栈并移除本地跟踪。

提供堆栈编号可在 GitHub 上取消指定堆栈。无论当前位于仓库的什么位置,此操作都有效,无论本地是否检出了该堆栈 — 此编号会直接通过 GitHub API 取消堆栈(类似于 gh stack link,无需本地跟踪)。如果该堆栈也在本地跟踪,其本地跟踪也会一并移除。

gh stack unstack [<stack-number>] [flags]
# Tear down the current stack — removes local tracking and the GitHub grouping (PRs are NOT deleted), 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。告知用户必须先在仓库中启用堆栈
10需要恢复 Modifygh stack modify 会话被中断。此技能不会使用 modify,因此 Agent 不应导致此情况;如果仓库处于此状态,请运行 gh stack modify --abort 以恢复到修改前的状态

已知限制

  1. 堆栈严格线性。 不支持分支堆栈(单个父分支有多个子分支)。每个分支恰好有一个父分支,且最多有一个子分支。如需并行工作流,请使用单独的堆栈。
  2. 无法绕过堆栈歧义检查。 如果当前分支是多个堆栈的主干,命令会报错,错误代码为 6。请先切换到非共享分支。
  3. 使用多个远程仓库时,需要指定 --remote 或配置。 如果配置了多个远程仓库,请在 git 配置中设置 remote.pushDefault,或对支持该选项的命令(push、submit、sync、rebase、link)传入 --remote <name>。checkout、modify 和 trunk 没有 --remote 标志,依赖 remote.pushDefault。
  4. 检出远程堆栈时需要堆栈编号或 PR 编号。 使用分支名称执行 checkout 仅适用于本地跟踪的堆栈。请使用堆栈编号或 PR 编号(例如 gh stack checkout 7 或 gh stack checkout 123),从 GitHub 拉取堆栈。
  5. PR 标题和正文会自动生成。 执行 submit 时没有用于设置自定义 PR 标题或正文的标志。标题和正文根据提交消息及页脚生成。创建后可使用 gh pr edit 修改 PR 标题和正文。

评论

支持 Markdown