12k
All articles

GitHub 堆叠式 Pull Request 的工作原理

了解 GitHub 堆叠式拉取请求的运作方式:使用 gh-stack 创建堆栈、逐层审查、变基更新并合并依赖的 PR。

OpenReplay Team
OpenReplay Team
GitHub 堆叠式 Pull Request 的工作原理

GitHub 堆叠式 pull request(stacked pull requests)将一个大型变更拆分为一条有序的小型 pull request 链:每个 PR 都以其下方 PR 的分支为目标,GitHub 会将整条链作为一个整体进行评审、变基(rebase)和合并。

大多数团队都熟悉另一种做法。你分三个相互依赖的部分开发一个功能,提交三个 PR,结果第二个 PR 不仅显示自身的改动,还连带显示了第一个 PR 的改动。评审者看不下去,或者你干脆把所有内容合并成一个巨型 PR,而没有人会认真读完它。

本文将完整介绍 GitHub 原生堆叠功能的工作方式:如何创建堆叠、如何进行评审、当堆叠中间的某个 PR 发生变更时会怎样,以及各种合并选项的具体效果。如果你需要先复习一下普通 PR 的用法,请参阅如何在终端中创建 pull request。

核心要点

  • 在 GitHub 堆叠中,每个 pull request 的 base 都是其下方 PR 的分支,因此每个 diff 只包含该层的改动。
  • GitHub 于 2026 年 7 月 30 日宣布堆叠式 pull request 进入公开预览阶段。该功能可能会有变动。
  • 根据 GitHub 的快速入门指南,gh-stack CLI 扩展需要 GitHub CLI 2.90.0 或更高版本,以及 Git 2.20 或更高版本。
  • 如果中间某一层发生变更,其上方的所有分支都会停留在过时的提交上。这些分支必须自下而上依次变基并强制推送。
  • 合并最顶层的就绪 PR 时,它会连同其下方所有未合并的 PR 一次性合并。如果合并中途失败,已经合入的 PR 保持已合并状态,其余 PR 保持打开状态。每个 PR 都会按照堆叠 base 分支的规则进行检查。

依赖型 Pull Request 的问题

依赖型 pull request 之所以难以评审,是因为 GitHub 会将每个 PR 与其 base 分支进行比较。假设你依次提交了三个都以 main 为目标的 PR:auth-middleware、api-routes、ui-login。第二个 PR 的 diff 中同样包含所有中间件代码,第三个则包含全部内容。没有人能分辨出某个 PR 实际引入了哪些代码行。

在没有原生堆叠支持的情况下,开发者通常采用以下两种变通方式之一:要么把所有内容合并成一个大 PR,这会拖慢评审并掩盖错误;要么手动将每个 PR 指向其下方的分支,然后在每次发生变更时亲自对整条链进行变基和重新指定目标分支。

什么是 GitHub 堆叠式 PR?

GitHub 上的堆叠式 pull request 是一条有序链中的一环。每个 PR 以其下方 PR 的分支作为 base,因此其 diff 只显示该层所做的改动。GitHub 关于堆叠式 pull request 的概述中说明,最底层的 PR 以堆叠的主干(trunk)为目标,主干通常是 main,但也可以是任意分支,例如某个发布分支。堆叠中的所有分支必须位于同一个仓库中,因此堆叠无法跨 fork 使用。

上述三层示例在堆叠后如下所示:

main
 └─ auth-middleware   PR → main             (diff: middleware only)
     └─ api-routes     PR → auth-middleware  (diff: routes only)
         └─ ui-login    PR → api-routes       (diff: login UI only)

其底层模型仍然是 Git 本身就能理解的分支与 base。GitHub 在此之上增加了追踪能力:它知道各 PR 的顺序,将它们关联起来,并作为一个整体进行操作。

使用 gh CLI 创建堆叠

gh-stack 扩展可以在终端中创建和管理堆叠。GitHub 的快速入门指南将 GitHub CLI 2.90.0 或更高版本以及 Git 2.20 或更高版本列为前置条件。CLI 命令参考和该扩展的 README 给出的最低要求较低,为 gh 2.0,因此以快速入门指南中的版本要求为准更为稳妥。此外,你还需要完成身份认证,并拥有该仓库的推送权限。如果你刚接触 gh,可以先阅读在终端中创建 pull request。

gh --version        # 2.90.0 or later
git --version       # 2.20 or later
gh auth login
gh extension install github/gh-stack

接下来,逐层构建堆叠:

gh stack init                      # prompts for the first branch name, e.g. auth-middleware
git add -A
git commit -m "Auth middleware"

gh stack add api-routes
git add -A
git commit -m "API routes"

gh stack add ui-login
git add -A
git commit -m "Login UI"

gh stack push                      # pushes branches only
gh stack submit                    # opens PRs with correct bases and links them
gh stack view                      # branches, PR links, statuses, latest commits

gh stack init 会为堆叠建立本地追踪并创建第一个分支。除非传入 --base,否则主干默认为仓库的默认分支。CLI 命令参考特别指出,push 只上传分支,从不涉及 pull request。submit 才是负责创建 PR、为每个 PR 设置正确的 base 分支并将它们组合成堆叠的命令。

你也可以使用 Web 界面。在 github.com 上,你可以通过创建一个以先前某个 PR 的分支为 base 的 PR,并选择 Create stack 来创建堆叠。要扩展现有堆叠,打开其中任意一个 PR,然后在页头中选择 Add to stack。如果你已打开的 PR 本身已经构成一条链,GitHub 会显示一个横幅,提示将它们转换为堆叠。堆叠可以在 GitHub 移动应用中使用,也可以由 Copilot 等编码智能体通过 gh-stack skill 使用。GitHub Desktop 暂不支持该功能。

逐层评审堆叠

评审者可以分别批准堆叠中的每一层。不同的评审者可以同时评审不同的层,而作者可以继续在上层进行开发。堆叠式 pull request 的主要优势在于评审针对的是小规模 diff,而且无需等到整个功能完成才开始评审。

打开堆叠中的任意 PR 时,你只会看到该层的 diff。合并框中包含一个堆叠图,列出链中的每个 PR 及其状态,页面顶部堆叠图标上的数字则表示当前打开的是哪一层。

小型分层还能在 main 上留下更有价值的历史记录。每个合入的提交都聚焦于单一改动,因此 git bisect 能够精确定位到具体的变更,而不是一次上千行的合并。

更新堆叠中间的 PR

当堆叠中间的某个分支有新提交合入时,其上方的所有分支仍然指向旧的提交。这些上层分支必须变基到更新后的层上并强制推送。在本例中,对 api-routes 的一次评审修复会使 ui-login 落后,而 auth-middleware 不受影响。

对于手动管理的分支链,你需要用原生 Git 亲自完成这些操作(变基的基础知识请参阅 10 个 Git 命令):

# plain Git, no gh-stack
git checkout ui-login
git rebase api-routes              # resolve any conflicts
git push --force-with-lease
# repeat for every branch above, bottom to top

而使用 gh-stack 时,根据堆叠代码变更教程,这一过程可以简化为两条命令:

gh stack checkout api-routes
git add -A
git commit -m "Fix API routes"
gh stack rebase --upstack          # rebases only the branches above api-routes
gh stack push                      # per-branch --force-with-lease

如果变基因冲突而中止,请修复冲突文件,执行 git add,然后运行 gh stack rebase --continue。如果想放弃,gh stack rebase --abort 会将所有分支恢复到变基之前的状态。

在 github.com 上,一旦堆叠不再是线性的,合并框中就会出现 Rebase stack 按钮。根据堆叠式 pull request 管理指南,点击该按钮会从底层开始将各分支重放到最新的主干上,并逐一强制推送。以这种方式在 GitHub 服务器上生成的提交不带签名,因此要求签名提交的仓库应改为在本地使用 gh stack rebase 进行变基。

变基会重写上层分支。如果某位团队成员检出了其中某个分支,他应当先停下来与远程仓库同步,然后再提交新的内容,否则他的下一次推送将与被重写的历史发生冲突。

合并堆叠

合并最顶层的就绪 pull request 时,它会连同其下方所有未合并的 PR 一次性合并。如果中途出现失败(例如意外的冲突),合并会在该 PR 处停止:其下方已合并的 PR 保留在 base 分支上,失败的 PR 及其上方所有 PR 保持打开状态。如果只合并较低的层,其上方的 PR 会保持打开状态,同时 GitHub 会对它们进行变基,并将其 base 改为堆叠的 base 分支。合并指南要求堆叠必须具有线性历史,才能合并其中的任何部分。

你合并的 PR合入 main 的内容其余 PR 的情况
最顶层就绪 PR(ui-login)全部三个 PR不再有打开的 PR
最底层 PR(auth-middleware)仅该 PR上层 PR 变基并将目标改为 main
中间 PR(api-routes)该 PR 及其下方所有 PRui-login 保持打开并将目标改为 main

GitHub 堆叠中间的 PR 无法单独合并,它总是会与其下方的 PR 一起合入。根据 GitHub 堆叠式 pull request 参考文档,堆叠支持合并提交(merge commit)、压缩合并(squash)和变基合并(rebase)。使用 squash 时,每个 PR 会各自压缩为一个提交,因此合并三个 PR 会在 main 上产生三个提交。

合并规则来自堆叠的 base 分支,而不是每个 PR 直接指向的分支。因此,分支保护规则和必需检查仍然决定着哪些内容能进入 main。堆叠同样支持合并队列(merge queue),队列会按正确顺序接收各 PR。2026 年 7 月 30 日的更新日志称合并队列支持将逐步推出,而 GitHub 的合并指南现已将合并队列描述为完全支持。

原生支持之前的堆叠方案

在 GitHub 原生支持堆叠之前,你需要借助独立工具来管理分支链。Graphite、spr 和 git-branchless 都是在 GitHub 自身的 pull request 模型之外处理这些繁琐的管理工作。

GitHub 原生堆叠式 pull request 能够与你现有的评审流程、必需检查和分支保护规则协同工作,因此团队无需再引入其他工具。对于那些仅仅为了解决依赖型 PR 难以阅读的问题而转向堆叠工具的团队来说,这消除了使用这些工具的主要理由。

总结

GitHub 堆叠是一条 pull request 链,其中每个 PR 都以其下方的分支为 base,由 GitHub 负责变基、重新指定目标分支和合并。由于每一层都很小,评审者能够认真阅读;而堆叠中间的变更也只需对其上方的分支进行变基。想要亲自尝试,请安装 github/gh-stack,并将你的下一个多部分功能拆分为三层。

常见问题

GitHub 堆叠式 pull request 支持自动合并(auto-merge)吗?

不支持。GitHub 的合并文档明确指出,堆叠式 pull request 不支持自动合并。通过 API 进行合并的工具需要针对堆叠使用异步合并 API,因为旧版 pull request 合并端点无法合并堆叠。如果希望在检查通过后自动合入堆叠,请改用合并队列:堆叠支持合并队列,且队列会按堆叠顺序接收各 PR。

当某个堆叠式 pull request 被移出合并队列时会发生什么?

堆叠中位于其上方的所有 pull request 也会一并离开队列。为了保持堆叠的完整性,合并队列允许合并组(merge group)超出其配置的最大规模,最多超出 50%。如果堆叠过大,超出了这一额外空间,GitHub 会将其拆分到多个连续的合并组中。

最底层的 pull request 合并后,如何更新本地堆叠?

运行 gh stack sync。它会一步完成以下操作:拉取最新变更、将主干前移、将剩余分支变基到主干之上、推送这些分支,并刷新每个 pull request 的状态。添加 --prune 可删除已合并 pull request 对应的本地分支。如果 sync 遇到冲突,它会将所有分支恢复原状,此时请运行 gh stack rebase 来解决冲突,然后运行 gh stack push。

如何对现有堆叠中的层进行重新排序、删除或重命名?

使用 gh stack modify,这是一个交互式终端界面,可以删除(drop)、折叠(fold)、插入(insert)、重新排序(reorder)和重命名(rename)分支。它要求工作区干净、历史呈线性、没有正在进行的变基,且没有排队等待合并的 pull request。在同一会话中,你不能既重新排序分支,又进行删除、折叠、插入或重命名操作。你的修改只有在保存后才会生效,而 gh stack modify --abort 可以将堆叠恢复到开始修改之前的状态。完成后,运行 gh stack submit 将更新同步到 GitHub。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.