分類 PRs
目標
將大量開啟的 PR 轉化為在一次專注會話中交付的合併。產出一份可追蹤、可恢復的計畫 (<VERSION>_PR_TRIAGE.md),然後依序執行 — 在需要時進行變基、以隔離安全的批次合併、應用合併後的後續修正,並在關閉已取代或部分適用的 PR 時給予作者 credit。
此技能與 draft-release-notes 和 release-bump 搭配使用:先進行分類,然後針對新的 main 分支起草發行說明,最後進行版本發行。
使用時機
- 在次要或主要版本發布前,當累積了 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
保留主工作樹用於發布準備工作 (變更日誌草稿、直接寫入 main 的後續修正)。保留審查工作樹用於 gh pr checkout — 每次 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 之間的重疊)
在向 main 推送之後,UNKNOWN 很常見 — 直接嘗試合併即可。
3. 分類為不同等級
將每個 PR 歸入恰好一個類別:
第 1 級 — 合併: 小型、可合併、修復實際錯誤、CI 乾淨、審查成本低。一行修正、依賴放寬、有針對性的安全性強化。這些是容易得手的成果。
第 2 級 — 候選,需要審查: 中型 (50-200 行),觸及更多表面,看起來合理但需要仔細閱讀。符合產品方向的新使用者面向功能。
已取代: 該修正或功能已被其他已合併的內容涵蓋。關閉時加上註解,指向取代的 PR。仔細檢查 — 「相似的標題」不能作為證據;比較實際的 diff。
延後到下個版本: 大型功能、骯髒的衝突、草稿 PR、任何以會引入風險的方式觸及發布管線的內容。不要在衝刺中合併這些 — 它們需要專門的關注。
4. 撰寫分類文件
在 PR 審查工作樹的根目錄中建立 <VERSION>_PR_TRIAGE.md。結構如下:
# <儲存庫> <VERSION> — PR 分類
用於追蹤哪些開啟的 PR 會進入 <VERSION> 的工作文件。在發布完成後刪除。
最後更新:<DATE>
## 進度
**第 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 checkout -b pr-review-<VERSION>
gh pr checkout <N>
b. 閱讀 實際的 commit,而非 main..HEAD:
git show HEAD # PR 的實際變更
git show --stat HEAD # 觸及的檔案 + 行數
如果 PR 分支比 main 舊,不要透過 git diff main..HEAD 進行審查。那個 diff 會將 PR 分支建立後合併到 main 上的每一個 commit 都顯示為 - (刪除) 行。一個 3 行的 PR 可能會看起來像是一個 700 行的還原提交。這是錯誤判斷 PR 最容易的方式。
c. 評估關注點: 正確性、範圍、與已合併工作的互動、版本相容性 (例如不能使用需要我們尚未鎖定版本的依賴項的 API)。
d. 如果分支落後於 main,則進行變基:
git fetch origin main
git rebase origin/main
在 squash 合併之前,這是至關重要的。GitHub 的 squash 計算的是 diff(PR-head, merge-base) — 在一個過時的分支上,這個 diff 包含了還原每個中間 commit 的動作。變基會將合併基礎向前移動,使 squash 變得乾淨。
e. 如果允許維護者編輯,則將變基後的內容推回貢獻者的分支:
git remote add <author> https://github.com/<author>/<repo>.git
git fetch <author> <branch> # 先取得他們的 ref
git push <author> HEAD:<branch> --force-with-lease
這可以保持 GitHub 的 PR 介面與變基後的狀態同步,並使 GitHub 端的合併變得乾淨。
f. 合併:
gh 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. 合併後的後續修正
有時某個 PR 值得合併,儘管存在已知的小問題 (例如不完整的 dtype 映射、過時的哨兵清理)。不要阻礙合併;在合併後立即以一個普通分支加 PR 的方式套用後續修正:
cd <main-worktree>
git pull --ff-only origin main
git checkout -b fix/<short-name>
# 編輯...
git commit -m "fix(<area>): <one-liner>"
git push -u origin fix/<short-name>
gh pr create --title "..." --body "接續 #<N>。..."
在分類文件中記錄兩個 SHA (✅ merged <pr-sha> + follow-up <pr>)。
直接寫入 main 的例外: 僅在明確、限定的政策下 (例如「發布衝刺」)。不要將其設為預設值。
8. 已取代:關閉並附上給予 credit 的註解
gh pr close <N> --comment "關閉 — 已被已合併的 #<M> 取代,該 PR 已合併 <簡短描述>。謝謝!"
先檢查 diff — 「相似的標題」是不夠的。如果 PR 是部分被取代 (診斷是正確的,但只有一半的變更仍然需要),則改用部分套用模式。
9. 部分套用模式
當一個 PR 同時包含了有價值和有問題的變更時:
cd <main-worktree>
git pull --ff-only origin main
# 從 PR 分支精選特定檔案
git checkout <pr-commit-sha> -- <file1> <file2>
# 審查暫存的變更,根據需要進行調整
git diff --cached
# 對您不想批量替換的檔案進行外科手術式編輯
# (例如 PR 的檔案早於您需要保留的最近 main commit)
# 使用帶有原始作者 credit 的 trailer 提交
git commit -m "$(cat <<'EOF'
<主旨>
<說明保留了什麼和捨棄了什麼的主體>
Co-Authored-By: <author> <[email protected]>
EOF
)"
git push ... # 分支加 PR,除非在直接寫入 main 的例外情況
然後關閉 PR,並附上說明套用了什麼和捨棄了什麼的註解,並引用 commit SHA。
10. 保持文件更新
每次合併、每次關閉、每次後續修正 → 都更新 <VERSION>_PR_TRIAGE.md。這份文件是您的會話記錄。如果您被打斷並在明天恢復,這份文件是「我進行到哪裡了」的唯一真相來源。
11. 當分類完成時
- 文件中的每個 PR 都有一個最終狀態 (✅ merged / ✅ closed / deferred)
- 進度標頭顯示每個等級的 N/N
- 接下來要執行的技能是
draft-release-notes(根據新的 main 重新產生[Unreleased]),然後是release-bump
您可以在發布之後刪除分類文件,或者將其保留在版本歷史中作為記錄。
潛在陷阱
- 過時分支上的
main..HEAD會誤導人。 它會將分支分岔後 main 獲得的所有內容顯示為刪除。務必透過git show HEAD審查 PR 的實際 commit。 - 對未經變基的分支進行 Squash 合併會還原中間的 work。 Squash 計算的是
diff(PR-head, merge-base)。變基會將合併基礎向前移動。 mergeable=UNKNOWN是暫時的 — GitHub 正在重新計算。直接嘗試合併即可。- 路由順序很重要 (FastAPI 和類似框架):
DELETE /history/failed必須在 之前 註冊DELETE /history/{id},否則參數化的路徑會將"failed"作為 ID 消耗掉。 - Apple 的
-weak_framework會覆蓋-framework對於同一個 framework,無論順序如何 — 當某個依賴項硬連結了某些可選項目時,請透過cargo:rustc-link-arg=-Wl,-weak_framework,Name使用它。 - 依賴項版本下限限制了您可以套用的內容。 在接受像
torch_dtype=→dtype=這樣的關鍵字參數重新命名之前,請檢查最小版本 pin 是否支援它。有時正確的做法是挑選 PR 的一半。 cpal::Stream和類似的!Send音訊類型 不能跨越await點或spawn_blocking。有時一個「不理想但正確」的同步等待是可用的最佳修正;標記但不要阻止。- PyTorch nightly 版本不能用於發行版 — 非確定性,可能在運行之間回歸。如果某個 PR 建議切換到 nightly 以修復 GPU 問題,請優先使用
TORCH_CUDA_ARCH_LIST=...+PTX或等待穩定版本支援。
規範命令參考
# 批次 PR 中繼資料
gh pr list --state open --limit 50 --json number,title,author,mergeable,mergeStateStatus,additions,deletions,maintainerCanModify,files
# 詳細的單一 PR 檢視
gh pr view <N> --json body,author,headRefName,baseRefName,mergeable,maintainerCanModify,files,statusCheckRollup
# 實際的 commit,而非分支與 main 的 diff
git show HEAD
git show --stat HEAD
gh pr diff <N>
# 將貢獻者分支變基到當前的 main
git fetch origin main && git rebase origin/main
# 將變基後的內容推回貢獻者的 fork (需要 maintainerCanModify=true)
git remote add <author> https://github.com/<author>/<repo>.git
git fetch <author> <branch>
git push <author> HEAD:<branch> --force-with-lease
# 合併
gh pr merge <N> --squash
# 確認合併 SHA 以供分類文件使用
gh pr view <N> --json state,mergeCommit --jq '{state, sha: .mergeCommit.oid[0:7]}'
# 關閉已取代的
gh pr close <N> --comment "關閉 — 已被已合併的 #<M> 取代。謝謝!"
備註
- 永遠不要透過
main..HEAD審查過時的分支。 這是此技能中最重要的一行。 - 分類文件是會話狀態。 遺失文件,就遺失了會話。每次操作後都更新它。
- 即使在部分套用中也要給予貢獻者 credit。 使用
Co-Authored-By:trailer 和連結到已套用 commit 的關閉註解。 - 不要讓完美成為交付的敵人。 一個從「損壞」變成「可以運作但有個小已知問題」的修正就是一個嚴格的改進。標記該問題,建立一個後續,然後合併修正。