fix(pr-watch): 試跑的預告與實跑的守門對齊,並說出路徑被佔住這件事
review 抓到一處落差:試跑判「終止且工作樹乾淨」就預告 git worktree remove, 但實跑多判一件事——那條路徑上的東西是不是真的一棵工作樹。別的 clone 在同一條路徑上 留下目錄時(路徑由 owner/repo/分支名 推導,不含本機 clone 的位置),試跑會預告一行 實跑必然拒絕的指令,而回報裡完全看不出原因。 判斷收進 lib 的 inspectWorktree,由它直接給出 reason(missing/foreign/dirty/ removable):試跑與實跑、自動與手動四條路徑從此擋在同一個判斷上,worktree-remove 裡 那份重算的副本也跟著刪掉。回報多一個「是工作樹」欄位,手動出口的 NOT_A_WORKTREE 不再是使用者第一次聽到這件事。 順帶兩件同源的修正: - 「是不是工作樹」改認 .git 為**檔案**。獨立 clone 的 .git 是目錄,先前會被當成工作樹, 然後在 git worktree remove 那一步炸出一句原始錯誤。 - 兩支腳本的 --dry-run 請求預告收進 pr-threads 的 plannedRequests,並補上 pr-watch 先前漏掉的那句說明(逐則 reaction 與逐個 review 的行內留言事前列不完)。預告與實際 發出的請求分開寫,加一個端點就會有一邊忘了改。 - PR 讀不到 head 分支時給出 PULL_HEAD_MISSING,而不是拿空字串去推導一條路徑。 議題 #41 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+36
-21
@@ -24,6 +24,7 @@
|
||||
* node scripts/pr-watch.js --repo owner/name --index 46 [--host <網址>] [--dry-run]
|
||||
*/
|
||||
import {
|
||||
ScriptError,
|
||||
expectOk,
|
||||
giteaRequest,
|
||||
inspectWorktree,
|
||||
@@ -36,7 +37,13 @@ import {
|
||||
resolveLogin,
|
||||
worktreePath,
|
||||
} from './lib.js';
|
||||
import { fetchPull, readPullComments, unhandledCount } from './pr-threads.js';
|
||||
import {
|
||||
COMMENT_REQUEST_NOTE,
|
||||
fetchPull,
|
||||
plannedRequests,
|
||||
readPullComments,
|
||||
unhandledCount,
|
||||
} from './pr-threads.js';
|
||||
|
||||
main(async () => {
|
||||
const flags = parseFlags(process.argv.slice(2), {
|
||||
@@ -58,7 +65,14 @@ main(async () => {
|
||||
const 未處理留言數 = unhandledCount(await readPullComments(login, repo, index, me));
|
||||
|
||||
// 工作樹由 PR 自己的 head 分支推導,不必另外給——同一顆工作包算出來的永遠是同一條路徑
|
||||
const branch = pull.head?.ref ?? '';
|
||||
const branch = pull.head?.ref;
|
||||
if (!branch) {
|
||||
throw new ScriptError(
|
||||
'PULL_HEAD_MISSING',
|
||||
`PR #${index} 讀不到 head 分支(來源分支可能已經被刪掉),推導不出工作樹在哪;` +
|
||||
'請改用 worktree-remove --branch 指名要清哪一棵',
|
||||
);
|
||||
}
|
||||
const worktree = worktreePath(repo, branch);
|
||||
const terminal = state === 'merged' || state === 'closed';
|
||||
|
||||
@@ -66,6 +80,8 @@ main(async () => {
|
||||
const 清理 = terminal && !dryRun ? removeWorktree(worktree) : null;
|
||||
const 工作樹 = 清理 ?? inspectWorktree(worktree);
|
||||
const cleaned = 清理?.removed === true;
|
||||
// 試跑要預告的那一行,條件與實跑完全同一個:reason 由 lib 算,兩邊不各判一次
|
||||
const 清得掉 = 工作樹.reason === 'removable';
|
||||
|
||||
const 報告 = {
|
||||
repo,
|
||||
@@ -77,14 +93,17 @@ main(async () => {
|
||||
未處理留言數,
|
||||
工作樹: {
|
||||
路徑: worktree,
|
||||
// 清掉之後這兩個欄位講的是清理之前的狀況:cleaned 已經說了現在還在不在
|
||||
// 清掉之後這幾個欄位講的是清理之前的狀況:cleaned 已經說了現在還在不在
|
||||
存在: cleaned ? false : 工作樹.exists,
|
||||
// 路徑上有東西卻不是工作樹(多半是別的 clone 留下的)時,清理不會發生也不該
|
||||
// 靜靜跳過——手動出口會給出 NOT_A_WORKTREE,這個欄位是它的前情提要
|
||||
是工作樹: 工作樹.isWorktree,
|
||||
有未提交變更: 工作樹.dirty,
|
||||
檔案: 工作樹.files,
|
||||
},
|
||||
terminal,
|
||||
cleaned,
|
||||
suggestedAction: suggest({ terminal, cleaned, dryRun, 未處理留言數, 工作樹 }),
|
||||
suggestedAction: suggest({ terminal, cleaned, 清得掉, 未處理留言數, 工作樹 }),
|
||||
};
|
||||
|
||||
if (!dryRun) return 報告;
|
||||
@@ -92,15 +111,10 @@ main(async () => {
|
||||
return {
|
||||
dryRun: true,
|
||||
...報告,
|
||||
requests: [
|
||||
{ method: 'GET', path: '/user' },
|
||||
{ method: 'GET', path: `/repos/${repo}/pulls/${index}` },
|
||||
{ method: 'GET', path: `/repos/${repo}/issues/${index}/comments` },
|
||||
{ method: 'GET', path: `/repos/${repo}/pulls/${index}/reviews` },
|
||||
{ method: 'GET', path: `/repos/${repo}/issues/${index}/timeline` },
|
||||
],
|
||||
requests: plannedRequests(repo, index),
|
||||
note: COMMENT_REQUEST_NOTE,
|
||||
// 讀取是冪等的,試跑照樣發;會改變東西的只有這一行,所以只有它被留到這裡
|
||||
commands: terminal && 工作樹.exists && !工作樹.dirty ? [`git worktree remove ${worktree}`] : [],
|
||||
commands: terminal && 清得掉 ? [`git worktree remove ${worktree}`] : [],
|
||||
};
|
||||
});
|
||||
|
||||
@@ -119,14 +133,15 @@ function stateOf(pull) {
|
||||
/**
|
||||
* 下一步該做什麼,固定四個值。
|
||||
*
|
||||
* 順序有意義:有東西擋住清理時要先講那件事,因為它需要人動手;未處理的留言其次;
|
||||
* 都沒有就是真的沒事。試跑時「該清而還沒清」講的是 cleanup——它是這次不做的那件事。
|
||||
* 終止的 PR 只問清理這件事:有沒提交的東西卡著就 blocked-dirty(要人自己處理),
|
||||
* 清掉了或本來就不在就沒事了,其餘都還有一棵樹等著清——試跑不動手,路徑上是別的
|
||||
* clone 留下的東西也一樣,兩種都落在 cleanup,由手動出口給出確切的原因。
|
||||
*
|
||||
* 還沒終止的 PR 只問留言:有沒處理完的就建議去跑 /sdlc-fix,但只是建議。
|
||||
*/
|
||||
function suggest({ terminal, cleaned, dryRun, 未處理留言數, 工作樹 }) {
|
||||
if (terminal) {
|
||||
if (工作樹.exists && 工作樹.dirty) return 'blocked-dirty';
|
||||
if (dryRun && 工作樹.exists && 工作樹.worktree) return 'cleanup';
|
||||
return cleaned || !工作樹.exists ? 'nothing-to-do' : 'cleanup';
|
||||
}
|
||||
return 未處理留言數 > 0 ? 'run-sdlc-fix' : 'nothing-to-do';
|
||||
function suggest({ terminal, cleaned, 清得掉, 未處理留言數, 工作樹 }) {
|
||||
if (!terminal) return 未處理留言數 > 0 ? 'run-sdlc-fix' : 'nothing-to-do';
|
||||
if (工作樹.reason === 'dirty') return 'blocked-dirty';
|
||||
if (cleaned || 工作樹.reason === 'missing') return 'nothing-to-do';
|
||||
return 清得掉 || 工作樹.reason === 'foreign' ? 'cleanup' : 'nothing-to-do';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user