Communitygithub.com

合并堆叠拉取请求到基础分支

将一串相互依赖的堆叠拉取请求(每个PR基于前一个PR的分支)逐层合并到目标基础分支(develop/main/master),无需为中间分支配置CI触发白名单。支持通过merge-async API处理GitHub原生堆叠PR。

合并堆叠拉取请求到基础分支 是什么?

该技能用于高效处理堆叠的拉取请求链(PR N基于PR N-1分支)。其核心原则是:**仅依赖基础分支的保护规则,而非中间分支的CI检查**。因此,系统会在合并前动态地将每一层PR的基础分支重定向为目标基础分支,并利用该分支的原生CI进行验证,避免在CI触发器中硬编码中间分支名称。 技能支持两种场景:常规堆叠PR和GitHub原生堆叠PR(通过merge-async API)。对于后者,技能会从最底层的PR开始合并,由GitHub自动将上一层PR重定向到基础分支。每次合并后,技能会更新当前层PR以同步基础分支的最新代码,并检查CI状态,确保仅在审核通过后执行合并。 该技能特别适合CI白名单限制严格、中间分支无法运行CI,或分支保护规则严格的环境。它适用于Claude Code、Cursor、Codex等AI代理工具,通过命令行工具`gh`与GitHub API交互。 使用前需确认真实的依赖顺序,并通过`git merge-tree`检测每个PR与基础分支的冲突。冲突解决策略包括:对工作流文件保留双向修改,对格式化冲突采用基础分支内容后重新格式化等。安全方面禁止强制推送、历史重写或推送到受保护分支,且每次合并前必须等待人工审核批准。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/sblattj/agent-chandlery/tree/main/skills/landing-stacked-prs

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

将堆叠拉取请求合并到基础分支

当多个拉取请求串成一条链(PR N 基于 PR N-1 的分支),且需要最终合并到真实的基础分支时使用。关键原则:中间分支的 CI 不是关卡,基础分支的保护才是。 因此,即时地将每一层重定向到基础分支,让原生的 CI 对其进行验证。切勿在 CI 触发器中硬编码中间分支名称。

仓库:向 gh 传递 -R owner/repo,或从克隆的仓库中运行(gh repo view --json nameWithOwner -q .nameWithOwner 可解析出)。下文中的 R 即该 owner/repo。

原则

  • 合并顺序由真实依赖关系决定(内容依赖 + 被多个层编辑的文件)。首先确立该顺序,切勿为了方便而重排。
  • 每一层仅合并到基础分支,一次只合并一层。切勿将子分支合并到其旧的先决条件分支。
  • 在某层 CI 生效之前,先将当前基础分支合并到该层,这样它的差异就只包含自身的变更,并已包含所有已合并的内容。
  • 最顶层的分支累积了所有层的变更;将顶层分支重定向到基础分支后运行一次 CI,即可验证整个堆叠。可作为可选的预检步骤。

第 0 步:确立顺序并检测冲突

确定仓库的真实基础分支(从 git 远程获取的默认分支,例如 develop 或 main;用户的代理指令文件(AGENTS.md / CLAUDE.md)中可能列出已知的基础分支)。针对每个 PR,检测其与基础分支的冲突,而非与其过时的堆叠基础分支(后者会显示为“干净”从而误导你):

git fetch origin <base> <head> --quiet
git merge-tree --write-tree origin/<base> origin/<head> | grep -iE 'CONFLICT|changed in both' || echo "clean vs base"

顺序经验法则:先合并先决条件内容,再合入门控(例如,先合并 lint 清理,再合并启用 lint);当两个层编辑了同一个文件时,较早的层优先合并。

第 1..N 步:按顺序处理每个层

PR=<number>
gh pr edit $PR -R $R --base <base>     # 从堆叠分支重定向
# 为头分支创建独立的工作树,保持主 checkout 干净:
WT=../<repo>-<head>                    # 主 checkout 外部的任意路径
git worktree add "$WT" <head> 2>/dev/null || true
git -C "$WT" fetch origin --quiet
git -C "$WT" merge origin/<base>       # 同步基础分支;解决冲突(见下文)
git -C "$WT" push
gh pr ready $PR -R $R                  # 当该层为当前活动层时
gh pr checks $PR -R $R --watch         # 原生 CI,base=<base>
# 仅审核通过后:
gh pr merge $PR -R $R --squash --delete-branch
# 然后移至下一层(其重定向和基础同步将吸收本层)

GitHub 原生堆叠(merge-async)

使用 GitHub 原生堆叠功能创建的 PR 拒绝上述经典步骤:

  • gh pr edit --base:GraphQL: Cannot change the base branch because the pull request is part of a stack. (updatePullRequest)
  • gh pr merge --auto:GraphQL: Auto-merge is not supported for stacked pull requests.
  • gh pr merge:This pull request is part of a stack and must be merged using the asynchronous merge REST API.

使用异步 API,从最底层的 PR 开始合并(GitHub 会自动将下一层 PR 重定向到基础分支,需等待约 30 秒):

sha=$(gh pr view $PR -R $R --json headRefOid -q .headRefOid)
u=$(gh api -X PUT repos/$R/pulls/$PR/merge-async -f merge_method=squash -f sha=$sha \
      -f merge_action=direct_merge --jq '.details.uuid // .status')
gh api repos/$R/pulls/$PR/merge-async/$u --jq '.status+" "+(.details.message//"")'   # 轮询直到状态不为 "pending"
  • 通过此方式合并一个堆叠 PR 也会同时合并其下方所有打开的 PR,因此只合并你真正要落地的层。
  • 每次 squash 合并后,下一层通常会因双方都触及的文件(如 ADR、版本列表)而变“脏”:将基础分支合并到该层,解决冲突,推送,等待 CI,然后对其实施 merge-async。
  • 在不撤销陈旧审核的仓库中(检查分支保护中的 required_pull_request_reviews.dismiss_stale_reviews),审核状态会在基础同步后保留。真正的门控通常是每个触及路径的 CODEOWNERS 审批,而非审批数量(pr-approval-gate 会列出缺口)。
  • 为在落地过程中保持每个打开的 PR 与其基础分支同步,可执行一次基础同步过程:将基础分支合并到每个打开的 PR 分支并推送(普通合并,不强制推送)。

冲突解决

  • 被多个层编辑的工作流文件(可重用的构建工作流、CI 协调器工作流):保留双方的修改,不要选择某一侧。
  • 格式化冲突(例如 Spotless/Prettier 应用清理层与基础分支上的内容变更):对于冲突文件,采用基础分支的内容,然后对其重新运行格式化工具(mvn -pl <module> -am spotless:apply 或 pnpm fmt)。采用清理侧会丢弃真实更改;采用基础分支而不重新格式化则会失败。格式化工具的运行需要构建解析(某些仓库从需要云端登录的私有注册表拉取依赖;如果构建无法获取依赖,请刷新凭据)。

安全性

  • 不要强制推送、重写历史或推送到受保护分支。
  • 遵循仓库的提交作者约定(某些仓库禁止 AI 合著者签名)。
  • 每次合并到基础分支前,等待人工审核批准。
  • 如果存在中间分支 CI 白名单 PR(本技能试图替代的反模式),建议关闭它;如果已合并,请恢复添加的分支名称和事件类型。

相关

  • opening-a-stacked-pr-sequence:创建堆叠(基础分支、合并顺序)以及每种合并类型的重排修复(stack.sh restack)。
  • pr-approval-gate:每一层仍需要的代码所有者。
  • resolve-pr-threads-and-conflicts-and-merge:在线程和审批就绪后合并一层。

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

相关技能