Compare commits
1 Commits
master
...
feat/issue
| Author | SHA1 | Date |
|---|---|---|
|
|
2aaa7e55fd |
12
README.md
12
README.md
|
|
@ -165,10 +165,16 @@ gitlink-cli issue +list --owner Gitlink --repo forgeplus
|
||||||
gitlink-cli issue +create --owner Gitlink --repo forgeplus -t "Bug: Login failed" -b "Steps to reproduce..."
|
gitlink-cli issue +create --owner Gitlink --repo forgeplus -t "Bug: Login failed" -b "Steps to reproduce..."
|
||||||
|
|
||||||
# View an issue
|
# View an issue
|
||||||
gitlink-cli issue +view --owner Gitlink --repo forgeplus -i 123
|
gitlink-cli issue +view --owner Gitlink --repo forgeplus -n 123
|
||||||
|
|
||||||
# Close an issue
|
# Close an issue
|
||||||
gitlink-cli issue +close --owner Gitlink --repo forgeplus -i 123
|
gitlink-cli issue +close --owner Gitlink --repo forgeplus -n 123
|
||||||
|
|
||||||
|
# Preview batch comment without changing data
|
||||||
|
gitlink-cli issue +batch-comment --owner Gitlink --repo forgeplus --numbers 123,124 -b "Please confirm whether this issue is still relevant." --dry-run
|
||||||
|
|
||||||
|
# Batch add a comment before closing stale issues
|
||||||
|
gitlink-cli issue +batch-comment --owner Gitlink --repo forgeplus --from issues.csv -b "This issue is inactive. Please reply if it is still relevant."
|
||||||
|
|
||||||
# Preview batch close without changing data
|
# Preview batch close without changing data
|
||||||
gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --numbers 123,124 --dry-run
|
gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --numbers 123,124 --dry-run
|
||||||
|
|
@ -177,7 +183,7 @@ gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --numbers 123,12
|
||||||
gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --from issues.csv
|
gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --from issues.csv
|
||||||
|
|
||||||
# Add a comment
|
# Add a comment
|
||||||
gitlink-cli issue +comment --owner Gitlink --repo forgeplus -i 123 -b "Fixed"
|
gitlink-cli issue +comment --owner Gitlink --repo forgeplus -n 123 -b "Fixed"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Pull Requests
|
### Pull Requests
|
||||||
|
|
|
||||||
|
|
@ -165,10 +165,16 @@ gitlink-cli issue +list --owner Gitlink --repo forgeplus
|
||||||
gitlink-cli issue +create --owner Gitlink --repo forgeplus -t "Bug: 登录失败" -b "复现步骤..."
|
gitlink-cli issue +create --owner Gitlink --repo forgeplus -t "Bug: 登录失败" -b "复现步骤..."
|
||||||
|
|
||||||
# 查看 Issue
|
# 查看 Issue
|
||||||
gitlink-cli issue +view --owner Gitlink --repo forgeplus -i 123
|
gitlink-cli issue +view --owner Gitlink --repo forgeplus -n 123
|
||||||
|
|
||||||
# 关闭 Issue
|
# 关闭 Issue
|
||||||
gitlink-cli issue +close --owner Gitlink --repo forgeplus -i 123
|
gitlink-cli issue +close --owner Gitlink --repo forgeplus -n 123
|
||||||
|
|
||||||
|
# 预览批量评论,不修改数据
|
||||||
|
gitlink-cli issue +batch-comment --owner Gitlink --repo forgeplus --numbers 123,124 -b "请确认该 Issue 是否仍需处理。" --dry-run
|
||||||
|
|
||||||
|
# 从 CSV 文件批量添加评论
|
||||||
|
gitlink-cli issue +batch-comment --owner Gitlink --repo forgeplus --from issues.csv -b "该 Issue 长期无更新,如仍需处理请回复。"
|
||||||
|
|
||||||
# 预览批量关闭,不修改数据
|
# 预览批量关闭,不修改数据
|
||||||
gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --numbers 123,124 --dry-run
|
gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --numbers 123,124 --dry-run
|
||||||
|
|
@ -177,7 +183,7 @@ gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --numbers 123,12
|
||||||
gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --from issues.csv
|
gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --from issues.csv
|
||||||
|
|
||||||
# 添加评论
|
# 添加评论
|
||||||
gitlink-cli issue +comment --owner Gitlink --repo forgeplus -i 123 -b "已修复"
|
gitlink-cli issue +comment --owner Gitlink --repo forgeplus -n 123 -b "已修复"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Pull Request
|
### Pull Request
|
||||||
|
|
|
||||||
|
|
@ -12,20 +12,20 @@ import (
|
||||||
|
|
||||||
const closedIssueStatusID = 5
|
const closedIssueStatusID = 5
|
||||||
|
|
||||||
type batchCloseResult struct {
|
type batchOperationResult struct {
|
||||||
Number string `json:"number" yaml:"number"`
|
Number string `json:"number" yaml:"number"`
|
||||||
Action string `json:"action" yaml:"action"`
|
Action string `json:"action" yaml:"action"`
|
||||||
Status string `json:"status" yaml:"status"`
|
Status string `json:"status" yaml:"status"`
|
||||||
Error string `json:"error,omitempty" yaml:"error,omitempty"`
|
Error string `json:"error,omitempty" yaml:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type batchCloseSummary struct {
|
type batchOperationSummary struct {
|
||||||
Repository string `json:"repository" yaml:"repository"`
|
Repository string `json:"repository" yaml:"repository"`
|
||||||
DryRun bool `json:"dry_run" yaml:"dry_run"`
|
DryRun bool `json:"dry_run" yaml:"dry_run"`
|
||||||
Total int `json:"total" yaml:"total"`
|
Total int `json:"total" yaml:"total"`
|
||||||
Succeeded int `json:"succeeded" yaml:"succeeded"`
|
Succeeded int `json:"succeeded" yaml:"succeeded"`
|
||||||
Failed int `json:"failed" yaml:"failed"`
|
Failed int `json:"failed" yaml:"failed"`
|
||||||
Results []batchCloseResult `json:"results" yaml:"results"`
|
Results []batchOperationResult `json:"results" yaml:"results"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBatchCloseShortcut() *common.Shortcut {
|
func newBatchCloseShortcut() *common.Shortcut {
|
||||||
|
|
@ -55,15 +55,15 @@ func runBatchClose(ctx *common.RuntimeContext) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
dryRun := parseBool(ctx.Arg("dry-run"))
|
dryRun := parseBool(ctx.Arg("dry-run"))
|
||||||
summary := batchCloseSummary{
|
summary := batchOperationSummary{
|
||||||
Repository: fmt.Sprintf("%s/%s", ctx.Owner, ctx.Repo),
|
Repository: fmt.Sprintf("%s/%s", ctx.Owner, ctx.Repo),
|
||||||
DryRun: dryRun,
|
DryRun: dryRun,
|
||||||
Total: len(numbers),
|
Total: len(numbers),
|
||||||
Results: make([]batchCloseResult, 0, len(numbers)),
|
Results: make([]batchOperationResult, 0, len(numbers)),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, number := range numbers {
|
for _, number := range numbers {
|
||||||
result := batchCloseResult{Number: number, Action: "close"}
|
result := batchOperationResult{Number: number, Action: "close"}
|
||||||
if dryRun {
|
if dryRun {
|
||||||
result.Status = "planned"
|
result.Status = "planned"
|
||||||
summary.Succeeded++
|
summary.Succeeded++
|
||||||
|
|
@ -108,6 +108,85 @@ func closeIssue(ctx *common.RuntimeContext, number string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newBatchCommentShortcut() *common.Shortcut {
|
||||||
|
return &common.Shortcut{
|
||||||
|
Name: "batch-comment",
|
||||||
|
Description: "Add a comment to multiple issues by issue numbers or a CSV file",
|
||||||
|
Flags: []common.Flag{
|
||||||
|
{Name: "numbers", Short: "n", Usage: "Comma-separated issue numbers from the web URL, for example: 1,2,3"},
|
||||||
|
{Name: "from", Usage: "Read issue numbers from a CSV file. Supports a number/issue_number/project_issues_index column or first column without header"},
|
||||||
|
{Name: "body", Short: "b", Usage: "Comment body", Required: true},
|
||||||
|
{Name: "dry-run", Usage: "Preview the issues that would be commented without changing them", Bool: true, Default: "false"},
|
||||||
|
},
|
||||||
|
Run: runBatchComment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runBatchComment(ctx *common.RuntimeContext) error {
|
||||||
|
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ctx.RequireArg("body")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
numbers, err := collectIssueNumbers(ctx.Arg("numbers"), ctx.Arg("from"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(numbers) == 0 {
|
||||||
|
return fmt.Errorf("no issue numbers provided; use --numbers 1,2,3 or --from issues.csv")
|
||||||
|
}
|
||||||
|
|
||||||
|
dryRun := parseBool(ctx.Arg("dry-run"))
|
||||||
|
summary := batchOperationSummary{
|
||||||
|
Repository: fmt.Sprintf("%s/%s", ctx.Owner, ctx.Repo),
|
||||||
|
DryRun: dryRun,
|
||||||
|
Total: len(numbers),
|
||||||
|
Results: make([]batchOperationResult, 0, len(numbers)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, number := range numbers {
|
||||||
|
result := batchOperationResult{Number: number, Action: "comment"}
|
||||||
|
if dryRun {
|
||||||
|
result.Status = "planned"
|
||||||
|
summary.Succeeded++
|
||||||
|
summary.Results = append(summary.Results, result)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := commentIssue(ctx, number, body); err != nil {
|
||||||
|
result.Status = "failed"
|
||||||
|
result.Error = err.Error()
|
||||||
|
summary.Failed++
|
||||||
|
} else {
|
||||||
|
result.Status = "commented"
|
||||||
|
summary.Succeeded++
|
||||||
|
}
|
||||||
|
summary.Results = append(summary.Results, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.OutputData(summary); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if summary.Failed > 0 {
|
||||||
|
return fmt.Errorf("%d of %d issue(s) failed to comment", summary.Failed, summary.Total)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func commentIssue(ctx *common.RuntimeContext, number, body string) error {
|
||||||
|
payload := map[string]interface{}{
|
||||||
|
"notes": body,
|
||||||
|
}
|
||||||
|
if _, err := ctx.CallAPI("POST", fmt.Sprintf("%s/issues/%s/journals", v1RepoPath(ctx), number), payload); err != nil {
|
||||||
|
return fmt.Errorf("comment issue: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func collectIssueNumbers(numbersValue, csvPath string) ([]string, error) {
|
func collectIssueNumbers(numbersValue, csvPath string) ([]string, error) {
|
||||||
numbers, err := parseIssueNumbers(numbersValue)
|
numbers, err := parseIssueNumbers(numbersValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ type existingIssue struct {
|
||||||
func Shortcuts() []*common.Shortcut {
|
func Shortcuts() []*common.Shortcut {
|
||||||
return []*common.Shortcut{
|
return []*common.Shortcut{
|
||||||
newBatchCloseShortcut(),
|
newBatchCloseShortcut(),
|
||||||
|
newBatchCommentShortcut(),
|
||||||
{
|
{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Description: "List issues",
|
Description: "List issues",
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,64 @@ func TestBatchClosePreservesCurrentDescription(t *testing.T) {
|
||||||
assertEqual(t, updatePayload["status_id"], float64(5))
|
assertEqual(t, updatePayload["status_id"], float64(5))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBatchCommentPostsNotesToEachIssue(t *testing.T) {
|
||||||
|
posted := map[string]string{}
|
||||||
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch {
|
||||||
|
case r.Method == "POST" && r.URL.Path == "/v1/owner/repo/issues/42/journals.json":
|
||||||
|
posted["42"] = decodeJSON(t, r)["notes"].(string)
|
||||||
|
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "success"})
|
||||||
|
case r.Method == "POST" && r.URL.Path == "/v1/owner/repo/issues/43/journals.json":
|
||||||
|
posted["43"] = decodeJSON(t, r)["notes"].(string)
|
||||||
|
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "success"})
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
err := runIssueShortcut(t, server, "batch-comment", map[string]string{
|
||||||
|
"numbers": "42,43",
|
||||||
|
"body": "stale issue reminder",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("batch-comment shortcut failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEqual(t, posted["42"], "stale issue reminder")
|
||||||
|
assertEqual(t, posted["43"], "stale issue reminder")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBatchCommentDryRunDoesNotPost(t *testing.T) {
|
||||||
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
t.Fatalf("dry-run should not call API, got: %s %s", r.Method, r.URL.Path)
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
err := runIssueShortcut(t, server, "batch-comment", map[string]string{
|
||||||
|
"numbers": "42,43",
|
||||||
|
"body": "stale issue reminder",
|
||||||
|
"dry-run": "true",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("batch-comment dry-run failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBatchCommentRequiresBody(t *testing.T) {
|
||||||
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
t.Fatalf("missing body should not call API, got: %s %s", r.Method, r.URL.Path)
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
err := runIssueShortcut(t, server, "batch-comment", map[string]string{
|
||||||
|
"numbers": "42",
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("batch-comment without body expected an error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func runIssueShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error {
|
func runIssueShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
shortcut := findIssueShortcut(t, name)
|
shortcut := findIssueShortcut(t, name)
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ skills/
|
||||||
|-------|------|----------|
|
|-------|------|----------|
|
||||||
| **gitlink-shared** | 认证、全局参数、API 参考、安全规则、分支约定 | `auth login`, `auth status` |
|
| **gitlink-shared** | 认证、全局参数、API 参考、安全规则、分支约定 | `auth login`, `auth status` |
|
||||||
| **gitlink-repo** | 仓库管理 | `repo +list`, `repo +create`, `repo +info`, `repo +fork` |
|
| **gitlink-repo** | 仓库管理 | `repo +list`, `repo +create`, `repo +info`, `repo +fork` |
|
||||||
| **gitlink-issue** | Issue 管理 | `issue +create`, `issue +list`, `issue +view`, `issue +close`, `issue +batch-close` |
|
| **gitlink-issue** | Issue 管理 | `issue +create`, `issue +list`, `issue +view`, `issue +close`, `issue +batch-comment`, `issue +batch-close` |
|
||||||
| **gitlink-pr** | Pull Request | `pr +list`, `pr +create`, `pr +view`, `pr +merge`, `pr +review` |
|
| **gitlink-pr** | Pull Request | `pr +list`, `pr +create`, `pr +view`, `pr +merge`, `pr +review` |
|
||||||
| **gitlink-branch** | 分支管理 | `branch +list`, `branch +create`, `branch +protect` |
|
| **gitlink-branch** | 分支管理 | `branch +list`, `branch +create`, `branch +protect` |
|
||||||
| **gitlink-release** | 版本发布 | `release +list`, `release +create`, `release +view` |
|
| **gitlink-release** | 版本发布 | `release +list`, `release +create`, `release +view` |
|
||||||
|
|
@ -162,13 +162,16 @@ gitlink-cli repo +info --owner wbtiger --repo gitlink-cli
|
||||||
gitlink-cli issue +create -t "Bug: 登录失败" -b "复现步骤..."
|
gitlink-cli issue +create -t "Bug: 登录失败" -b "复现步骤..."
|
||||||
|
|
||||||
# 查看 Issue
|
# 查看 Issue
|
||||||
gitlink-cli issue +view -i 123
|
gitlink-cli issue +view -n 123
|
||||||
|
|
||||||
# 添加评论
|
# 添加评论
|
||||||
gitlink-cli issue +comment -i 123 -b "已修复"
|
gitlink-cli issue +comment -n 123 -b "已修复"
|
||||||
|
|
||||||
# 关闭 Issue
|
# 关闭 Issue
|
||||||
gitlink-cli issue +close -i 123
|
gitlink-cli issue +close -n 123
|
||||||
|
|
||||||
|
# 预览批量评论 Issue
|
||||||
|
gitlink-cli issue +batch-comment --numbers 123,124 -b "请确认该 Issue 是否仍需处理。" --dry-run
|
||||||
|
|
||||||
# 预览批量关闭 Issue
|
# 预览批量关闭 Issue
|
||||||
gitlink-cli issue +batch-close --numbers 123,124 --dry-run
|
gitlink-cli issue +batch-close --numbers 123,124 --dry-run
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
name: gitlink-issue
|
name: gitlink-issue
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
description: "Issue 管理:创建、查看、更新、关闭/批量关闭 Issue,添加评论。当用户需要操作 GitLink Issue 时触发。"
|
description: "Issue 管理:创建、查看、更新、评论/批量评论、关闭/批量关闭 Issue。当用户需要操作 GitLink Issue 时触发。"
|
||||||
metadata:
|
metadata:
|
||||||
requires:
|
requires:
|
||||||
bins: ["gitlink-cli"]
|
bins: ["gitlink-cli"]
|
||||||
|
|
@ -25,8 +25,9 @@ metadata:
|
||||||
| `issue +view` | Issue 详情 | 否(公开项目) |
|
| `issue +view` | Issue 详情 | 否(公开项目) |
|
||||||
| `issue +update` | 更新 Issue | 是 |
|
| `issue +update` | 更新 Issue | 是 |
|
||||||
| `issue +close` | 关闭 Issue | 是 |
|
| `issue +close` | 关闭 Issue | 是 |
|
||||||
| `issue +batch-close` | 批量关闭 Issue,支持 `--dry-run` 预览 | 是(dry-run 不写入) |
|
|
||||||
| `issue +comment` | 添加评论 | 是 |
|
| `issue +comment` | 添加评论 | 是 |
|
||||||
|
| `issue +batch-comment` | 批量添加评论,支持 `--dry-run` 预览 | 是(dry-run 不写入) |
|
||||||
|
| `issue +batch-close` | 批量关闭 Issue,支持 `--dry-run` 预览 | 是(dry-run 不写入) |
|
||||||
|
|
||||||
## 使用示例
|
## 使用示例
|
||||||
|
|
||||||
|
|
@ -46,6 +47,12 @@ gitlink-cli issue +update --number 4 --title "新标题" --body "更新描述"
|
||||||
# 关闭 Issue
|
# 关闭 Issue
|
||||||
gitlink-cli issue +close --number 4
|
gitlink-cli issue +close --number 4
|
||||||
|
|
||||||
|
# 预览批量评论 Issue,不修改数据
|
||||||
|
gitlink-cli issue +batch-comment --owner myuser --repo myrepo --numbers 123,124 --body "请确认该 Issue 是否仍需处理。" --dry-run
|
||||||
|
|
||||||
|
# 从 CSV 文件批量添加评论
|
||||||
|
gitlink-cli issue +batch-comment --owner myuser --repo myrepo --from issues.csv --body "该 Issue 长期无更新,如仍需处理请回复。"
|
||||||
|
|
||||||
# 预览批量关闭 Issue,不修改数据
|
# 预览批量关闭 Issue,不修改数据
|
||||||
gitlink-cli issue +batch-close --owner myuser --repo myrepo --numbers 123,124 --dry-run
|
gitlink-cli issue +batch-close --owner myuser --repo myrepo --numbers 123,124 --dry-run
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
# issue +batch-comment
|
||||||
|
|
||||||
|
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||||
|
|
||||||
|
批量给多个 Issue 添加评论。支持直接传入 Issue 编号列表、从 CSV 读取 Issue 编号,以及用 `--dry-run` 安全预览。
|
||||||
|
|
||||||
|
> **Issue 编号说明:** `--numbers` 使用的是网页 URL 中可见的 Issue 编号,即 v1 API 的 `project_issues_index`,不是数据库内部 ID。
|
||||||
|
|
||||||
|
## 命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 预览,不修改数据
|
||||||
|
gitlink-cli issue +batch-comment --owner Gitlink --repo forgeplus --numbers 42,43 --body "请确认该 Issue 是否仍需处理。" --dry-run
|
||||||
|
|
||||||
|
# 按 Issue 编号批量添加评论
|
||||||
|
gitlink-cli issue +batch-comment --owner Gitlink --repo forgeplus --numbers 42,43 --body "该 Issue 长期无更新,如仍需处理请回复。"
|
||||||
|
|
||||||
|
# 从 CSV 文件读取 Issue 编号
|
||||||
|
gitlink-cli issue +batch-comment --owner Gitlink --repo forgeplus --from issues.csv --body "该 Issue 长期无更新,如仍需处理请回复。"
|
||||||
|
```
|
||||||
|
|
||||||
|
## CSV 格式
|
||||||
|
|
||||||
|
CSV 文件可以包含 `number`、`issue_number` 或 `project_issues_index` 列:
|
||||||
|
|
||||||
|
```csv
|
||||||
|
number,title
|
||||||
|
42,stale issue
|
||||||
|
43,duplicate issue
|
||||||
|
```
|
||||||
|
|
||||||
|
如果没有表头,则默认第一列是 Issue 编号:
|
||||||
|
|
||||||
|
```csv
|
||||||
|
42,stale issue
|
||||||
|
43,duplicate issue
|
||||||
|
```
|
||||||
|
|
||||||
|
## 参数
|
||||||
|
|
||||||
|
| 参数 | 必填 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `--numbers, -n` | 否 | 逗号分隔的 Issue 编号,例如 `1,2,3` |
|
||||||
|
| `--from` | 否 | 包含 Issue 编号的 CSV 文件 |
|
||||||
|
| `--body, -b` | 是 | 评论内容 |
|
||||||
|
| `--dry-run` | 否 | 仅预览计划操作,不添加评论 |
|
||||||
|
| `--owner` | 否 | 仓库所有者(自动从 git remote 解析) |
|
||||||
|
| `--repo` | 否 | 仓库名称(自动从 git remote 解析) |
|
||||||
|
| `--format` | 否 | 输出格式: `json`/`table`/`yaml` |
|
||||||
|
| `--debug` | 否 | 开启调试输出 |
|
||||||
|
|
||||||
|
`--numbers` 和 `--from` 至少提供一个。两者同时提供时,会按顺序合并并去重。
|
||||||
|
|
||||||
|
## 输出
|
||||||
|
|
||||||
|
命令会输出批量操作汇总:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"repository": "Gitlink/forgeplus",
|
||||||
|
"dry_run": true,
|
||||||
|
"total": 2,
|
||||||
|
"succeeded": 2,
|
||||||
|
"failed": 0,
|
||||||
|
"results": [
|
||||||
|
{"number": "42", "action": "comment", "status": "planned"},
|
||||||
|
{"number": "43", "action": "comment", "status": "planned"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
每个 Issue 使用 v1 评论接口:
|
||||||
|
|
||||||
|
```text
|
||||||
|
POST /v1/{owner}/{repo}/issues/{number}/journals
|
||||||
|
Body: { "notes": <comment body> }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. 与用户确认目标仓库、要评论的 Issue 编号和评论内容。
|
||||||
|
2. 先执行 `--dry-run` 并展示计划结果。
|
||||||
|
3. 用户确认后,再执行不带 `--dry-run` 的命令。
|
||||||
|
4. 汇报成功数量、失败数量和失败原因。
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> 不带 `--dry-run` 是 **写操作**,执行前必须确认用户意图。
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [gitlink-issue](../SKILL.md)
|
||||||
|
- [gitlink-shared](../../gitlink-shared/SKILL.md)
|
||||||
Loading…
Reference in New Issue