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:
+52
-2
@@ -28,7 +28,7 @@ const BRANCH = `feat/${SLUG}/main`;
|
||||
* 一顆 PR 的現場:狀態由 options 決定,留言預設沒有。
|
||||
* 一般留言只有在沒有自己打的 `+1` 時才算未處理。
|
||||
*/
|
||||
function routes({ state = 'open', merged = false, draft = false, general = [] } = {}) {
|
||||
function routes({ state = 'open', merged = false, draft = false, general = [], head } = {}) {
|
||||
const base = healthyRoutes(REPO, {
|
||||
[`GET /api/v1/repos/${REPO}/pulls/${INDEX}`]: {
|
||||
status: 200,
|
||||
@@ -39,7 +39,9 @@ function routes({ state = 'open', merged = false, draft = false, general = [] }
|
||||
state,
|
||||
merged,
|
||||
draft,
|
||||
head: { ref: BRANCH },
|
||||
// 預設是「PR 還開著」的樣子:head.ref 就是分支名。合併之後 Gitea 會換一種樣子,
|
||||
// 那一種由測試自己指定。
|
||||
head: head ?? { ref: BRANCH, label: BRANCH },
|
||||
},
|
||||
},
|
||||
[`GET /api/v1/repos/${REPO}/issues/${INDEX}/comments`]: {
|
||||
@@ -250,6 +252,54 @@ test('清不掉的路徑,--dry-run 不會預告一行實跑會拒絕的指令'
|
||||
assert.equal(json.data.工作樹.是工作樹, false);
|
||||
});
|
||||
|
||||
// ── 合併之後的 head ───────────────────────────────────────────────
|
||||
|
||||
test('來源分支在合併時被刪掉,仍要推導出正確的工作樹並清掉它', async (t) => {
|
||||
// Gitea 在這種情況下把 head.ref 換成 refs/pull/{編號}/head,分支名退到 head.label。
|
||||
// 拿 ref 去推導會算出一條不存在的路徑,然後靜靜回報「沒事要做」——而合併正是唯一
|
||||
// 該動手清理的時機,等於自動清理在真實情況下從來不會成立。
|
||||
const { run, worktree } = await withScene(t, {
|
||||
state: 'closed',
|
||||
merged: true,
|
||||
head: { ref: `refs/pull/${INDEX}/head`, label: BRANCH },
|
||||
});
|
||||
|
||||
const { code, json } = await run();
|
||||
|
||||
assert.equal(code, 0, json.error?.message);
|
||||
assert.equal(json.data.branch, BRANCH, '分支名要取自 head.label');
|
||||
assert.equal(json.data.工作樹.路徑, worktree);
|
||||
assert.equal(json.data.cleaned, true);
|
||||
assert.equal(existsSync(worktree), false);
|
||||
});
|
||||
|
||||
test('fork 來的 PR:head.label 是 owner:branch,只取分支那一段', async (t) => {
|
||||
const { run, worktree } = await withScene(t, {
|
||||
state: 'closed',
|
||||
merged: true,
|
||||
head: { ref: `refs/pull/${INDEX}/head`, label: `someone:${BRANCH}` },
|
||||
});
|
||||
|
||||
const { json } = await run();
|
||||
|
||||
assert.equal(json.data.branch, BRANCH);
|
||||
assert.equal(existsSync(worktree), false, '推導出來的仍是同一條路徑');
|
||||
});
|
||||
|
||||
test('分支名兩邊都取不到時明確中止,不拿空字串去推導路徑', async (t) => {
|
||||
const { run } = await withScene(t, {
|
||||
state: 'closed',
|
||||
merged: true,
|
||||
head: { ref: `refs/pull/${INDEX}/head` },
|
||||
});
|
||||
|
||||
const { code, json } = await run();
|
||||
|
||||
assert.equal(code, 1);
|
||||
assert.equal(json.error.code, 'PULL_HEAD_MISSING');
|
||||
assert.match(json.error.message, /worktree-remove/, '要指出手動出口怎麼指名那一棵');
|
||||
});
|
||||
|
||||
// ── 回報內容 ───────────────────────────────────────────────────────
|
||||
|
||||
test('回報內容含 PR 狀態、未處理留言數與工作樹現況', async (t) => {
|
||||
|
||||
Reference in New Issue
Block a user