forked from Gitlink/gitlink-cli
9.3 KiB
9.3 KiB
示例:PR 长期未活动催办工作流
本示例演示对长期未活动的 PR 执行催办流程。 ⚠️ 与 Issue 不同,PR 端点暂不支持 label 操作,因此只评论催办,不打 stale 标签。
场景
- 仓库:
Gitlink/forgeplus - 目标:识别 60+ 天未活动的 open PR,评论催办;74+ 天的关闭
- 执行者:Claude Code + 用户(人在环路)
Step 0 — 准备环境
cd D:\code\SE\Evolution_and_Maintenance_of_SE\Mission2\gitlink-cli
.\gitlink-cli.exe version
.\gitlink-cli.exe auth status
Step 1 — 拉取 PR 列表
1.1 获取所有 open PR
.\gitlink-cli.exe pr +list `
--owner Gitlink `
--repo forgeplus `
--state open `
--format json | Out-File -Encoding utf8 "$env:TEMP\prs-open.json"
1.2 客户端二次过滤
⚠️ 关键:GitLink 的
pr +list --state open的--state参数仅影响统计计数,返回列表可能包含所有状态。必须按pull_request_status == 0二次过滤。
$raw = Get-Content "$env:TEMP\prs-open.json" -Raw | ConvertFrom-Json
# 二次过滤:仅保留真正 open 的 PR
$openPRs = $raw.data.pull_requests | Where-Object { $_.pull_request_status -eq 0 }
# 时间过滤:60+ 天未活动
$threshold = (Get-Date).AddDays(-60)
$stalePRs = $openPRs | Where-Object {
$updated = if ($_.updated_at) { [DateTime]::Parse($_.updated_at) } else { [DateTime]::Parse($_.created_at) }
$updated -lt $threshold
}
Write-Host "Total open PRs: $($openPRs.Count)"
Write-Host "Stale candidates (60+ days): $($stalePRs.Count)"
示例输出:
Total open PRs: 12
Stale candidates (60+ days): 4
Step 2 — 逐个详情分析
对每个候选 PR:
$results = @()
foreach ($pr in $stalePRs) {
# 拉取 PR 详情
$detail = (& .\gitlink-cli.exe pr +view `
--owner Gitlink --repo forgeplus `
--id $pr.pull_request_number `
--format json) | ConvertFrom-Json
# 计算天数
$lastActivity = if ($detail.data.updated_at) {
[DateTime]::Parse($detail.data.updated_at)
} else {
[DateTime]::Parse($detail.data.created_at)
}
$days = [int]((Get-Date) - $lastActivity).TotalDays
# AI 判断(PR 特化规则)
$analysis = ai_judge_pr_stale $detail
$results += [PSCustomObject]@{
Id = $pr.pull_request_number
Title = $pr.title
Author = $pr.user.login
Days = $days
Confidence = $analysis.confidence
Action = if ($days -ge 74) { "auto_close" } else { "mark_stale" }
Reason = $analysis.reason
}
}
$results | Format-Table
AI 判断 PR 的特殊规则
PR 与 Issue 的差异:
| 维度 | Issue | PR |
|---|---|---|
| 标签 | 支持豁免标签 | ❌ 暂不支持 |
| 评论催办 | mark_stale + 评论 | 仅评论 |
| 自动关闭 | issue +close | pr +close |
| 合并状态 | N/A | 已 merged 的不算 stale |
def ai_judge_pr_stale(pr_detail):
"""
PR 特化判断
"""
# 已 merged 或已 closed 的不算(理论上已被过滤)
if pr_detail.pull_request_status != 0:
return {"truly_stale": False, "exempt": True, "reason": "已 merged/closed"}
# 是否有冲突?
if pr_detail.conflict:
return {
"truly_stale": True,
"confidence": 0.9,
"reason": "存在冲突,可能需要 rebase"
}
# 是否等待 review?
if pr_detail.reviewers and not pr_detail.approved:
return {
"truly_stale": True,
"confidence": 0.75,
"reason": "等待 reviewer 回应"
}
# 作者活跃度
if pr_detail.user.login in repo_contributors:
return {
"truly_stale": True,
"confidence": 0.65,
"reason": "贡献者提交后未跟进"
}
return {
"truly_stale": True,
"confidence": 0.8,
"reason": "默认判断"
}
Step 3 — 展示报告
Claude Code 输出:
发现 4 个 60+ 天未活动的 PR:
┌──────┬────────────────────────────┬──────────┬────────┬─────────────┬────────────┐
│ # │ 标题 │ 作者 │ 天数 │ 置信度 │ 动作 │
├──────┼────────────────────────────┼──────────┼────────┼─────────────┼────────────┤
│ 8 │ feat: 新增搜索功能 │ contrib-a│ 68 │ 0.85 │ mark_stale │
│ 12 │ fix: 修复登录 bug │ newbie │ 92 │ 0.92 │ auto_close │
│ 15 │ docs: 更新 README │ user-1 │ 65 │ 0.70 │ mark_stale │
│ 21 │ refactor: 重构 API │ contrib-b│ 78 │ 0.88 │ auto_close │
└──────┴────────────────────────────┴──────────┴────────┴─────────────┴────────────┘
⚠️ 注意:PR 暂不支持 label 操作,将仅评论催办。
是否应用?[yes / 选择性 / 取消]
Step 4 — 应用动作(用户确认后)
4.1 备份
$ts = Get-Date -Format "yyyyMMddHHmmss"
.\gitlink-cli.exe pr +list `
--owner Gitlink --repo forgeplus `
--state open --format json |
Out-File -Encoding utf8 "$env:TEMP\before-pr-stale-$ts.json"
4.2 批量应用
$OWNER = "Gitlink"
$REPO = "forgeplus"
$report = Get-Content "$env:TEMP\pr-stale-report.json" -Raw | ConvertFrom-Json
$toApply = $report.items | Where-Object {
$_.recommended_action -in @("mark_stale", "auto_close") -and
$_.ai_analysis.confidence -ge 0.6
}
foreach ($item in $toApply) {
$id = $item.number
$action = $item.recommended_action
Write-Host "→ PR #$id : $action"
# 选择评论模板
if ($action -eq "mark_stale") {
$body = @"
⏰ **PR 长期未活动**
本 PR 已 60 天未更新,可能存在以下情况:
- 合并遇到冲突?请 rebase 后重新推送
- 等待 review?可 @mention 相关维护者
- 不再需要?欢迎手动关闭
如果 **14 天内**没有新活动,将默认关闭。
> 🤖 由 gitlink-stale skill 自动生成。
"@
} else {
$body = @"
🔒 **PR 自动关闭(长期未活动)**
本 PR 已 74 天无活动,自动关闭。
- 如仍需合并,请 rebase 后重新打开
- 如有冲突,可重新发起 PR
> 🤖 由 gitlink-stale skill 自动关闭。
"@
}
# 1. 评论催办
& .\gitlink-cli.exe pr +comment `
--owner $OWNER --repo $REPO `
--id $id --body $body 2>&1 | Out-Null
# 2. 若 auto_close,关闭 PR
if ($action -eq "auto_close") {
& .\gitlink-cli.exe pr +close `
--owner $OWNER --repo $REPO `
--id $id 2>&1 | Out-Null
}
Start-Sleep -Milliseconds 500
}
Write-Host "✓ Batch applied"
Step 5 — 验证
# 检查 PR #8 是否已评论
.\gitlink-cli.exe pr +view `
--owner Gitlink --repo forgeplus `
--id 8 --format json |
ConvertFrom-Json |
Select-Object -ExpandProperty data |
Select-Object title, @{N="status";E={$_.pull_request_status}}, @{N="journals_count";E={$_.journals.Count}}
故障恢复
误关闭的 PR 恢复
# 重新打开 PR(Raw API)
# 注意:GitLink PR 端点重新打开的 API 可能不完善
# 推荐做法:让作者重新发起 PR
评论失败
# 现象:pr +comment 返回 404
# 原因:--id 用了内部 id 而非 pull_request_number
# 处理:确认 id 是网页 URL 中的 pull_request_number
与 Issue 处理的差异
| 维度 | Issue | PR |
|---|---|---|
| 标签 | 支持 stale/pinned 等 | ❌ 不支持 |
| 评论催办 | ✅ | ✅ |
| 自动关闭 | issue +close --number N |
pr +close --id N |
| 客户端过滤 | 直接看 status_id | 必须看 pull_request_status(state 参数不可靠) |
| 重开 | PATCH status_id=1 | API 可能不完善 |
💡 核心差异:PR 没有 label 维度,所有"stale 状态"必须通过评论标题或正文中的 ⏰/🔒 emoji 表达。
关键检查点
- ✅ Step 1 完成后,按
pull_request_status == 0二次过滤 - ✅ Step 2 PR 详情中检查是否有冲突
- ✅ Step 3 展示时明确告知用户"PR 不打标签,仅评论"
- ✅ Step 4 应用前备份 PR 列表
性能数据
| 阶段 | API 调用次数 | 耗时 |
|---|---|---|
| Step 1 列表 | 1 | 3s |
| Step 2 详情 | N × 1 | 8s |
| Step 4 评论+关闭 | N × 2 | 6s |
| 总计(4 个 PR) | 13 | ~20s |
总结
PR stale 处理的核心要点:
- ✅ 必过滤
pull_request_status—--state参数不可靠 - ✅ 仅评论催办 — 不打标签
- ✅ AI 判断考虑 PR 特性 — 冲突、reviewer、合并状态
- ✅ 关闭操作可逆性差 — 建议作者重新发起而非自动重开