66# Change Logs:
77# Date Author Notes
88# 2025-01-21 kurisaW Initial version
9- #
9+ # 2025-03-14 hydevcode
1010
1111# Script Function Description: Assign PR reviews based on the MAINTAINERS list.
1212
1313name : Auto Review Assistant
1414
1515on :
16- pull_request :
16+ pull_request_target :
17+ branches : [ master ]
1718 types : [opened, synchronize, reopened]
18- workflow_dispatch :
19- issue_comment :
20- types : [created]
2119
2220jobs :
2321 assign-reviewers :
2422 runs-on : ubuntu-22.04
25- # if: github.repository_owner == 'RT-Thread'
23+ if : github.repository_owner == 'RT-Thread'
2624 permissions :
27- issues : write
28- pull-requests : read
25+ issues : read
26+ pull-requests : write
2927 contents : read
3028 steps :
29+ - name : Extract PR number
30+ id : extract-pr
31+ run : |
32+ PR_NUMBER=${{ github.event.pull_request.number }}
33+ echo "PR_NUMBER=${PR_NUMBER}" >> $GITHUB_OUTPUT
3134 - name : Checkout code
32- uses : actions/checkout@v3
35+ uses : actions/checkout@v4
3336 with :
37+ ref : master
38+ sparse-checkout : MAINTAINERS
3439 persist-credentials : false
3540 - name : Get changed files
3641 id : changed_files
3742 run : |
3843 # 通过 GitHub API 获取 PR 的变更文件列表
3944 changed_files=$(curl -s \
40- -H "Authorization: Bearer ${{ github.token }}" \
41- "https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" | \
45+ "https://api.github.com/repos/${{ github.repository }}/pulls/${{ steps.extract-pr.outputs.PR_NUMBER }}/files" | \
4246 jq -r '.[].filename') # 使用 jq 提取文件名
4347 echo "$changed_files" | grep -v '^MAINTAINERS$' > changed_files.txt
48+
49+ existing_comment=$(curl -s \
50+ "https://api.github.com/repos/${{ github.repository }}/issues/${{ steps.extract-pr.outputs.PR_NUMBER }}/comments" | \
51+ jq -r '.[] | select(.user.login == "github-actions[bot]") | {body: .body} | @base64')
52+
53+ comment_body=""
54+ if [[ ! -z "$existing_comment" ]]; then
55+ comment_body=$(echo "$existing_comment" | head -1 | base64 -d | jq -r .body|sed -nE 's/.*Last Updated: ([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2} UTC).*/\1/p')
56+
57+ comment_time=$(date -d "$comment_body" +%s)
58+
59+ echo "${comment_body}"
60+ echo "COMMENT_TIME=${comment_time}" >> $GITHUB_OUTPUT
61+ else
62+ comment_time=""
63+ echo "COMMENT_TIME=${comment_time}" >> $GITHUB_OUTPUT
64+ fi
65+ echo "COMMENT_TIME=${comment_time}"
4466 - name : Parse MAINTAINERS file
4567 id : parse_maintainer
4668 run : |
4769 # 使用 AWK 解析 MAINTAINERS 文件格式:
48- # 提取 tag(标签) 、path(路径) 和 owners( 维护者 GitHub ID)
70+ # 提取 tag(标签) 、path(路径) 和 owners( 维护者 GitHub ID)
4971 awk '
5072 /^tag:/ {
5173 tag = substr($0, index($0, $2)) # 提取标签内容
@@ -63,21 +85,30 @@ jobs:
6385 print tag "|" path "|" github_ids
6486 }
6587 ' MAINTAINERS > tag_data.csv
66-
6788 - name : Generate reviewers list
6889 id : generate_reviewers
6990 run : |
7091 # 根据变更文件路径匹配维护者规则
7192 rm -f triggered_reviewers.txt
93+ rm -f triggered_tags.txt
7294 while IFS='|' read -r tag path reviewers; do
7395 # 使用正则匹配路径(支持子目录)
7496 if grep -qE "^$path(/|$)" changed_files.txt; then
7597 echo "$reviewers" | tr ' ' '\n' >> triggered_reviewers.txt
98+ echo "$tag" | tr ' ' '\n' >> triggered_tags.txt
7699 fi
77100 done < tag_data.csv
78- # 去重处理
79101 awk 'NF && !seen[$0]++' triggered_reviewers.txt > unique_reviewers.txt
80-
102+ awk 'NF && !seen[$0]++' triggered_tags.txt > unique_tags.txt
103+ - name : Restore Reviewers Cache
104+ id : reviewers-cache-restore
105+ if : ${{ steps.changed_files.outputs.COMMENT_TIME != '' }}
106+ uses : actions/cache/restore@v4
107+ with :
108+ path : |
109+ unique_tags_bak.txt
110+ unique_reviewers_bak.txt
111+ key : ${{ runner.os }}-auto-assign-reviewers-${{ steps.extract-pr.outputs.PR_NUMBER }}-${{ steps.changed_files.outputs.COMMENT_TIME }}
81112 - name : Get approval status
82113 id : get_approval
83114 run : |
86117
87118 # 获取 PR 的所有评论
88119 comments=$(curl -s \
89- -H "Authorization: Bearer ${{ github.token }}" \
90- "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments")
120+ "https://api.github.com/repos/${{ github.repository }}/issues/${{ steps.extract-pr.outputs.PR_NUMBER }}/comments")
91121
92122 echo '#!/bin/bash' > approval_data.sh
93123 echo 'declare -A approvals=()' >> approval_data.sh
@@ -107,24 +137,69 @@ jobs:
107137 chmod +x approval_data.sh
108138 source ./approval_data.sh
109139
140+ jq -r --arg reviewers "$reviewers" '
141+ .[] |
142+ select(.user.login != "github-actions[bot]") | # 排除 bot 的评论
143+ select(.body | test("^\\s*LGTM\\s*$"; "i")) | # 匹配 LGTM 评论(不区分大小写)
144+ .user.login as $user |
145+ "@\($user)" as $mention |
146+ select($mention | inside($reviewers)) | # 过滤有效审查者
147+ "\($mention) \(.created_at)" # 输出审查者和时间
148+ ' <<< "$comments" >> approval_data.txt
149+
150+ notified_users=""
151+ if [[ -f unique_reviewers_bak.txt ]]; then
152+ notified_users=$(cat unique_reviewers_bak.txt | xargs)
153+ else
154+ notified_users=""
155+ fi
156+
110157 {
111158 echo "---"
112159 echo "### 📊 Current Review Status (Last Updated: $current_time)"
113160 while read -r reviewer; do
161+ formatted_reviewers=""
162+ for r in $reviewers; do
163+ if [[ " ${notified_users[@]} " =~ " $reviewer " ]]; then
164+ formatted_reviewers+="${reviewer#@}"
165+ else
166+ formatted_reviewers+="$reviewer"
167+ fi
168+ done
169+
114170 if [[ -n "${approvals[$reviewer]}" ]]; then
115171 timestamp=$(date -d "${approvals[$reviewer]}" -u +"%Y-%m-%d %H:%M UTC")
116- echo "- ✅ **$reviewer** Reviewed On $timestamp"
172+
173+ echo "- ✅ **$formatted_reviewers** Reviewed On $timestamp"
117174 else
118- echo "- ⌛ **$reviewer ** Pending Review"
175+ echo "- ⌛ **$formatted_reviewers ** Pending Review"
119176 fi
120177 done < unique_reviewers.txt
121178 } > review_status.md
122-
179+
180+ echo "CURRENT_TIME=${current_time}" >> $GITHUB_OUTPUT
123181 - name : Generate review data
124182 id : generate_review
125183 run : |
184+ unique_tags=""
185+ unique_tags=$(cat unique_tags.txt | xargs)
186+ unique_tags_bak=""
187+ if [[ -f unique_tags_bak.txt ]]; then
188+ unique_tags_bak=$(cat unique_tags_bak.txt | xargs)
189+ fi
190+
191+ existing_tags=""
192+ for r in $unique_tags; do
193+ if [[ " ${unique_tags_bak[@]} " =~ " $r " ]]; then
194+ echo "$r 不存在于数组中"
195+ else
196+ existing_tags+="$r "
197+ fi
198+ done
199+
126200 current_time=$(date -u +"%Y-%m-%d %H:%M UTC")
127201 {
202+
128203 # 生成审查分配信息
129204 echo "## 📌 Code Review Assignment"
130205 echo ""
@@ -133,7 +208,17 @@ jobs:
133208 if grep -qE "^$path(/|$)" changed_files.txt; then
134209 echo "### 🏷️ Tag: $tag"
135210 echo "**Path:** \`$path\` "
136- echo "**Reviewers:** $reviewers "
211+
212+ if [[ " ${existing_tags[@]} " =~ " $tag " ]]; then
213+ echo "**Reviewers:** $reviewers "
214+ else
215+ formatted_reviewers=""
216+ for r in $reviewers; do
217+ formatted_reviewers+="${r#@} "
218+ done
219+ echo "**Reviewers:** $formatted_reviewers "
220+ fi
221+
137222 echo "<details>"
138223 echo "<summary><b>Changed Files</b> (Click to expand)</summary>"
139224 echo ""
@@ -144,6 +229,8 @@ jobs:
144229 fi
145230 done < tag_data.csv
146231 # 插入审查状态
232+ cat review_status.md
233+
147234 echo "---"
148235 echo "### 📝 Review Instructions"
149236 echo ""
@@ -159,12 +246,47 @@ jobs:
159246 echo "> ℹ️ **刷新CI状态操作需要具备仓库写入权限。**"
160247 echo "> ℹ️ **Refresh CI status operation requires repository Write permission.**"
161248 } > review_data.md
162- - name : Save PR number
249+ - name : Post/Update comment
250+ id : post_comment
251+ run : |
252+ # 查找现有的 bot 评论
253+ existing_comment=$(curl -s \
254+ -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
255+ "https://api.github.com/repos/${{ github.repository }}/issues/${{ steps.extract-pr.outputs.PR_NUMBER }}/comments" | \
256+ jq -r '.[] | select(.user.login == "github-actions[bot]") | {id: .id, body: .body} | @base64')
257+
258+ if [[ -n "$existing_comment" ]]; then
259+ # 更新现有评论
260+ comment_id=$(echo "$existing_comment" | head -1 | base64 -d | jq -r .id)
261+ echo "Updating existing comment $comment_id"
262+ response=$(curl -s -X PATCH \
263+ -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
264+ -d "$(jq -n --arg body "$(cat review_data.md)" '{body: $body}')" \
265+ "https://api.github.com/repos/${{ github.repository }}/issues/comments/$comment_id")
266+ else
267+ # 创建新评论
268+ echo "Creating new comment"
269+ response=$(curl -s -X POST \
270+ -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
271+ -d "$(jq -n --arg body "$(cat review_data.md)" '{body: $body}')" \
272+ "https://api.github.com/repos/${{ github.repository }}/issues/${{ steps.extract-pr.outputs.PR_NUMBER }}/comments")
273+ fi
274+ - name : Get Comment Time
275+ id : get_comment_time
163276 run : |
164- mkdir -p ./pr
165- echo ${{ github.event.number }} > ./pr/NR
166- mv /home/runner/work/rt-thread/rt-thread/review_data.md ./pr/
167- - uses : actions/upload-artifact@v4
277+ existing_comment=$(curl -s \
278+ "https://api.github.com/repos/${{ github.repository }}/issues/${{ steps.extract-pr.outputs.PR_NUMBER }}/comments" | \
279+ jq -r '.[] | select(.user.login == "github-actions[bot]") | {body: .body} | @base64')
280+ comment_body="${{ steps.get_approval.outputs.CURRENT_TIME }}"
281+ comment_time=$(date -d "$comment_body" +%s)
282+ echo "CURRENT_TIME=${comment_time}" >> $GITHUB_OUTPUT
283+ cp unique_reviewers.txt unique_reviewers_bak.txt
284+ cp unique_tags.txt unique_tags_bak.txt
285+ - name : Restore Reviewers Save
286+ id : reviewers-cache-save
287+ uses : actions/cache/save@v4
168288 with :
169- name : pr
170- path : pr
289+ path : |
290+ unique_tags_bak.txt
291+ unique_reviewers_bak.txt
292+ key : ${{ runner.os }}-auto-assign-reviewers-${{ steps.extract-pr.outputs.PR_NUMBER }}-${{ steps.get_comment_time.outputs.CURRENT_TIME }}
0 commit comments