feat/plan-analyze-timing/main #63

Merged
admin merged 4 commits from feat/plan-analyze-timing/main into master 2026-09-18 00:52:27 +00:00
2 changed files with 63 additions and 26 deletions
Showing only changes of commit 5638593c4c - Show all commits
+61
View File
@@ -1007,6 +1007,67 @@ export function stopwatchOnIssue(watches, repo, index) {
);
}
/**
* 「這顆議題上沒有碼錶在跑」的回法不只一種:看過 500,也看過 409。
* 狀態碼隨站台版本而異,所以認的是「狀態碼在這一組裡 **且** 訊息說的是碼錶」——
* 只看訊息會把真的伺服器錯誤一起吞掉,只看狀態碼會把別的衝突也當成沒錶。
*/
const NO_STOPWATCH_STATUS = [409, 500];
/**
* 停錶。停在指定議題上,只停那一顆——端點本身就是議題範圍的,
* 停錶不會波及別顆議題上的錶,那正是領取鎖那條規則要守住的事。
*
* 錶沒在跑不算失敗:停錶多半排在別的事情做完之後(開完 PR、回報完),
* 把「本來就沒在跑」報成失敗,只會讓人以為前面那件事沒做成而重跑一次。
*
* @param {{base: string, token: string}} login
* @param {string} repo owner/name
* @param {number} index
* @returns {Promise<boolean>} 這次真的停了一支錶才是 true
*/
export async function stopStopwatch(login, repo, index) {
const path = `/repos/${repo}/issues/${index}/stopwatch/stop`;
const response = await giteaRequest(login, 'POST', path, { body: {} });
if (response.status >= 200 && response.status < 300) return true;
if (
NO_STOPWATCH_STATUS.includes(response.status) &&
/stopwatch/i.test(response.body?.message ?? '')
) {
return false;
}
expectOk(response, `POST ${path}`);
return false;
}
/**
* 這顆議題上已經記到的工時。
*
* 清單的範圍隨權限而異:不是 repo admin 時 Gitea 只回自己的那幾筆,是 admin 時連別人的
* 一起回。呼叫端(補登)問的是「這顆議題上已經有工時了嗎」,**兩種範圍都答得了那一問**:
* 剛建好的議題上一筆都不該有,有了就代表這個流程跑過一次。範圍寬一點只會讓它偏向不補,
* 而那是安全的方向——工時記重複比記不到更難在報表上被發現。
* @param {{base: string, token: string}} login
* @param {string} repo owner/name
* @param {number} index
* @returns {Promise<object[]>}
*/
export async function listIssueTimes(login, repo, index) {
const path = `/repos/${repo}/issues/${index}/times`;
const response = await giteaRequest(login, 'GET', path);
// 議題本身讀得到卻在這裡 404,代表的是 repo 的時間追蹤關著。訊息與前置檢查第四層
// 對齊:同一件事在試跑與實跑上要說同一句話,否則試跑看到的是一句看不懂的 404
if (response.status === 404) {
throw new ScriptError(
'TIME_TRACKER_OFF',
'repo 尚未開啟時間追蹤,工時記不進去;請到 Settings → Advanced Settings → Enable Time Tracker 開啟',
);
}
return expectOk(response, `GET ${path}`) ?? [];
}
// ── 標籤 ───────────────────────────────────────────────────────────
/**
+2 -26
View File
@@ -36,6 +36,7 @@ import {
preflight,
readTextFile,
resolveLogin,
stopStopwatch,
} from './lib.js';
/** 描述的固定段落,順序即 reviewer 閱讀的順序 */
@@ -126,7 +127,7 @@ main(async () => {
);
// 錶只在 PR 確實存在之後才停。既有的 PR 也要停——那一步可能正是上次中斷的地方。
const stopped = await stopStopwatch(login, stopPath);
const stopped = await stopStopwatch(login, issueRepo, index);
return {
repo,
@@ -211,28 +212,3 @@ function checkTestResult(body) {
);
}
}
/**
* 「這顆議題上沒有碼錶在跑」的回法不只一種:看過 500,也看過 409。
* 狀態碼隨站台版本而異,所以認的是「狀態碼在這一組裡 **且** 訊息說的是碼錶」——
* 只看訊息會把真的伺服器錯誤一起吞掉,只看狀態碼會把別的衝突也當成沒錶。
*/
const NO_STOPWATCH_STATUS = [409, 500];
/**
* 停錶。錶沒在跑時不算失敗——PR 已經開出去了,把整件事報成失敗只會讓人以為
* PR 沒開成而重跑一次。
*/
async function stopStopwatch(login, path) {
const response = await giteaRequest(login, 'POST', path, { body: {} });
if (response.status >= 200 && response.status < 300) return true;
if (
NO_STOPWATCH_STATUS.includes(response.status) &&
/stopwatch/i.test(response.body?.message ?? '')
) {
return false;
}
expectOk(response, `POST ${path}`);
return false;
}