fix(pr-watch): 來源分支被刪掉之後,改從 head.label 取分支名

PR 合併而來源分支被刪掉之後,Gitea 把 head.ref 換成 refs/pull/{編號}/head,分支名
退到 head.label。pr-watch 用的是 head.ref,於是推導出一條根本不存在的工作樹路徑,
回報「工作樹不在、沒事要做」——而合併正是唯一該清理工作樹的時機,等於自動清理在真實
情況下從來不會成立,而且靜悄悄的,沒有人會發現。

實測 #48(合併後)推導出 bb4f5127f641,真正的那一棵在 bd457620184e。

fork 來的 PR 的 label 是 owner:branch,只取分支那一段。兩邊都取不到時明確中止
(PULL_HEAD_MISSING),並指出手動出口怎麼指名那一棵,不拿空字串去推導路徑。

測試先前沒抓到,是因為假的 PR 一律照「開著的 PR」寫,head.ref 永遠是分支名。
補上「已合併且來源分支已刪」與 fork 兩種 head 的樣子。

議題 #50

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-17 17:16:44 +08:00
co-authored by Claude Opus 5
parent d0e7e569a3
commit 695694b3fd
2 changed files with 77 additions and 7 deletions
+25 -5
View File
@@ -64,13 +64,13 @@ main(async () => {
const state = stateOf(pull);
const 未處理留言數 = unhandledCount(await readPullComments(login, repo, index, me));
// 工作樹由 PR 自己的 head 分支推導,不必另外給——同一顆工作包算出來的永遠是同一條路徑
const branch = pull.head?.ref;
if (!branch) {
// 工作樹由 PR 自己的來源分支推導,不必另外給——同一顆工作包算出來的永遠是同一條路徑
const branch = headBranch(pull);
if (branch === '') {
throw new ScriptError(
'PULL_HEAD_MISSING',
`PR #${index} 讀不到 head 分支(來源分支可能已經被刪掉),推導不出工作樹在哪;` +
'請改用 worktree-remove --branch 指名要清哪一棵',
`PR #${index} 讀不到來源分支名,推導不出工作樹在哪;` +
'請改用 worktree-remove --repo <owner/name> --branch <分支名> 指名要清哪一棵',
);
}
const worktree = worktreePath(repo, branch);
@@ -119,6 +119,26 @@ main(async () => {
});
/**
* PR 的來源分支名。
*
* **不能只看 `head.ref`。** PR 合併而來源分支被刪掉之後,Gitea 會把 `head.ref` 換成
* `refs/pull/{編號}/head`,分支名退到 `head.label`——而合併正是唯一該清理工作樹的時機。
* 拿那個 ref 去推導會算出一條根本不存在的路徑,然後回報「工作樹不在、沒事要做」:
* 自動清理於是在真實情況下從來不會成立,而且靜悄悄的,沒有人會發現。
*
* fork 來的 PR 的 label 是 `owner:branch`,只取分支那一段——工作樹是以分支名推導的。
*/
function headBranch(pull) {
const label = (pull.head?.label ?? '').trim();
const 分支 = label.includes(':') ? label.slice(label.indexOf(':') + 1) : label;
if (分支 !== '') return 分支;
// label 缺席時才退回 ref,而且 refs/pull/… 不是分支名,寧可說讀不到
const ref = (pull.head?.ref ?? '').trim();
return /^refs\/pull\//.test(ref) ? '' : ref;
}
/**
* PR 的四種狀態。
* Gitea 的 `state` 只有 open/closed,合併與草稿各是另一個布林值——三個欄位湊成一種