11 KiB
11 KiB
PR 自动更新
概述
自动更新 Pull Request 描述和标签的命令。通过分析 Git 更改内容,生成并设置适当的描述文本和标签。
使用方法
/pr-auto-update [选项] [PR 编号]
选项
--pr <编号>: 指定目标 PR 编号 (省略时从当前分支自动检测)--description-only: 仅更新描述 (不修改标签)--labels-only: 仅更新标签 (不修改描述)--dry-run: 不执行实际更新,仅显示生成的内容--lang <语言>: 指定语言 (zh-cn, en)
基本示例
# 自动更新当前分支的 PR
/pr-auto-update
# 更新特定的 PR
/pr-auto-update --pr 1234
# 仅更新描述
/pr-auto-update --description-only
# 预演模式确认
/pr-auto-update --dry-run
功能详情
1. PR 自动检测
从当前分支自动检测对应的 PR:
# 从分支搜索 PR
gh pr list --head $(git branch --show-current) --json number,title,url
2. 更改内容分析
收集和分析以下信息:
- 文件更改: 添加、删除、修改的文件
- 代码分析: import 语句、函数定义、类定义的更改
- 测试: 测试文件的存在与内容
- 文档: README、docs 的更新
- 配置: package.json、pubspec.yaml、配置文件的更改
- CI/CD: GitHub Actions、workflow 的更改
3. 描述文本自动生成
模板处理优先级
- 现有 PR 描述: 完全遵循已存在的内容
- 项目模板: 从
.github/PULL_REQUEST_TEMPLATE.md获取结构 - 默认模板: 上述不存在时的后备方案
现有内容保留规则
重要: 不修改现有内容
- 保留已写的部分
- 仅补充空白部分
- 保留功能性注释 (如 Copilot review rule 等)
项目模板的使用
# 解析 .github/PULL_REQUEST_TEMPLATE.md 的结构
parse_template_structure() {
local template_file="$1"
if [ -f "$template_file" ]; then
# 提取部分结构
grep -E '^##|^###' "$template_file"
# 识别注释占位符
grep -E '<!--.*-->' "$template_file"
# 完全遵循现有模板结构
cat "$template_file"
fi
}
4. 标签自动设置
标签获取机制
优先级:
.github/labels.yml: 从项目特定的标签定义获取- GitHub API: 使用
gh api repos/{OWNER}/{REPO}/labels --jq '.[].name'获取现有标签
自动判定规则
基于文件模式:
- 文档:
*.md,README,docs/→ 包含documentation|docs|doc的标签 - 测试:
test,spec→ 包含test|testing的标签 - CI/CD:
.github/,*.yml,Dockerfile→ 包含ci|build|infra|ops的标签 - 依赖:
package.json,pubspec.yaml,requirements.txt→ 包含dependencies|deps的标签
基于更改内容:
- Bug 修复:
fix|bug|error|crash|修复→ 包含bug|fix的标签 - 新功能:
feat|feature|add|implement|新功能|实装→ 包含feature|enhancement|feat的标签 - 重构:
refactor|clean|重构→ 包含refactor|cleanup|clean的标签 - 性能:
performance|perf|optimize|性能→ 包含performance|perf的标签 - 安全:
security|secure|安全→ 包含security的标签
约束
- 最多 3 个: 自动选择的标签数量上限
- 仅限现有标签: 禁止创建新标签
- 部分匹配: 根据标签名是否包含关键词判定
实际使用示例
存在 .github/labels.yml 时:
# 从标签定义自动获取
grep "^- name:" .github/labels.yml | sed "s/^- name: '\\?\\([^']*\\)'\\?/\\1/"
# 例:使用项目特定的标签体系
从 GitHub API 获取时:
# 获取现有标签列表
gh api repos/{OWNER}/{REPO}/labels --jq '.[].name'
# 例:使用 bug, enhancement, documentation 等标准标签
5. 执行流程
#!/bin/bash
# 1. PR 检测与获取
detect_pr() {
if [ -n "$PR_NUMBER" ]; then
echo $PR_NUMBER
else
gh pr list --head $(git branch --show-current) --json number --jq '.[0].number'
fi
}
# 2. 更改内容分析
analyze_changes() {
local pr_number=$1
# 获取文件更改
gh pr diff $pr_number --name-only
# 内容分析
gh pr diff $pr_number | head -1000
}
# 3. 描述生成
generate_description() {
local pr_number=$1
local changes=$2
# 获取当前 PR 描述
local current_body=$(gh pr view $pr_number --json body --jq -r .body)
# 如有现有内容则直接使用
if [ -n "$current_body" ]; then
echo "$current_body"
else
# 从模板生成新内容
local template_file=".github/PULL_REQUEST_TEMPLATE.md"
if [ -f "$template_file" ]; then
generate_from_template "$(cat "$template_file")" "$changes"
else
generate_from_template "" "$changes"
fi
fi
}
# 从模板生成
generate_from_template() {
local template="$1"
local changes="$2"
if [ -n "$template" ]; then
# 直接使用模板 (保留 HTML 注释)
echo "$template"
else
# 使用默认格式生成
echo "## What does this change?"
echo ""
echo "$changes"
fi
}
# 4. 标签确定
determine_labels() {
local changes=$1
local file_list=$2
local pr_number=$3
# 获取可用标签
local available_labels=()
if [ -f ".github/labels.yml" ]; then
# 从 labels.yml 提取标签名
available_labels=($(grep "^- name:" .github/labels.yml | sed "s/^- name: '\\?\\([^']*\\)'\\?/\\1/"))
else
# 从 GitHub API 获取标签
local repo_info=$(gh repo view --json owner,name)
local owner=$(echo "$repo_info" | jq -r .owner.login)
local repo=$(echo "$repo_info" | jq -r .name)
available_labels=($(gh api "repos/$owner/$repo/labels" --jq '.[].name'))
fi
local suggested_labels=()
# 通用模式匹配
analyze_change_patterns "$file_list" "$changes" available_labels suggested_labels
# 限制最多 3 个
echo "${suggested_labels[@]:0:3}"
}
# 根据更改模式确定标签
analyze_change_patterns() {
local file_list="$1"
local changes="$2"
local -n available_ref=$3
local -n suggested_ref=$4
# 根据文件类型判定
if echo "$file_list" | grep -q "\\.md$\\|README\\|docs/"; then
add_matching_label "documentation\\|docs\\|doc" available_ref suggested_ref
fi
if echo "$file_list" | grep -q "test\\|spec"; then
add_matching_label "test\\|testing" available_ref suggested_ref
fi
# 根据更改内容判定
if echo "$changes" | grep -iq "fix\\|bug\\|error\\|crash\\|修复"; then
add_matching_label "bug\\|fix" available_ref suggested_ref
fi
if echo "$changes" | grep -iq "feat\\|feature\\|add\\|implement\\|新功能\\|实现"; then
add_matching_label "feature\\|enhancement\\|feat" available_ref suggested_ref
fi
}
# 添加匹配的标签
add_matching_label() {
local pattern="$1"
local -n available_ref=$2
local -n suggested_ref=$3
# 如果已有 3 个则跳过
if [ ${#suggested_ref[@]} -ge 3 ]; then
return
fi
# 添加匹配模式的第一个标签
for available_label in "${available_ref[@]}"; do
if echo "$available_label" | grep -iq "$pattern"; then
# 重复检查
local already_exists=false
for existing in "${suggested_ref[@]}"; do
if [ "$existing" = "$available_label" ]; then
already_exists=true
break
fi
done
if [ "$already_exists" = false ]; then
suggested_ref+=("$available_label")
return
fi
fi
done
}
# 为兼容性保留旧函数
find_and_add_label() {
add_matching_label "$@"
}
# 5. PR 更新
update_pr() {
local pr_number=$1
local description="$2"
local labels="$3"
if [ "$DRY_RUN" = "true" ]; then
echo "=== DRY RUN ==="
echo "Description:"
echo "$description"
echo "Labels: $labels"
else
# 获取仓库信息
local repo_info=$(gh repo view --json owner,name)
local owner=$(echo "$repo_info" | jq -r .owner.login)
local repo=$(echo "$repo_info" | jq -r .name)
# 使用 GitHub API 更新正文 (保留 HTML 注释)
# 正确处理 JSON 转义
local escaped_body=$(echo "$description" | jq -R -s .)
gh api \
--method PATCH \
"/repos/$owner/$repo/pulls/$pr_number" \
--field body="$description"
# 标签使用常规 gh 命令即可
if [ -n "$labels" ]; then
gh pr edit $pr_number --add-label "$labels"
fi
fi
}
配置文件 (未来扩展用)
~/.claude/pr-auto-update.config:
{
"language": "zh-cn",
"max_labels": 3
}
常见模式
Flutter 项目
## What does this change?
实现了{功能名}。解决了用户的{问题}。
### 主要更改内容
- **UI 实现**: 新建{画面名}
- **状态管理**: 添加 Riverpod Provider
- **API 集成**: 实现 GraphQL 查询与变更
- **测试**: 添加 Widget 测试和单元测试
### 技术规格
- **架构**: {使用的模式}
- **依赖**: {新增的包}
- **性能**: {优化内容}
Node.js 项目
## What does this change?
实现了{API 名}端点。支持{用例}。
### 主要更改内容
- **API 实现**: 新建{端点}
- **验证**: 添加请求验证逻辑
- **数据库**: 实现对{表名}的操作
- **测试**: 添加集成测试和单元测试
### 安全性
- **认证**: JWT 令牌验证
- **授权**: 基于角色的访问控制
- **输入验证**: SQL 注入防护
CI/CD 改进
## What does this change?
改进了 GitHub Actions 工作流。实现了{效果}。
### 改进内容
- **性能**: 构建时间减少{时间}
- **可靠性**: 增强错误处理
- **安全性**: 改进密钥管理
### 技术细节
- **并行化**: {作业名}并行执行
- **缓存**: 优化{缓存对象}的缓存策略
- **监控**: 添加{指标}监控
注意事项
-
完全保留现有内容:
- 一字不改现有描述内容
- 仅补充空白注释和占位符
- 尊重用户有意编写的内容
-
模板优先级:
- 现有 PR 描述 >
.github/PULL_REQUEST_TEMPLATE.md> 默认 - 完全遵循项目特定的模板结构
- 现有 PR 描述 >
-
标签约束:
- 如存在
.github/labels.yml则优先使用 - 不存在时从 GitHub API 获取现有标签
- 禁止创建新标签
- 自动选择最多 3 个
- 如存在
-
安全更新:
- 推荐使用
--dry-run预先确认 - 包含敏感信息的更改时显示警告
- 保存原始描述作为备份
- 推荐使用
-
保持一致性:
- 符合项目现有 PR 风格
- 统一语言 (日语/英语)
- 继承标签规则
故障排除
常见问题
- 找不到 PR: 检查分支名与 PR 的关联
- 权限错误: 检查 GitHub CLI 的认证状态
- 无法设置标签: 检查仓库权限
- HTML 注释被转义: GitHub CLI 的规格导致
<!-- -->被转换为<!-- -->
GitHub CLI 的 HTML 注释转义问题
重要: GitHub CLI (gh pr edit) 会自动转义 HTML 注释。此外,Shell 的重定向处理可能混入 EOF < /dev/null 等非法字符串。
根本解决方案
- 使用 GitHub API 的 --field 选项: 使用
--field进行适当的转义处理 - 简化 Shell 处理: 避免复杂的重定向和管道处理
- 简化模板处理: 废除 HTML 注释移除处理,完全保留
- 正确处理 JSON 转义: 正确处理特殊字符
调试选项
# 输出详细日志 (实现时添加)
/pr-auto-update --verbose