Skip to content

Commit 1c16eb1

Browse files
改用重建分支方式处理投稿 PR 冲突
1 parent a056b0d commit 1c16eb1

1 file changed

Lines changed: 139 additions & 38 deletions

File tree

.github/workflows/approve_submission.yml

Lines changed: 139 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
1-
name: Auto-rebase submission PRs
1+
name: Rebuild open submission PRs after merge
22

3-
# 每当有 PR 合并到 main 时触发
43
on:
54
pull_request:
65
types: [closed]
76
branches: [main]
87

98
concurrency:
10-
group: rebase-submissions
9+
group: rebuild-submissions
1110
cancel-in-progress: false
1211

1312
jobs:
14-
rebase-open-prs:
15-
# 只在 PR 被合并(不是关闭)时运行
13+
rebuild:
1614
if: github.event.pull_request.merged == true
1715
runs-on: ubuntu-latest
1816
permissions:
@@ -23,14 +21,16 @@ jobs:
2321
- uses: actions/checkout@v4
2422
with:
2523
ref: main
26-
fetch-depth: 0
24+
fetch-depth: 1
2725
token: ${{ secrets.GITHUB_TOKEN }}
2826

29-
- name: Rebase all open submission PRs
27+
- name: Rebuild open PRs onto latest main
3028
uses: actions/github-script@v7
3129
with:
3230
script: |
3331
const { execSync } = require('child_process');
32+
const fs = require('fs');
33+
const path = require('path');
3434
3535
execSync('git config user.name "github-actions[bot]"');
3636
execSync('git config user.email "github-actions[bot]@users.noreply.github.com"');
@@ -46,54 +46,155 @@ jobs:
4646
});
4747
4848
if (openPRs.length === 0) {
49-
core.info('No open PRs to rebase.');
49+
core.info('No open PRs to rebuild.');
5050
return;
5151
}
5252
53-
core.info(`Found ${openPRs.length} open PR(s) to rebase.`);
54-
55-
let succeeded = 0;
56-
let failed = 0;
53+
core.info(`Found ${openPRs.length} open PR(s) to rebuild.`);
5754
5855
for (const pr of openPRs) {
5956
const branch = pr.head.ref;
60-
core.info(`\nRebasing PR #${pr.number}: ${pr.title} (branch: ${branch})`);
57+
core.info(`\nRebuilding PR #${pr.number}: ${pr.title} (branch: ${branch})`);
6158
6259
try {
63-
// 获取远程分支
64-
execSync(`git fetch origin ${branch}:${branch}`, { stdio: 'pipe' });
65-
66-
// 切到该分支并 rebase 到最新 main
67-
execSync(`git checkout ${branch}`, { stdio: 'pipe' });
68-
execSync('git rebase origin/main', { stdio: 'pipe' });
69-
70-
// force push 更新 PR
71-
execSync(`git push origin ${branch} --force-with-lease`, { stdio: 'pipe' });
72-
73-
core.info(`✅ PR #${pr.number} rebased successfully.`);
74-
succeeded++;
60+
// 1. 获取 PR 的文件变更列表
61+
const { data: files } = await github.rest.pulls.listFiles({
62+
owner: context.repo.owner,
63+
repo: context.repo.repo,
64+
pull_number: pr.number
65+
});
7566
76-
// 切回 main 准备下一个
67+
// 2. 找到新增的脚本文件(排除 index.json)
68+
const scriptFiles = files.filter(f =>
69+
f.status === 'added' && f.filename !== 'script_library/index.json'
70+
);
71+
72+
if (scriptFiles.length === 0) {
73+
core.warning(`PR #${pr.number}: No new script files found, skipping.`);
74+
continue;
75+
}
76+
77+
// 3. 从 PR 分支获取每个脚本文件的内容
78+
const scriptContents = [];
79+
for (const sf of scriptFiles) {
80+
try {
81+
const { data: fileData } = await github.rest.repos.getContent({
82+
owner: context.repo.owner,
83+
repo: context.repo.repo,
84+
path: sf.filename,
85+
ref: branch
86+
});
87+
const content = Buffer.from(fileData.content, 'base64').toString('utf8');
88+
scriptContents.push({ path: sf.filename, content });
89+
} catch (e) {
90+
core.warning(`PR #${pr.number}: Failed to read ${sf.filename}: ${e.message}`);
91+
}
92+
}
93+
94+
if (scriptContents.length === 0) {
95+
core.warning(`PR #${pr.number}: Could not read script content, skipping.`);
96+
continue;
97+
}
98+
99+
// 4. 从 PR body 中提取元数据
100+
const body = pr.body || '';
101+
const field = (key) => {
102+
const re = new RegExp(`${key}[::]\\s*(.+)`, 'i');
103+
const m = body.match(re);
104+
return m ? m[1].trim() : '';
105+
};
106+
const author = field('作者') || field('Author') || '社区投稿';
107+
const category = field('分类') || field('Category') || 'basic';
108+
const desc = field('描述') || field('Description') || pr.title;
109+
const scriptName = pr.title.replace(/^\[投稿[::]\s*\w+\]\s*/, '').replace(/^\[投稿\]\s*/, '').trim() || 'untitled';
110+
111+
const catMap = {
112+
'基础脚本': 'basic', 'basic': 'basic',
113+
'UI 界面': 'ui', 'UI': 'ui', 'ui': 'ui',
114+
'游戏': 'games', 'Games': 'games', 'games': 'games',
115+
'小组件': 'widgets', 'Widgets': 'widgets', 'widgets': 'widgets',
116+
'其他': 'other', 'Other': 'other', 'other': 'other'
117+
};
118+
const catId = catMap[category] || 'basic';
119+
120+
// 5. 基于最新 main 重建分支
77121
execSync('git checkout main', { stdio: 'pipe' });
78-
} catch (e) {
79-
core.warning(`❌ PR #${pr.number} rebase failed, aborting.`);
80-
failed++;
122+
execSync('git pull origin main', { stdio: 'pipe' });
81123
82124
try {
83-
execSync('git rebase --abort', { stdio: 'pipe' });
125+
execSync(`git branch -D ${branch}`, { stdio: 'pipe' });
84126
} catch (_) {}
85-
127+
execSync(`git checkout -b ${branch}`, { stdio: 'pipe' });
128+
129+
// 6. 写入脚本文件
130+
for (const sc of scriptContents) {
131+
const dir = path.dirname(sc.path);
132+
fs.mkdirSync(dir, { recursive: true });
133+
fs.writeFileSync(sc.path, sc.content, 'utf8');
134+
}
135+
136+
// 7. 读取最新 index.json 并添加条目
137+
const indexPath = 'script_library/index.json';
138+
const index = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
139+
140+
const safeId = scriptName
141+
.toLowerCase()
142+
.replace(/[\s]+/g, '_')
143+
.replace(/[^a-z0-9_\u4e00-\u9fff]/g, '')
144+
.substring(0, 40) || `submission_${pr.number}`;
145+
const scriptId = `community_${safeId}`;
146+
147+
// 去重:如果已存在则跳过
148+
if (!index.scripts.some(s => s.id === scriptId)) {
149+
const mainScript = scriptContents[0];
150+
const lineCount = mainScript.content.split('\n').length;
151+
const fileType = path.extname(mainScript.path).replace('.', '') || 'py';
152+
153+
index.data_version += 1;
154+
index.updated = new Date().toISOString().replace(/\.\d+Z$/, 'Z');
155+
index.scripts.push({
156+
id: scriptId,
157+
name: scriptName,
158+
name_en: scriptName,
159+
desc: desc,
160+
desc_en: desc,
161+
category: catId,
162+
file: mainScript.path.replace('script_library/', ''),
163+
thumbnail: null,
164+
version: 1,
165+
file_type: fileType,
166+
author: author,
167+
author_en: author,
168+
tags: ['community', catId],
169+
requires: [],
170+
min_app_version: '1.5.0',
171+
added: new Date().toISOString().split('T')[0],
172+
updated: null,
173+
status: 'active',
174+
lines: lineCount
175+
});
176+
fs.writeFileSync(indexPath, JSON.stringify(index, null, 2) + '\n', 'utf8');
177+
}
178+
179+
// 8. 提交并 force push
180+
execSync(`git add -A`, { stdio: 'pipe' });
181+
execSync(`git commit -m "投稿: ${scriptName}" --allow-empty`, { stdio: 'pipe' });
182+
execSync(`git push origin ${branch} --force`, { stdio: 'pipe' });
183+
184+
core.info(`✅ PR #${pr.number} rebuilt successfully on latest main.`);
185+
186+
// 切回 main
86187
execSync('git checkout main', { stdio: 'pipe' });
87188
88-
// 在 PR 上留言提示冲突
89-
await github.rest.pulls.createReview({
189+
} catch (e) {
190+
core.warning(`❌ PR #${pr.number} rebuild failed: ${e.message}`);
191+
try { execSync('git checkout main', { stdio: 'pipe' }); } catch (_) {}
192+
193+
await github.rest.issues.createComment({
90194
owner: context.repo.owner,
91195
repo: context.repo.repo,
92-
pull_number: pr.number,
93-
event: 'COMMENT',
94-
body: '⚠️ 自动 rebase 失败,此 PR 存在无法自动解决的冲突,需要手动处理。'
196+
issue_number: pr.number,
197+
body: '⚠️ 自动重建失败,可能需要手动处理。'
95198
});
96199
}
97200
}
98-
99-
core.info(`\nDone. Succeeded: ${succeeded}, Failed: ${failed}`);

0 commit comments

Comments
 (0)