deepseek-harness 学习手册
← 返回 8.3 把工程约定写成 AI 可执行的形式
工程文化 · 8.3›Skill 08 · dsh-merging-stacked-prs
12 min

dsh-merging-stacked-prs

依赖 PR 栈(A ← B ← C)的官方落地法:为什么必须用 gh stack,而不是逐个 merge 再 retarget。

最后核对于 v0.1.1-rc.2 · commit b150a551b8 · 2026-08-22

这是 .agents/skills/dsh-merging-stacked-prs/SKILL.md 的中文精读。它管的是「PR 栈」的落地:A ← B ← C 这样一串互相依赖的 PR,怎么合进 master 才不把历史搅乱。

什么时候触发

把一串依赖 PR 落到 master、合并一个 base 是另一个开放 PR 分支的 PR,或请求里出现「stacked PRs」「PR stack」「dependent PRs」「按顺序合几个相关 PR」时使用。核心要求:每条同仓库的依赖链,落地前必须使用 GitHub 官方的 stacked-PR 功能,让 GitHub 来管栈级规则、CI、顺序、retarget 和合并状态。

核心立场

通过 GitHub 原生栈对象和 gh stack merge 落地依赖 PR。不要用 gh pr merge + gh pr edit 逐个合并再手动 retarget 去复刻栈语义。根 AGENTS.md 拥有允许的 merge-forward 与 rebase 历史规则;栈上 review 修复的传播另有 cookbook 指南管。

一、要求原生栈支持

改动任何 GitHub 状态之前先跑 gh stack --version。官方扩展或服务端栈功能不可用就硬停——不要退化成逐个手动合并加 retarget。GitHub 栈要求每个 head 分支都在同一个仓库,跨 fork 的链同样硬停。

用一个干净的专用 worktree。拉当前 PR 元数据和精确 head OID,而不是信任分支名或旧报告:

terminal
$ gh pr view <pr> --json number,author,baseRefName,baseRefOid,headRefName,headRefOid,isCrossRepository,state,isDraft,reviewDecision,mergeStateStatus,statusCheckRollup

对每条疑似链里的至少一个 PR 查 PullRequest.stack 和 stackEntry.position——这个官方对象(而不是 base 分支推断)才是栈成员资格的权威。size 超过返回页大小就分页 entries(原文给出完整 GraphQL 查询)。从 live PR base 建立自底向上的预期顺序:最底层指向主干,上面的每层指向下方的 head 分支。

先把已有栈条目与预期链对比。一个已有栈可以包含所请求链的保序子集;出现多个栈号、意外条目或冲突顺序,先问用户再动。

有依赖 PR 还没进官方栈时:

  1. 逐个精确比对 author.login;
  2. 作者全一致,就按自底向上顺序自动链接:gh stack link --base <trunk> <bottom-pr> <next-pr> ... <top-pr>;
  3. 作者不一致或查不到,先问用户是否链接,再动 GitHub 状态;
  4. 重新查 GraphQL,要求:一个栈号、预期主干、完整的 PR 集合、预期的位置与 base 链。

绝不自动解散、重排或重建已有栈;gh stack link 是只增的,已合并或已入队的条目无法出栈。

三、只在需要时刷新

不要因为「有刷新机制存在」就重写分支。live 合并状态或仓库规则确实要求跟上主干时,两种被允许的历史二选一:

review 之后允许任何历史重写,但它会使 commit-OID 假设失效:push 后重新拉精确 heads,重新审计未解决的 review 线程、approval、可合并性和检查。永远不用裸 --force,永不覆盖并发推进过的远端 head。

四、合并前检飞

合并前一刻重新查官方栈。要求每个选中的 PR:open、非 draft、顺序符合预期、满足仓库的 review 与检查要求。每个 PR 的状态独立判断——顶层 ready 证明不了它的依赖 ready。

「落地整个栈」就是选整个栈。部分落地必须显式给出边界 PR,范围是从最底层到该边界(含)的每一层。

五、通过栈 API 合并

terminal
$ gh stack merge <stack-number> --yes --merge    # 整栈
$ gh stack merge <boundary-pr> --yes --merge     # 显式请求的部分落地

不要传 --delete-branch,不要手动 retarget 依赖方,不要发逐个 PR 的合并命令。GitHub 会自底向上合并选中范围,并 retarget/rebase 剩余上层。直接栈合并是 all-or-nothing;主干用 merge queue 时,GitHub 把选中范围一起入队,但可能分批落地。

不要绕过合并要求。原生合并报阻塞就通过归属的 PR 检查并解决,或停下汇报——绝不退回 gh pr merge。

六、验证落地后的状态

等每个选中的 PR 报 MERGED——已入队不是已落地:

terminal
$ gh pr view <pr> --json number,state,mergedAt,mergeCommit,baseRefName,headRefName
$ gh pr list --state open --base <branch> --json number --jq length   # 删分支前必须为 0

部分落地要重查官方栈:每个剩余 PR 仍按预期顺序链接,指向栈主干或下一层;重新检查当前 heads、review 状态和 CI——GitHub 可能已经 rebase 了剩余层。

删分支只在所有对应 PR 报 MERGED 之后、作为单独的收尾步骤进行;删除前要求 GitHub 报告没有任何开放 PR 还拿它当 base,任何非 0 结果都阻止删除。

清单

← 返回 8.3 把工程约定写成 AI 可执行的形式
目录

本页

Skill 08 · dsh-merging-stacked-prs