feat(ai-code-review): 建問題模式導向 issue,並整併流程調整、文件與 CI #6
+49
-17
@@ -244,10 +244,10 @@ function fileLastUpdatedIso(cwd, file) {
|
||||
* findings 檔與 `.gitea/ai-review/exclusions.json` 等結果檔提交回 PR 來源分支,
|
||||
* 並依回傳值記錄「已 commit/push」或「無實際變更、略過」的不同日誌。
|
||||
*
|
||||
* 安全注意:push 重試時組出的 URL 內含 token(形如
|
||||
* `https://ai-review-bot:<token>@host/owner/repo.git`),
|
||||
* 絕對不得將此 URL 輸出到日誌、錯誤訊息或任何 action 輸出,以免洩漏 token;
|
||||
* 若需記錄重試行為,只能記載「改用帶認證 URL 重試」而不得包含 URL 本身。
|
||||
* 安全注意:帶認證的推送一律透過 {@link pushWithCredential} 進行——認證只以
|
||||
* 環境變數(`http.<url>.extraheader` 的 base64 Basic)傳入,**不進 argv**,
|
||||
* 推送目標 URL 本身不含帳密;且 push 失敗時改拋固定訊息,避免 `execFileSync`
|
||||
* 例外把命令列(含 token)回顯到 CI log 或程序清單。
|
||||
*/
|
||||
function commitAndPushFindings(cwd, { headRef, headSha, message, files, token, pushToken, serverUrl, repository }) {
|
||||
const current = gitTrim(cwd, 'rev-parse', 'HEAD');
|
||||
@@ -267,28 +267,60 @@ function commitAndPushFindings(cwd, { headRef, headSha, message, files, token, p
|
||||
'-c', 'user.email=ai-review-bot@noreply.gitea',
|
||||
'commit', '-m', message,
|
||||
);
|
||||
const refspec = `HEAD:refs/heads/${headRef}`;
|
||||
const remoteUrl = `${serverUrl}/${repository}.git`;
|
||||
if (pushToken) {
|
||||
// 有專用 PAT → 直接以帶 token 的 URL 推送(略過 origin,因 origin 帶的是不會再觸發 CI 的自動 token);
|
||||
// 以 PAT 身分推送才會讓 PR 的 synchronize 事件再觸發 CI。不得把含 token 的 URL 輸出到日誌。
|
||||
const url = new URL(`${serverUrl}/${repository}.git`);
|
||||
url.username = 'ai-review-bot';
|
||||
url.password = pushToken;
|
||||
git(cwd, 'push', url.toString(), `HEAD:refs/heads/${headRef}`);
|
||||
// 有專用 PAT → 以 PAT 推送(略過 origin,因 origin 帶的是不會再觸發 CI 的自動 token);
|
||||
// 以 PAT 身分推送才會讓 PR 的 synchronize 事件再觸發 CI。
|
||||
pushWithCredential(cwd, remoteUrl, pushToken, refspec);
|
||||
} else {
|
||||
try {
|
||||
git(cwd, 'push', 'origin', `HEAD:refs/heads/${headRef}`);
|
||||
git(cwd, 'push', 'origin', refspec);
|
||||
} catch {
|
||||
// 遠端未帶認證(checkout 未保留 credentials)時,改用帶 token 的 URL 重試。
|
||||
// 注意:不得把這個 URL 輸出到日誌,避免洩漏 token。
|
||||
const url = new URL(`${serverUrl}/${repository}.git`);
|
||||
url.username = 'ai-review-bot';
|
||||
url.password = token;
|
||||
git(cwd, 'push', url.toString(), `HEAD:refs/heads/${headRef}`);
|
||||
// 遠端未帶認證(checkout 未保留 credentials)時,改用帶認證的推送重試。
|
||||
pushWithCredential(cwd, remoteUrl, token, refspec);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 以帶認證的方式推送到指定遠端,認證資訊只經環境變數傳入、不進命令列 argv。
|
||||
*
|
||||
* 認證方式:等同 `https://ai-review-bot:<secret>@host/...` 的 HTTP Basic(git 會把
|
||||
* URL 帳密轉成相同的 `Authorization: Basic` 標頭送出),但改以 git 的
|
||||
* `GIT_CONFIG_*` 環境變數注入 `http.<url>.extraheader`,使 base64 憑證**不出現在 argv**
|
||||
* (避免程序清單/例外回顯洩漏);推送目標 URL 亦不含帳密。
|
||||
* 推送失敗時**不重拋原始例外**(其 message 會含命令列與遠端 URL),改拋固定訊息。
|
||||
*
|
||||
* @param {string} cwd - git 工作目錄(repo 的 checkout 路徑)。
|
||||
* @param {string} remoteUrl - 不含帳密的遠端 URL(形如 `https://host/owner/repo.git`)。
|
||||
* @param {string} secret - 具 push 權限的 token/PAT(作為 Basic 認證的密碼)。
|
||||
* @param {string} refspec - push 的 refspec(形如 `HEAD:refs/heads/<branch>`)。
|
||||
* @returns {void} 成功即返回;失敗拋出不含 URL/argv/token 的固定錯誤。
|
||||
* @throws {Error} 推送失敗時拋出固定訊息(已隱藏遠端 URL 與認證資訊)。
|
||||
* @remarks 本函式未匯出,僅供 {@link commitAndPushFindings} 使用。
|
||||
*/
|
||||
function pushWithCredential(cwd, remoteUrl, secret, refspec) {
|
||||
const basic = Buffer.from(`ai-review-bot:${secret}`).toString('base64');
|
||||
try {
|
||||
execFileSync('git', ['push', remoteUrl, refspec], {
|
||||
cwd,
|
||||
encoding: 'utf8',
|
||||
maxBuffer: 64 * 1024 * 1024,
|
||||
env: {
|
||||
...process.env,
|
||||
GIT_TERMINAL_PROMPT: '0',
|
||||
GIT_CONFIG_COUNT: '1',
|
||||
GIT_CONFIG_KEY_0: `http.${remoteUrl}.extraheader`,
|
||||
GIT_CONFIG_VALUE_0: `Authorization: Basic ${basic}`,
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
throw new Error('推送審查結果 commit 失敗(已隱藏遠端 URL 與認證資訊)。');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
latestCommitSubject,
|
||||
resolveMergeBase,
|
||||
|
||||
Reference in New Issue
Block a user