@@ -37,15 +37,22 @@ jobs:
3737 run : |
3838 # 根据触发类型设置环境变量
3939 if [ "${{ github.event_name }}" = "pull_request_target" ]; then
40+ IS_FORK=false
41+ if [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then
42+ IS_FORK=true
43+ fi
4044 echo "触发类型: PR触发"
4145 echo "trigger_type=pr" >> $GITHUB_OUTPUT
4246 echo "validate_path=pr_files" >> $GITHUB_OUTPUT
4347 echo "auto_fix=true" >> $GITHUB_OUTPUT
4448 echo "pr_number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
4549 echo "head_ref=${{ github.event.pull_request.head.ref }}" >> $GITHUB_OUTPUT
4650 echo "head_repo=${{ github.event.pull_request.head.repo.full_name }}" >> $GITHUB_OUTPUT
51+ echo "head_sha=${{ github.event.pull_request.head.sha }}" >> $GITHUB_OUTPUT
4752 echo "base_ref=${{ github.event.pull_request.base.ref }}" >> $GITHUB_OUTPUT
4853 echo "base_sha=${{ github.event.pull_request.base.sha }}" >> $GITHUB_OUTPUT
54+ echo "maintainer_can_modify=${{ github.event.pull_request.maintainer_can_modify }}" >> $GITHUB_OUTPUT
55+ echo "is_fork=$IS_FORK" >> $GITHUB_OUTPUT
4956 else
5057 echo "触发类型: 手动触发"
5158 echo "trigger_type=manual" >> $GITHUB_OUTPUT
@@ -54,18 +61,31 @@ jobs:
5461 echo "pr_number=${{ github.event.inputs.pr_number }}" >> $GITHUB_OUTPUT
5562 echo "head_ref=" >> $GITHUB_OUTPUT
5663 echo "head_repo=" >> $GITHUB_OUTPUT
64+ echo "head_sha=" >> $GITHUB_OUTPUT
5765 echo "base_ref=main" >> $GITHUB_OUTPUT
5866 echo "base_sha=" >> $GITHUB_OUTPUT
67+ echo "maintainer_can_modify=false" >> $GITHUB_OUTPUT
68+ echo "is_fork=false" >> $GITHUB_OUTPUT
5969 fi
6070
61- - name : Checkout code
71+ - name : Checkout PR code for same-repo PR or manual run
72+ if : ${{ steps.set_env.outputs.trigger_type != 'pr' || steps.set_env.outputs.is_fork != 'true' }}
6273 uses : actions/checkout@v4
6374 with :
6475 fetch-depth : 0
6576 ref : ${{ github.event.pull_request.head.sha || github.sha }}
6677 token : ${{ secrets.GITHUB_TOKEN }}
6778 repository : ${{ github.event.pull_request.head.repo.full_name || github.repository }}
6879
80+ - name : Checkout base repository for fork PR
81+ if : ${{ steps.set_env.outputs.trigger_type == 'pr' && steps.set_env.outputs.is_fork == 'true' }}
82+ uses : actions/checkout@v4
83+ with :
84+ fetch-depth : 0
85+ ref : ${{ github.event.pull_request.base.sha }}
86+ token : ${{ secrets.GITHUB_TOKEN }}
87+ repository : ${{ github.repository }}
88+
6989 - name : Set up Python
7090 uses : actions/setup-python@v5
7191 with :
@@ -81,7 +101,10 @@ jobs:
81101 echo "触发类型: ${{ steps.set_env.outputs.trigger_type }}"
82102 echo "验证路径: ${{ steps.set_env.outputs.validate_path }}"
83103 echo "自动修复: ${{ steps.set_env.outputs.auto_fix }}"
104+ echo "是否为fork PR: ${{ steps.set_env.outputs.is_fork }}"
84105 echo "PR号: ${{ steps.set_env.outputs.pr_number }}"
106+ echo "Head SHA: ${{ steps.set_env.outputs.head_sha }}"
107+ echo "允许维护者修改: ${{ steps.set_env.outputs.maintainer_can_modify }}"
85108 echo "Current directory: $(pwd)"
86109 echo "List files in root:"
87110 ls -la
95118
96119 - name : Setup Git and repositories
97120 run : |
121+ BOT_TOKEN="${{ secrets.PR_FIX_PAT }}"
98122 git config --global user.name "GitHub Actions Bot"
99123 git config --global user.email "actions@github.com"
100124 # 设置Git处理中文等特殊字符
@@ -116,8 +140,13 @@ jobs:
116140
117141 # 确保origin指向PR的fork仓库
118142 if [ "$PR_REPO" != "$MAIN_REPO" ] && [ "${{ steps.set_env.outputs.trigger_type }}" = "pr" ]; then
119- ORIGIN_REPO="https://github.com/${PR_REPO}.git"
120- echo "PR来自fork仓库,设置origin指向: $ORIGIN_REPO"
143+ if [ -n "$BOT_TOKEN" ] && [ "${{ steps.set_env.outputs.maintainer_can_modify }}" = "true" ]; then
144+ ORIGIN_REPO="https://x-access-token:${BOT_TOKEN}@github.com/${PR_REPO}.git"
145+ echo "PR来自fork仓库,已配置自动修复推送目标"
146+ else
147+ ORIGIN_REPO="https://github.com/${PR_REPO}.git"
148+ echo "PR来自fork仓库,但缺少 PR_FIX_PAT 或未允许维护者修改,后续将无法自动回推"
149+ fi
121150 else
122151 ORIGIN_REPO=$UPSTREAM_REPO
123152 echo "PR来自同一仓库或非PR触发,origin与upstream相同: $ORIGIN_REPO"
@@ -136,12 +165,10 @@ jobs:
136165
137166 # 检查是否处于PR环境并切换到正确的分支
138167 if [ "${{ steps.set_env.outputs.trigger_type }}" = "pr" ] && [ -n "${{ steps.set_env.outputs.head_ref }}" ]; then
139- echo "检测到PR,切换到PR分支: ${{ steps.set_env.outputs.head_ref }} "
168+ echo "检测到PR,准备工作分支 "
140169 if [ "$PR_REPO" != "$MAIN_REPO" ]; then
141- # fork仓库的PR,需要先创建本地分支追踪fork的远程分支
142- git checkout -b "${{ steps.set_env.outputs.head_ref }}" --track "origin/${{ steps.set_env.outputs.head_ref }}" || \
143- git checkout -b "${{ steps.set_env.outputs.head_ref }}" --no-track && \
144- git push --set-upstream origin "${{ steps.set_env.outputs.head_ref }}"
170+ # fork仓库的PR不要直接检出fork代码,避免在受信上下文执行不受信工作区
171+ git checkout -B "pr-fix-${{ steps.set_env.outputs.pr_number }}" "${{ steps.set_env.outputs.base_sha }}"
145172 else
146173 # 同一仓库的PR
147174 git checkout "${{ steps.set_env.outputs.head_ref }}" || git checkout -b "${{ steps.set_env.outputs.head_ref }}"
@@ -159,6 +186,63 @@ jobs:
159186 git checkout -b temp-validation-branch
160187 fi
161188
189+ - name : Apply fork PR JSON files to workspace
190+ if : ${{ steps.set_env.outputs.trigger_type == 'pr' && steps.set_env.outputs.is_fork == 'true' }}
191+ uses : actions/github-script@v6
192+ with :
193+ github-token : ${{ secrets.GITHUB_TOKEN }}
194+ script : |
195+ const fs = require('fs');
196+ const path = require('path');
197+
198+ const owner = context.repo.owner;
199+ const repo = context.repo.repo;
200+ const pull_number = Number('${{ steps.set_env.outputs.pr_number }}');
201+ const headOwner = '${{ github.event.pull_request.head.repo.owner.login }}';
202+ const headRepo = '${{ github.event.pull_request.head.repo.name }}';
203+ const headSha = '${{ steps.set_env.outputs.head_sha }}';
204+
205+ const files = await github.paginate(github.rest.pulls.listFiles, {
206+ owner,
207+ repo,
208+ pull_number,
209+ per_page: 100
210+ });
211+
212+ const targetFiles = files.filter(file => /^repo\/pathing\/.*\.json$/.test(file.filename));
213+ core.info(`需要同步到工作区的 JSON 文件数: ${targetFiles.length}`);
214+
215+ for (const file of targetFiles) {
216+ if (file.status === 'removed') {
217+ if (fs.existsSync(file.filename)) {
218+ fs.unlinkSync(file.filename);
219+ core.info(`已删除文件: ${file.filename}`);
220+ }
221+ continue;
222+ }
223+
224+ if (file.status === 'renamed' && file.previous_filename && fs.existsSync(file.previous_filename)) {
225+ fs.unlinkSync(file.previous_filename);
226+ core.info(`已删除旧文件: ${file.previous_filename}`);
227+ }
228+
229+ const response = await github.rest.repos.getContent({
230+ owner: headOwner,
231+ repo: headRepo,
232+ path: file.filename,
233+ ref: headSha
234+ });
235+
236+ if (Array.isArray(response.data) || !response.data.content) {
237+ throw new Error(`无法读取文件内容: ${file.filename}`);
238+ }
239+
240+ const content = Buffer.from(response.data.content, 'base64').toString('utf8');
241+ fs.mkdirSync(path.dirname(file.filename), { recursive: true });
242+ fs.writeFileSync(file.filename, content);
243+ core.info(`已同步文件: ${file.filename}`);
244+ }
245+
162246 - name : Prepare validation script
163247 run : |
164248 # 检查build目录和validate.py文件是否存在
@@ -282,9 +366,12 @@ jobs:
282366 GITHUB_ACTOR : ${{ github.actor }}
283367 PR_NUMBER : ${{ steps.set_env.outputs.pr_number }}
284368 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
369+ PR_FIX_PAT : ${{ secrets.PR_FIX_PAT }}
285370 HEAD_REF : ${{ steps.set_env.outputs.head_ref }}
286371 PR_REPO : ${{ steps.set_env.outputs.head_repo || github.repository }}
287372 CHANGED_FILES_B64 : ${{ steps.changed_files.outputs.changed_files }}
373+ IS_FORK : ${{ steps.set_env.outputs.is_fork }}
374+ MAINTAINER_CAN_MODIFY : ${{ steps.set_env.outputs.maintainer_can_modify }}
288375 run : |
289376 set -e
290377 # 使用base64解码文件列表
@@ -358,6 +445,26 @@ jobs:
358445
359446 # 检查是否有文件被修改
360447 if [ -n "$(git status --porcelain)" ]; then
448+ if [ "$IS_FORK" = "true" ] && [ "$MAINTAINER_CAN_MODIFY" != "true" ]; then
449+ echo "Fork PR 需要开启“允许维护者修改”才能自动回推"
450+ {
451+ echo "### ❌ 自动修复无法回推"
452+ echo "当前 PR 来自 fork,但未开启“允许维护者修改(Allow edits by maintainers)”。"
453+ echo "请开启后重新运行工作流。"
454+ } > validation_notes.md
455+ exit 1
456+ fi
457+
458+ if [ "$IS_FORK" = "true" ] && [ -z "$PR_FIX_PAT" ]; then
459+ echo "缺少 PR_FIX_PAT,无法将修复推回 fork PR"
460+ {
461+ echo "### ❌ 自动修复无法回推"
462+ echo "当前 PR 来自 fork,工作流需要仓库 Secret `PR_FIX_PAT` 才能自动推回修复结果。"
463+ echo "请配置该 Secret 后重新运行工作流。"
464+ } > validation_notes.md
465+ exit 1
466+ fi
467+
361468 echo "发现修改,提交更改"
362469 git add .
363470 git commit -m "自动修复 JSON 格式和版本号 [ci skip]"
@@ -366,7 +473,10 @@ jobs:
366473 CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
367474 echo "当前分支: ${CURRENT_BRANCH}"
368475
369- if [ "$CURRENT_BRANCH" = "HEAD" ]; then
476+ if [ "$IS_FORK" = "true" ]; then
477+ echo "推送修复到 fork 分支: ${HEAD_REF}"
478+ git push origin HEAD:${HEAD_REF}
479+ elif [ "$CURRENT_BRANCH" = "HEAD" ]; then
370480 # 如果在detached HEAD状态,使用正确的方式推送
371481 if [ -n "${HEAD_REF}" ]; then
372482 echo "在detached HEAD状态,使用HEAD_REF推送: ${HEAD_REF}"
@@ -500,7 +610,7 @@ jobs:
500610 fi
501611
502612 - name : Add PR comment
503- if : ${{ always() && (( steps.set_env.outputs.trigger_type == 'pr') || (steps.set_env.outputs.trigger_type == 'manual' && steps.set_env.outputs.pr_number != '' && steps.pr_info.outputs.found == 'true')) }}
613+ if : ${{ always() && (steps.set_env.outputs.trigger_type == 'pr' || (steps.set_env.outputs.trigger_type == 'manual' && steps.set_env.outputs.pr_number != '' && steps.pr_info.outputs.found == 'true')) }}
504614 continue-on-error : true
505615 uses : actions/github-script@v6
506616 with :
0 commit comments