|
| 1 | +# |
| 2 | +# Copyright (c) 2006-2025, RT-Thread Development Team |
| 3 | +# |
| 4 | +# SPDX-License-Identifier: Apache-2.0 |
| 5 | +# |
| 6 | +# Change Logs: |
| 7 | +# Date Author Notes |
| 8 | +# 2025-01-21 kurisaW Initial version |
| 9 | +# |
| 10 | + |
| 11 | +# Script Function Description: Assign PR reviews based on the MAINTAINERS list. |
| 12 | + |
| 13 | +name: Auto Review Assistant |
| 14 | + |
| 15 | +on: |
| 16 | + pull_request: |
| 17 | + types: [opened, synchronize, reopened] |
| 18 | + workflow_dispatch: |
| 19 | + issue_comment: |
| 20 | + types: [created] |
| 21 | + |
| 22 | +jobs: |
| 23 | + assign-reviewers: |
| 24 | + runs-on: ubuntu-22.04 |
| 25 | + if: github.repository_owner == 'RT-Thread' |
| 26 | + permissions: |
| 27 | + issues: write |
| 28 | + pull-requests: write |
| 29 | + contents: read |
| 30 | + steps: |
| 31 | + - name: Checkout code |
| 32 | + uses: actions/checkout@v3 |
| 33 | + |
| 34 | + - name: Get changed files |
| 35 | + id: changed_files |
| 36 | + run: | |
| 37 | + # 通过 GitHub API 获取 PR 的变更文件列表 |
| 38 | + changed_files=$(curl -s \ |
| 39 | + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN_AUTO_REVIEW }}" \ |
| 40 | + "https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" | \ |
| 41 | + jq -r '.[].filename') # 使用 jq 提取文件名 |
| 42 | + |
| 43 | + echo "$changed_files" | grep -v '^MAINTAINERS$' > changed_files.txt |
| 44 | +
|
| 45 | + - name: Parse MAINTAINERS file |
| 46 | + id: parse_maintainer |
| 47 | + run: | |
| 48 | + # 使用 AWK 解析 MAINTAINERS 文件格式: |
| 49 | + # 提取 tag(标签)、path(路径)和 owners(维护者 GitHub ID) |
| 50 | + awk ' |
| 51 | + /^tag:/ { |
| 52 | + tag = substr($0, index($0, $2)) # 提取标签内容 |
| 53 | + } |
| 54 | + /^path:/ { |
| 55 | + path = substr($0, index($0, $2)) # 提取路径内容 |
| 56 | + } |
| 57 | + /^owners:/ { |
| 58 | + owners = substr($0, index($0, $2)) # 提取维护者信息 |
| 59 | + split(owners, parts, /[()]/) # 拆分出 GitHub ID(括号内内容) |
| 60 | + github_ids = "" |
| 61 | + for (i=2; i<=length(parts); i+=2) { |
| 62 | + github_ids = github_ids "@" parts[i] " " # 拼接为 @user 格式 |
| 63 | + } |
| 64 | + print tag "|" path "|" github_ids |
| 65 | + } |
| 66 | + ' MAINTAINERS > tag_data.csv |
| 67 | +
|
| 68 | + - name: Generate reviewers list |
| 69 | + id: generate_reviewers |
| 70 | + run: | |
| 71 | + # 根据变更文件路径匹配维护者规则 |
| 72 | + rm -f triggered_reviewers.txt |
| 73 | + while IFS='|' read -r tag path reviewers; do |
| 74 | + # 使用正则匹配路径(支持子目录) |
| 75 | + if grep -qE "^$path(/|$)" changed_files.txt; then |
| 76 | + echo "$reviewers" | tr ' ' '\n' >> triggered_reviewers.txt |
| 77 | + fi |
| 78 | + done < tag_data.csv |
| 79 | + # 去重处理 |
| 80 | + awk 'NF && !seen[$0]++' triggered_reviewers.txt > unique_reviewers.txt |
| 81 | +
|
| 82 | + - name: Get approval status |
| 83 | + id: get_approval |
| 84 | + run: | |
| 85 | + current_time=$(date -u +"%Y-%m-%d %H:%M UTC") |
| 86 | + reviewers=$(cat unique_reviewers.txt | tr '\n' '|') |
| 87 | + |
| 88 | + # 获取 PR 的所有评论 |
| 89 | + comments=$(curl -s \ |
| 90 | + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN_AUTO_REVIEW }}" \ |
| 91 | + "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments") |
| 92 | +
|
| 93 | + echo '#!/bin/bash' > approval_data.sh |
| 94 | + echo 'declare -A approvals=()' >> approval_data.sh |
| 95 | + |
| 96 | + # 使用 jq 解析包含 LGTM 的有效评论 |
| 97 | + jq -r --arg reviewers "$reviewers" ' |
| 98 | + .[] | |
| 99 | + select(.user.login != "github-actions[bot]") | # 排除 bot 的评论 |
| 100 | + select(.body | test("^\\s*LGTM\\s*$"; "i")) | # 匹配 LGTM 评论(不区分大小写) |
| 101 | + .user.login as $user | |
| 102 | + "@\($user)" as $mention | |
| 103 | + select($mention | inside($reviewers)) | # 过滤有效审查者 |
| 104 | + "approvals[\"\($mention)\"]=\"\(.created_at)\"" # 记录审批时间 |
| 105 | + ' <<< "$comments" >> approval_data.sh |
| 106 | + |
| 107 | + # 加载审查数据并生成状态报告 |
| 108 | + chmod +x approval_data.sh |
| 109 | + source ./approval_data.sh |
| 110 | +
|
| 111 | + { |
| 112 | + echo "---" |
| 113 | + echo "### 📊 Current Review Status (Last Updated: $current_time)" |
| 114 | + while read -r reviewer; do |
| 115 | + if [[ -n "${approvals[$reviewer]}" ]]; then |
| 116 | + timestamp=$(date -d "${approvals[$reviewer]}" -u +"%Y-%m-%d %H:%M UTC") |
| 117 | + echo "- ✅ **$reviewer** Reviewed On $timestamp" |
| 118 | + else |
| 119 | + echo "- ⌛ **$reviewer** Pending Review" |
| 120 | + fi |
| 121 | + done < unique_reviewers.txt |
| 122 | + } > review_status.md |
| 123 | +
|
| 124 | + - name: Generate review data |
| 125 | + id: generate_review |
| 126 | + run: | |
| 127 | + current_time=$(date -u +"%Y-%m-%d %H:%M UTC") |
| 128 | + { |
| 129 | + # 生成审查分配信息 |
| 130 | + echo "## 📌 Code Review Assignment" |
| 131 | + echo "" |
| 132 | +
|
| 133 | + while IFS='|' read -r tag path reviewers; do |
| 134 | + if grep -qE "^$path(/|$)" changed_files.txt; then |
| 135 | + echo "### 🏷️ Tag: $tag" |
| 136 | + echo "**Path:** \`$path\` " |
| 137 | + echo "**Reviewers:** $reviewers " |
| 138 | + echo "<details>" |
| 139 | + echo "<summary><b>Changed Files</b> (Click to expand)</summary>" |
| 140 | + echo "" |
| 141 | + grep -E "^$path(/|$)" changed_files.txt | sed 's/^/- /' # 列出匹配的变更文件 |
| 142 | + echo "" |
| 143 | + echo "</details>" |
| 144 | + echo "" |
| 145 | + fi |
| 146 | + done < tag_data.csv |
| 147 | + # 插入审查状态 |
| 148 | + cat review_status.md |
| 149 | +
|
| 150 | + echo "---" |
| 151 | + echo "### 📝 Review Instructions" |
| 152 | + echo "" |
| 153 | + echo "1. **维护者可以通过单击此处来刷新审查状态:** [🔄 刷新状态](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" |
| 154 | + echo " **Maintainers can refresh the review status by clicking here:** [🔄 Refresh Status](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" |
| 155 | + echo "" |
| 156 | + echo "2. **确认审核通过后评论 \`LGTM/lgtm\`**" |
| 157 | + echo " **Comment \`LGTM/lgtm\` after confirming approval**" |
| 158 | + echo "" |
| 159 | + echo "3. **PR合并前需至少一位维护者确认**" |
| 160 | + echo " **PR must be confirmed by at least one maintainer before merging**" |
| 161 | + echo "" |
| 162 | + echo "> ℹ️ **刷新CI状态操作需要具备仓库写入权限。**" |
| 163 | + echo "> ℹ️ **Refresh CI status operation requires repository Write permission.**" |
| 164 | + } > review_data.md |
| 165 | +
|
| 166 | + - name: Post/Update comment |
| 167 | + id: post_comment |
| 168 | + run: | |
| 169 | + # 查找现有的 bot 评论 |
| 170 | + existing_comment=$(curl -s \ |
| 171 | + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN_AUTO_REVIEW }}" \ |
| 172 | + "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" | \ |
| 173 | + jq -r '.[] | select(.user.login == "github-actions[bot]") | {id: .id, body: .body} | @base64') |
| 174 | + |
| 175 | + if [[ -n "$existing_comment" ]]; then |
| 176 | + # 更新现有评论 |
| 177 | + comment_id=$(echo "$existing_comment" | head -1 | base64 -d | jq -r .id) |
| 178 | + echo "Updating existing comment $comment_id" |
| 179 | + response=$(curl -s -X PATCH \ |
| 180 | + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN_AUTO_REVIEW }}" \ |
| 181 | + -d "$(jq -n --arg body "$(cat review_data.md)" '{body: $body}')" \ |
| 182 | + "https://api.github.com/repos/${{ github.repository }}/issues/comments/$comment_id") |
| 183 | + else |
| 184 | + # 创建新评论 |
| 185 | + echo "Creating new comment" |
| 186 | + response=$(curl -s -X POST \ |
| 187 | + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN_AUTO_REVIEW }}" \ |
| 188 | + -d "$(jq -n --arg body "$(cat review_data.md)" '{body: $body}')" \ |
| 189 | + "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments") |
| 190 | + fi |
0 commit comments