fix/pr-head-after-merge/main #51
+25
-5
@@ -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,合併與草稿各是另一個布林值——三個欄位湊成一種
|
||||
|
||||
+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