PR 分类
目标
在单一专注会话中,将积压的开放PR转化为已发布的合并集合。生成一个可追踪、可恢复的计划(<VERSION>_PR_TRIAGE.md),然后执行该计划——在需要时进行变基,以隔离安全的批次进行合并,应用合并后的跟进操作,并关闭已取代或部分适用的PR,同时向作者致谢。
此技能与 draft-release-notes 和 release-bump 配合使用:先分类,然后针对新的主分支起草发布说明,最后进行发布。
何时使用
- 在次要或主要版本发布前,当积累了10个或更多开放PR时
- 当您希望在不丢失合并内容叙述的情况下解锁合并时
- 当您知道自己无法深入审查每个PR,但需要快速合并关键子集时
先决条件
- 已针对仓库进行身份验证的
ghCLI - 用于PR审查的专用工作树(避免将贡献者分支的检出污染到
main) - 明确目标版本——分类文档以该版本命名(例如
0.4.0_PR_TRIAGE.md)
工作流
1. 设置隔离的PR审查工作树
git worktree list # 先检查是否有陈旧的
git worktree prune
git worktree add ../voicebox-pr-review -b pr-review-<VERSION> main
保留主工作树用于发布准备工作(变更日志草稿、直接提交到主分支的跟进操作)。保留审查工作树用于 gh pr checkout——每次检出都会将HEAD移动到贡献者分支,您不希望在主工作树中进行此操作。
2. 收集每个开放PR的元数据
gh pr list --state open --limit 50 --json \
number,title,author,isDraft,mergeable,mergeStateStatus,files,additions,deletions,reviewDecision,statusCheckRollup,maintainerCanModify \
--jq '.[] | {num: .number, title, author: .author.login, mergeable, state: .mergeStateStatus, canModify: .maintainerCanModify, changes: "+\(.additions)/-\(.deletions)", files: [.files[].path]}'
对于每个PR,您需要:
- 大小(
+新增/-删除) - 可合并状态(
CLEAN,UNSTABLE,DIRTY= 冲突,UNKNOWN= GitHub仍在计算) - 是否允许维护者编辑分支(稍后如果需要为作者变基时需要)
- 涉及的文件路径(有助于发现PR之间的重叠)
UNKNOWN 在推送到主分支后很常见——只需尝试合并即可。
3. 分类到层级
将每个PR精确放入一个桶中:
层级1 — 合并: 小的、可合并的、修复了真实错误、CI干净、审查成本低。一行代码修改、依赖放宽、针对性的安全性强化。这些都是轻松获胜的。
层级2 — 候选,需要审查: 中等大小(50-200行)、涉及更多表面区域、看起来合理但需要更仔细的阅读。符合产品方向的新用户面向功能。
已取代: 修复或功能已被已合并的内容覆盖。关闭并评论指向取代它的PR。仔细检查——“相似标题”不是证据;比较实际的差异。
推迟到下一个版本: 大型功能、冲突、草稿PR、任何以可能引入风险的方式触及发布管道的内容。不要在冲刺中合并这些——它们需要专门的关注。
4. 编写分类文档
在PR审查工作树的根目录创建 <VERSION>_PR_TRIAGE.md。结构如下:
# <仓库> <VERSION> — PR 分类
跟踪哪些开放PR将在<VERSION>中合并的工作文档。发布切割后删除。
最后更新:<日期>
## 进度
**层级1:0 / N 已合并**
**层级2:0 / M 已处理**
**已取代分类:待定**
---
## 合并到<VERSION> — 关键错误修复
| PR | 状态 | 大小 | 修复内容 | 为何必须合并 |
|---|---|---|---|---|
| [#123](url) | [ ] | +5/-0 | ... | ... |
## 强候选 — 需要快速审查
| PR | 状态 | 大小 | 摘要 |
|---|---|---|---|
## 关闭为已取代
| PR | 状态 | 原因 |
|---|---|---|
## 推迟到<NEXT_VERSION>
- [#xxx](url) ... — 原因
---
## 攻击顺序
1. 关闭已取代的PR(一行评论)
2. 在无依赖冲突的批次中合并层级1 — 检查文件路径不重叠
3. 单独审查层级2
4. 重新运行 `draft-release-notes` 以拾取所有内容
5. 运行 `release-bump`
进度 标题是最重要的部分——它是您的记分板,并允许在会话中断时干净地恢复。
5. 循环工作 — 每个PR
对于层级1/层级2列表中的每个PR:
a. 在审查工作树中检出:
cd ../voicebox-pr-review
git checkout pr-review-<VERSION> # 重置到中性基础
git pr checkout <N>
b. 阅读实际提交,而不是 main..HEAD:
git show HEAD # PR的实际更改
git show --stat HEAD # 涉及的文件 + 行数
如果PR分支早于主分支,不要通过 git diff main..HEAD 审查。该差异包括PR分叉后合并到主分支的每个提交作为 -(删除)行。一个3行的PR可能看起来像一个700行的还原。这是最容易误判PR的方法。
c. 评估关注点: 正确性、范围、与已合并工作的交互、版本兼容性(例如,不能使用需要尚未固定的依赖版本的API)。
d. 如果分支落后于主分支,进行变基:
git fetch origin main
git rebase origin/main
这在进行压缩合并之前至关重要。GitHub的压缩计算diff(PR头, 合并基础)——在陈旧分支上,该差异包括还原每个中间提交。变基将合并基础向前移动,使压缩干净。
e. 如果允许维护者编辑,将变基后的分支推送回贡献者的分支:
git remote add <作者> https://github.com/<作者>/<仓库>.git
git fetch <作者> <分支> # 首先获取他们的ref
git push <作者> HEAD:<分支> --force-with-lease
这使GitHub的PR UI与变基后的状态同步,并从GitHub端使合并干净。
f. 合并:
git pr merge <N> --squash
g. 更新分类文档——将复选框翻转为 ✅ merged <sha>(使用来自 gh pr view <N> --json mergeCommit --jq '.mergeCommit.oid[0:7]' 的短SHA)。更新进度标题。
6. 批量处理小修复
对于更改≤5行、CI干净、文件路径不重叠且意图明显正确(例如,一行依赖放宽、环境变量添加、导入路径修复)的PR,可以在一个循环中合并,无需每个PR的审查仪式:
for pr in 425 384 416 429; do
echo "=== 合并 PR $pr ==="
gh pr merge $pr --squash
done
之后验证每个PR已干净合并:
for pr in 425 384 416 429; do
gh pr view $pr --json state,mergeCommit --jq "{pr: $pr, state, sha: .mergeCommit.oid[0:7]}"
done
7. 合并后跟进
有时,尽管存在已知的次要问题(例如,不完整的dtype映射、过时的哨兵清理),PR仍值得合并。不要阻止合并;立即以正常分支+PR的形式应用跟进:
cd <主工作树>
git pull --ff-only origin main
git checkout -b fix/<简短名称>
# 编辑...
git commit -m "fix(<领域>): <一行描述>"
git push -u origin fix/<简短名称>
git pr create --title "..." --body "跟进 #<N>. ..."
在分类文档中记录两个SHA(✅ merged <pr-sha> + 跟进 <pr>)。
直接提交到主分支的例外: 仅在明确、限定范围的策略下(例如“发布冲刺”)。不要默认采用。
8. 已取代:使用指向致谢的评论关闭
git pr close <N> --comment "关闭 — 被已合并的 #<M> 取代,该PR <简短描述>。谢谢!"
首先检查差异——“相似标题”不够。如果PR是部分被取代(诊断正确但只有一半的更改仍然需要),则改用部分应用。
9. 部分应用模式
当PR同时包含有价值的和有问题的更改时:
cd <主工作树>
git pull --ff-only origin main
# 从PR分支挑选特定文件
git checkout <pr提交sha> -- <文件1> <文件2>
# 审查暂存的更改,根据需要调整
git diff --cached
# 对不想批量替换的文件进行手术式编辑
# (例如,PR的文件早于需要保留的最近主分支提交)
# 使用致谢原始作者的尾注提交
git commit -m "$(cat <<'EOF'
<主题>
<正文解释保留和放弃的内容>
Co-Authored-By: <作者> <[email protected]>
EOF
)"
git push ... # 分支 + PR,除非在直接提交到主分支的例外下
然后关闭PR,并附带评论解释应用和放弃了什么,引用提交SHA。
10. 保持文档更新
每次合并、每次关闭、每次跟进 → 更新 <VERSION>_PR_TRIAGE.md。文档是您的会话日志。如果您被中断并在明天恢复,文档是“我在哪里”的唯一真实来源。
11. 当分类完成时
- 文档中的每个PR都有最终状态(✅ 已合并 / ✅ 已关闭 / 已推迟)
- 进度标题显示每个层级的 N/N
- 下一个要运行的技能是
draft-release-notes(以针对新的主分支重新生成[Unreleased]),然后是release-bump
您可以在发布发货后删除分类文档,或将其保留在版本历史中作为记录。
陷阱
- 过期分支上的
main..HEAD是谎言。 它显示分支分叉后主分支获得的每个内容作为删除行。始终通过git show HEAD审查PR的实际提交。 - 在未变基分支上进行压缩合并会还原中间工作。 压缩计算
diff(PR头, 合并基础)。变基将合并基础向前移动。 mergeable=UNKNOWN是瞬态的——GitHub在推送后重新计算。只需尝试合并。- 路由顺序很重要(FastAPI等):
DELETE /history/failed必须注册在DELETE /history/{id}之前,否则参数化路径会将"failed"作为ID使用。 - Apple的
-weak_framework覆盖-framework对于同一个框架,无论顺序如何——当依赖项硬链接某个可选内容时,通过cargo:rustc-link-arg=-Wl,-weak_framework,Name使用它。 - 依赖版本下限限制了您可以应用的内容。 在接受诸如
torch_dtype=→dtype=之类的kwarg重命名之前,检查最低版本固定是否支持它。有时正确的做法是挑选PR的一半。 cpal::Stream和类似的!Send音频类型 不能跨越await点或spawn_blocking。有时“不理想但正确”的同步等待是可用的最佳修复;标记但不阻止。- PyTorch nightly构建不可用于发布——非确定性,可能在运行之间回归。如果PR建议切换到nightly以修复GPU问题,更倾向于使用
TORCH_CUDA_ARCH_LIST=...+PTX或等待稳定支持。
规范命令参考
# 批量PR元数据
git pr list --state open --limit 50 --json number,title,author,mergeable,mergeStateStatus,additions,deletions,maintainerCanModify,files
# 详细单一PR视图
git pr view <N> --json body,author,headRefName,baseRefName,mergeable,maintainerCanModify,files,statusCheckRollup
# 实际提交,而不是分支与主分支的差异
git show HEAD
git show --stat HEAD
git pr diff <N>
# 将贡献者分支变基到当前主分支
git fetch origin main && git rebase origin/main
# 将变基后的分支推送回贡献者分支(需要maintainerCanModify=true)
git remote add <作者> https://github.com/<作者>/<仓库>.git
git fetch <作者> <分支>
git push <作者> HEAD:<分支> --force-with-lease
# 合并
git pr merge <N> --squash
# 确认合并SHA以用于分类文档
git pr view <N> --json state,mergeCommit --jq '{state, sha: .mergeCommit.oid[0:7]}'
# 关闭已取代
git pr close <N> --comment "关闭 — 被已合并的 #<M> 取代。谢谢!"
备注
- 千万不要通过
main..HEAD审查过期分支。 这是此技能中最重要的原则。 - 分类文档是会话状态。 丢失文档,丢失会话。每次操作后更新它。
- 即使在部分应用中也对贡献者致谢。 使用
Co-Authored-By:尾注和链接到已应用提交的关闭评论。 - 不要让完美成为发货的敌人。 一个从“损坏”变为“具有已知次要问题的可用”的修复是严格的改进。标记问题,提交跟进,合并修复。