-
Notifications
You must be signed in to change notification settings - Fork 1.7k
144 lines (127 loc) · 6.7 KB
/
pr-reviewer.yml
File metadata and controls
144 lines (127 loc) · 6.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
name: Auto Assign Reviewers
on:
pull_request_target:
branches: [ 'develop', 'release_**' ]
types: [ opened, edited, reopened ]
jobs:
assign-reviewers:
name: Assign Reviewers by Scope
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Assign reviewers based on PR title scope
uses: actions/github-script@v8
with:
script: |
const title = context.payload.pull_request.title;
const prAuthor = context.payload.pull_request.user.login;
// ── Scope → Reviewer mapping ──────────────────────────────
const scopeReviewers = {
'framework': ['xxo1shine', '317787106'],
'chainbase': ['halibobo1205', 'lvs0075'],
'db': ['halibobo1205', 'xxo1shine'],
'trie': ['halibobo1205', '317787106'],
'actuator': ['yanghang8612', 'lxcmyf'],
'consensus': ['lvs0075', 'xxo1shine'],
'protocol': ['lvs0075', 'waynercheung'],
'common': ['xxo1shine', 'lxcmyf'],
'crypto': ['Federico2014', '3for'],
'net': ['317787106', 'xxo1shine'],
'vm': ['yanghang8612', 'CodeNinjaEvan'],
'tvm': ['yanghang8612', 'CodeNinjaEvan'],
'jsonrpc': ['0xbigapple', 'bladehan1'],
'rpc': ['317787106', 'Sunny6889'],
'http': ['Sunny6889', 'bladehan1'],
'event': ['xxo1shine', '0xbigapple'],
'config': ['317787106', 'halibobo1205'],
'backup': ['xxo1shine', '317787106'],
'lite': ['bladehan1', 'halibobo1205'],
'toolkit': ['halibobo1205', 'Sunny6889'],
'plugins': ['halibobo1205', 'Sunny6889'],
'docker': ['3for', 'kuny0707'],
'test': ['bladehan1', 'lxcmyf'],
'metrics': ['halibobo1205', 'Sunny6889'],
'api': ['0xbigapple', 'waynercheung', 'bladehan1'],
'ci': ['bladehan1', 'halibobo1205'],
};
const defaultReviewers = ['halibobo1205', '317787106'];
// ── Normalize helper ─────────────────────────────────────
// Strip spaces, hyphens, underscores and lower-case so that
// "VM", " json rpc ", "chain-base", "Json_Rpc" all normalize
// to their canonical key form ("vm", "jsonrpc", "chainbase").
const normalize = s => s.toLowerCase().replace(/[\s\-_]/g, '');
// ── Extract scope from conventional commit title ──────────
// Format: type(scope): description
// Also supports: type(scope1,scope2): description
const scopeMatch = title.match(/^\w+\(([^)]+)\):/);
const rawScope = scopeMatch ? scopeMatch[1] : null;
core.info(`PR title : ${title}`);
core.info(`Raw scope: ${rawScope || '(none)'}`);
// ── Skip if reviewers already assigned ──────────────────
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
});
const existing = pr.data.requested_reviewers || [];
if (existing.length > 0) {
core.info(`Reviewers already assigned (${existing.map(r => r.login).join(', ')}). Skipping.`);
return;
}
// ── Determine reviewers ───────────────────────────────────
// 1. Split by comma to support multi-scope: feat(vm,rpc): ...
// 2. Normalize each scope token
// 3. Match against keys: exact match first, then contains match
// (longest key wins to avoid "net" matching inside "jsonrpc")
let matched = new Set();
let matchedScopes = [];
if (rawScope) {
const tokens = rawScope.split(',').map(s => normalize(s.trim()));
// Pre-sort keys by length descending so longer keys match first
const sortedKeys = Object.keys(scopeReviewers)
.sort((a, b) => b.length - a.length);
for (const token of tokens) {
if (!token) continue;
// Exact match
if (scopeReviewers[token]) {
matchedScopes.push(token);
scopeReviewers[token].forEach(r => matched.add(r));
continue;
}
// Contains match: token contains a key, or key contains token
// Prefer longest key that matches
const found = sortedKeys.find(k => token.includes(k) || k.includes(token));
if (found) {
matchedScopes.push(`${token}→${found}`);
scopeReviewers[found].forEach(r => matched.add(r));
}
}
}
let reviewers = matched.size > 0
? [...matched]
: defaultReviewers;
core.info(`Matched scopes: ${matchedScopes.length > 0 ? matchedScopes.join(', ') : '(none — using default)'}`);
core.info(`Candidate reviewers: ${reviewers.join(', ')}`);
// Exclude the PR author from the reviewer list
reviewers = reviewers.filter(r => r.toLowerCase() !== prAuthor.toLowerCase());
if (reviewers.length === 0) {
core.info('No eligible reviewers after excluding PR author. Skipping.');
return;
}
core.info(`Assigning reviewers: ${reviewers.join(', ')}`);
// ── Request reviews ───────────────────────────────────────
try {
await github.rest.pulls.requestReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
reviewers: reviewers,
});
core.info('Reviewers assigned successfully.');
} catch (error) {
// If a reviewer is not a collaborator the API returns 422;
// log the error but do not fail the workflow.
core.warning(`Failed to assign some reviewers: ${error.message}`);
}