461 lines
11 KiB
Markdown
461 lines
11 KiB
Markdown
## PR 自動更新
|
||
|
||
## 概述
|
||
|
||
自動更新 Pull Request 描述和標簽的命令。通過分析 Git 更改內容,生成並設置適当的描述文本和標簽。
|
||
|
||
## 使用方法
|
||
|
||
```bash
|
||
/pr-auto-update [選項] [PR 編号]
|
||
```
|
||
|
||
### 選項
|
||
|
||
- `--pr <編号>` : 指定目標 PR 編号 (省略時從當前分支自動檢測)
|
||
- `--description-only` : 仅更新描述 (不修改標簽)
|
||
- `--labels-only` : 仅更新標簽 (不修改描述)
|
||
- `--dry-run` : 不執行實際更新,仅顯示生成的內容
|
||
- `--lang <語言>` : 指定語言 (zh-tw, en)
|
||
|
||
### 基本示例
|
||
|
||
```bash
|
||
# 自動更新當前分支的 PR
|
||
/pr-auto-update
|
||
|
||
# 更新特定的 PR
|
||
/pr-auto-update --pr 1234
|
||
|
||
# 仅更新描述
|
||
/pr-auto-update --description-only
|
||
|
||
# 預演模式確認
|
||
/pr-auto-update --dry-run
|
||
```
|
||
|
||
## 功能詳情
|
||
|
||
### 1. PR 自動檢測
|
||
|
||
從當前分支自動檢測對應的 PR:
|
||
|
||
```bash
|
||
# 從分支搜索 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. 描述文本自動生成
|
||
|
||
#### 模板處理優先級
|
||
|
||
1. **現有 PR 描述**: **完全遵循**已存在的內容
|
||
2. **項目模板**: 從 `.github/PULL_REQUEST_TEMPLATE.md` 獲取結構
|
||
3. **默認模板**: 上述不存在時的後備方案
|
||
|
||
#### 現有內容保留規則
|
||
|
||
**重要**: 不修改現有內容
|
||
|
||
- 保留已寫的部分
|
||
- 仅補充空白部分
|
||
- 保留功能性注釋 (如 Copilot review rule 等)
|
||
|
||
#### 項目模板的使用
|
||
|
||
```bash
|
||
# 解析 .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. 標簽自動設置
|
||
|
||
#### 標簽獲取機制
|
||
|
||
**優先級**:
|
||
|
||
1. **`.github/labels.yml`**: 從項目特定的標簽定義獲取
|
||
2. **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` 時**:
|
||
|
||
```bash
|
||
# 從標簽定義自動獲取
|
||
grep "^- name:" .github/labels.yml | sed "s/^- name: '\\?\\([^']*\\)'\\?/\\1/"
|
||
|
||
# 例:使用項目特定的標簽體系
|
||
```
|
||
|
||
**從 GitHub API 獲取時**:
|
||
|
||
```bash
|
||
# 獲取現有標簽列表
|
||
gh api repos/{OWNER}/{REPO}/labels --jq '.[].name'
|
||
|
||
# 例:使用 bug, enhancement, documentation 等標準標簽
|
||
```
|
||
|
||
### 5. 執行流程
|
||
|
||
```bash
|
||
#!/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`:
|
||
|
||
```json
|
||
{
|
||
"language": "zh-tw",
|
||
"max_labels": 3
|
||
}
|
||
```
|
||
|
||
## 常見模式
|
||
|
||
### Flutter 項目
|
||
|
||
```markdown
|
||
## What does this change?
|
||
|
||
實現了{功能名}。解決了用戶的{問題}。
|
||
|
||
### 主要更改內容
|
||
|
||
- **UI 實現**: 新建{画面名}
|
||
- **狀態管理**: 添加 Riverpod Provider
|
||
- **API 集成**: 實現 GraphQL 查询與變更
|
||
- **測試**: 添加 Widget 測試和單元測試
|
||
|
||
### 技術規格
|
||
|
||
- **架構**: {使用的模式}
|
||
- **依賴**: {新增的包}
|
||
- **性能**: {優化內容}
|
||
```
|
||
|
||
### Node.js 項目
|
||
|
||
```markdown
|
||
## What does this change?
|
||
|
||
實現了{API 名}端點。支持{用例}。
|
||
|
||
### 主要更改內容
|
||
|
||
- **API 實現**: 新建{端點}
|
||
- **驗證**: 添加請求驗證邏輯
|
||
- **數據庫**: 實現對{表名}的操作
|
||
- **測試**: 添加集成測試和單元測試
|
||
|
||
### 安全性
|
||
|
||
- **認證**: JWT 令牌驗證
|
||
- **授權**: 基于角色的訪問控制
|
||
- **輸入驗證**: SQL 注入防護
|
||
```
|
||
|
||
### CI/CD 改進
|
||
|
||
```markdown
|
||
## What does this change?
|
||
|
||
改進了 GitHub Actions 工作流。實現了{效果}。
|
||
|
||
### 改進內容
|
||
|
||
- **性能**: 構建時間减少{時間}
|
||
- **可靠性**: 增強錯誤處理
|
||
- **安全性**: 改進密鑰管理
|
||
|
||
### 技術细节
|
||
|
||
- **並行化**: {作業名}並行執行
|
||
- **緩存**: 優化{緩存對象}的緩存策略
|
||
- **監控**: 添加{指標}監控
|
||
```
|
||
|
||
## 注意事項
|
||
|
||
1. **完全保留現有內容**:
|
||
- **一字不改**現有描述內容
|
||
- 仅補充空白注釋和佔位符
|
||
- 尊重用戶有意編寫的內容
|
||
|
||
2. **模板優先級**:
|
||
- 現有 PR 描述 > `.github/PULL_REQUEST_TEMPLATE.md` > 默認
|
||
- 完全遵循項目特定的模板結構
|
||
|
||
3. **標簽約束**:
|
||
- 如存在 `.github/labels.yml` 則優先使用
|
||
- 不存在時從 GitHub API 獲取現有標簽
|
||
- 禁止創建新標簽
|
||
- 自動選擇最多 3 個
|
||
|
||
4. **安全更新**:
|
||
- 推薦使用 `--dry-run` 預先確認
|
||
- 包含敏感資訊的更改時顯示警告
|
||
- 保存原始描述作為備份
|
||
|
||
5. **保持一致性**:
|
||
- 符合項目現有 PR 風格
|
||
- 統一語言 (中文/英語)
|
||
- 繼承標簽規則
|
||
|
||
## 故障排除
|
||
|
||
### 常見問題
|
||
|
||
1. **找不到 PR**: 檢查分支名與 PR 的關聯
|
||
2. **權限錯誤**: 檢查 GitHub CLI 的認證狀態
|
||
3. **無法設置標簽**: 檢查倉庫權限
|
||
4. **HTML 注釋被轉義**: GitHub CLI 的規格導致 `<!-- -->` 被轉換為 `<!-- -->`
|
||
|
||
### GitHub CLI 的 HTML 注釋轉義問題
|
||
|
||
**重要**: GitHub CLI (`gh pr edit`) 會自動轉義 HTML 注釋。此外,Shell 的重定向處理可能混入 `EOF < /dev/null` 等非法字符串。
|
||
|
||
#### 根本解決方案
|
||
|
||
1. **使用 GitHub API 的 --field 選項**: 使用 `--field` 進行適当的轉義處理
|
||
2. **簡化 Shell 處理**: 避免復杂的重定向和管道處理
|
||
3. **簡化模板處理**: 废除 HTML 注釋移除處理,完全保留
|
||
4. **正確處理 JSON 轉義**: 正確處理特殊字符
|
||
|
||
### 調試選項
|
||
|
||
```bash
|
||
# 輸出詳细日誌 (實現時添加)
|
||
/pr-auto-update --verbose
|
||
```
|