From 4570c9885bd473502b582c52b1adcb5404e5e683 Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Sun, 12 Apr 2026 12:59:06 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20AI=20=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=AE=A1=E6=9F=A5=E5=B7=A5=E4=BD=9C=E6=B5=81=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E5=9F=BA=E5=87=86=E5=88=86=E6=94=AF=E6=A3=80=E5=87=BA?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E7=A1=AE=E4=BF=9D=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=88=86=E6=94=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/scripts/ai-pr-review.mjs | 85 ++++++++++++++++++++++++++---- .github/workflows/ai-pr-review.yml | 4 +- 2 files changed, 78 insertions(+), 11 deletions(-) diff --git a/.github/scripts/ai-pr-review.mjs b/.github/scripts/ai-pr-review.mjs index 426a303..448a23d 100644 --- a/.github/scripts/ai-pr-review.mjs +++ b/.github/scripts/ai-pr-review.mjs @@ -10,7 +10,7 @@ const DEFAULT_TARGET_BRANCH = 'dev'; const DEFAULT_MAX_FILES = 40; const DEFAULT_MAX_PATCH_CHARS_PER_FILE = 12000; const DEFAULT_MAX_PATCH_CHARS_TOTAL = 120000; -const DEFAULT_TRUSTED_ASSOCIATIONS = ['COLLABORATOR', 'CONTRIBUTOR', 'MEMBER', 'OWNER']; +const DEFAULT_TRUSTED_ASSOCIATIONS = []; class ReviewBlockedError extends Error { constructor(message) { @@ -36,7 +36,21 @@ async function main() { if (currentBaseRef !== targetBranch) { if (currentBaseRef === 'master') { - await retargetPullRequest(repo, prNumber, targetBranch); + const retargetResult = await retargetPullRequest(repo, pr, targetBranch); + if (retargetResult.status === 'duplicate') { + await upsertManagedComment( + repo, + prNumber, + renderDuplicateTargetPrComment({ + targetBranch, + duplicatePrNumber: retargetResult.pull.number + }) + ); + await appendSummary( + `PR #${prNumber} 没有自动转到 ${targetBranch},因为已存在同源分支的 PR #${retargetResult.pull.number} 指向 ${targetBranch}。` + ); + return; + } await upsertManagedComment( repo, prNumber, @@ -336,13 +350,51 @@ async function ensureBranchExists(repo, branchName) { await githubRequest(`/repos/${repo}/branches/${encodeURIComponent(branchName)}`); } -async function retargetPullRequest(repo, prNumber, targetBranch) { - await githubRequest(`/repos/${repo}/pulls/${prNumber}`, { - method: 'PATCH', - body: JSON.stringify({ - base: targetBranch - }) - }); +async function listOpenPullRequestsByHeadAndBase(repo, headOwner, headRef, baseRef) { + const query = [ + 'state=open', + `head=${encodeURIComponent(`${headOwner}:${headRef}`)}`, + `base=${encodeURIComponent(baseRef)}`, + 'per_page=100' + ].join('&'); + return githubRequestJson(`/repos/${repo}/pulls?${query}`); +} + +async function retargetPullRequest(repo, pr, targetBranch) { + try { + await githubRequest(`/repos/${repo}/pulls/${pr.number}`, { + method: 'PATCH', + body: JSON.stringify({ + base: targetBranch + }) + }); + return { status: 'retargeted' }; + } catch (error) { + const message = String(error?.message || ''); + if (!message.includes('(422)') || !message.includes("A pull request already exists for base branch")) { + throw error; + } + + const headOwner = String(pr.head?.repo?.owner?.login || '').trim(); + const headRef = String(pr.head?.ref || '').trim(); + if (!headOwner || !headRef) { + throw error; + } + + const pulls = await listOpenPullRequestsByHeadAndBase(repo, headOwner, headRef, targetBranch); + const duplicatePull = Array.isArray(pulls) + ? pulls.find((pull) => Number(pull.number) !== Number(pr.number)) + : null; + + if (!duplicatePull) { + throw error; + } + + return { + status: 'duplicate', + pull: duplicatePull + }; + } } function buildReviewInput({ repo, pr, files, maxFiles, maxPatchCharsPerFile, maxPatchCharsTotal }) { @@ -643,6 +695,21 @@ function renderRetargetedComment({ fromBranch, targetBranch }) { return `${lines.join('\n').trim()}\n`; } +function renderDuplicateTargetPrComment({ targetBranch, duplicatePrNumber }) { + const lines = [ + MARKER, + '## 已存在对应的开发分支 PR', + '', + `系统原本想把这个 PR 自动转到 \`${targetBranch}\`,但同一个来源分支已经有一个指向 \`${targetBranch}\` 的 PR:#${duplicatePrNumber}。`, + '', + `为了避免重复 PR 混淆,本次没有继续自动转向,也没有执行自动合并。`, + '', + `请优先处理已有的 PR #${duplicatePrNumber},或者手动关闭其中一个重复 PR。` + ]; + + return `${lines.join('\n').trim()}\n`; +} + async function upsertManagedComment(repo, prNumber, body) { const comments = await listIssueComments(repo, prNumber); const existing = comments.find((comment) => typeof comment.body === 'string' && comment.body.includes(MARKER)); diff --git a/.github/workflows/ai-pr-review.yml b/.github/workflows/ai-pr-review.yml index e20338c..2c40f5d 100644 --- a/.github/workflows/ai-pr-review.yml +++ b/.github/workflows/ai-pr-review.yml @@ -26,10 +26,10 @@ jobs: issues: write pull-requests: write steps: - - name: 检出当前基准分支上的工作流文件 + - name: 检出默认分支上的自动化脚本 uses: actions/checkout@v4 with: - ref: ${{ github.event.pull_request.base.ref }} + ref: ${{ github.event.repository.default_branch }} fetch-depth: 1 persist-credentials: false