diff --git a/README.md b/README.md index cb2663d..7f2536a 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,8 @@ The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans | Category | Capabilities | |----------|-------------| | 📦 Repo | List, create, fork, delete repositories, view repo info | -| 🐛 Issue | Create, update, close, batch close, comment on issues | +| 🐛 Issue | Create, update, close, comment on issues, 6 batch operations (close/status/priority/assignee/label/create) | +| 📖 Wiki | View, create, update, delete Wiki pages | | 🔀 PR | Create, merge, review pull requests, view changed files | | 🌿 Branch | Create, delete, list, protect, unprotect branches | | 🏷️ Release | Create, view, delete releases | @@ -235,6 +236,28 @@ gitlink-cli ci +log --owner Gitlink --repo forgeplus -i gitlink-cli ci +restart --owner Gitlink --repo forgeplus -i ``` +### Wiki Management + +```bash +# List wiki pages +gitlink-cli wiki +list --owner Gitlink --repo forgeplus + +# View a wiki page +gitlink-cli wiki +view --owner Gitlink --repo forgeplus --title "Getting Started" + +# Create a wiki page +gitlink-cli wiki +create --title "New Page" --content "Page content" + +# Update a wiki page (overwrite) +gitlink-cli wiki +update --title "New Page" --cover "Updated content" --owner Gitlink --repo forgeplus + +# Append content to a wiki page +gitlink-cli wiki +update --title "New Page" --add "Appended content" --owner Gitlink --repo forgeplus + +# Delete a wiki page +gitlink-cli wiki +delete --title "New Page" --owner Gitlink --repo forgeplus +``` + ### Search ```bash @@ -339,6 +362,7 @@ gitlink-cli/ │ ├── ci/ # CI shortcuts │ ├── search/ # Search shortcuts │ ├── user/ # User shortcuts +│ ├── wiki/ # Wiki shortcuts │ └── register.go # Registration entry point ├── skills/ # AI Agent Skills │ ├── README.md # Skills guide diff --git a/README.zh-CN.md b/README.zh-CN.md index 4531992..a301ac0 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -27,7 +27,8 @@ | 分类 | 能力 | |------|------| | 📦 仓库 | 列出、创建、Fork、删除仓库,查看仓库信息 | -| 🐛 Issue | 创建、更新、关闭、批量关闭、评论 Issue | +| 🐛 Issue | 创建、更新、关闭、评论 Issue,6 个批量操作(关闭/状态/优先级/负责人/标记/创建) | +| 📖 Wiki | 查看、创建、更新、删除 Wiki 页面 | | 🔀 PR | 创建、合并、Review Pull Request,查看变更文件 | | 🌿 分支 | 创建、删除、保护分支 | | 🏷️ 发布 | 创建、查看、删除 Release | @@ -153,6 +154,12 @@ gitlink-cli repo +create -n my-project -d "项目描述" # Fork 仓库 gitlink-cli repo +fork --owner Gitlink --repo forgeplus + +# 批量创建仓库(默认公开,--private设置为私有) +gitlink-cli repo +batch-create -n "repo1,repo2" -d "项目描述" + +# 批量更新仓库信息(--private设置为私有,--public设置为公开,注意要指定仓库所有者) +gitlink-cli repo +batch-update -n "repo1,repo2" -d "更新描述" ``` ### Issue 管理 @@ -178,6 +185,25 @@ gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --from issues.cs # 添加评论 gitlink-cli issue +comment --owner Gitlink --repo forgeplus -i 123 -b "已修复" + +# 批量修改状态 +gitlink-cli issue +batch-status --state resolved --numbers 1,2,3 --owner Gitlink --repo forgeplus + +# 批量修改优先级 +gitlink-cli issue +batch-priority --priority urgent --numbers 1,2,3 --owner Gitlink --repo forgeplus + +# 批量修改标签 +gitlink-cli issue +batch-label --numbers 1,2,3 --label 功能 --owner Gitlink --repo forgeplus + +# 批量修改负责人 +gitlink-cli issue +batch-assignee --numbers 1,2,3 --assignee zhangsan --owner Gitlink --repo forgeplus + +# 批量创建 +gitlink-cli issue --owner Gitlink --repo forgeplus +batch-create --titles "issue1,issue2,issue3" + +# 批量关闭 +gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --numbers 1,2,3 + ``` ### Pull Request @@ -215,6 +241,28 @@ gitlink-cli release +create --owner Gitlink --repo forgeplus -t v1.0.0 -n "v1.0. gitlink-cli release +view --owner Gitlink --repo forgeplus -i ``` +### Wiki 管理 + +```bash +# 列出 Wiki 页面 +gitlink-cli wiki +list --owner Gitlink --repo forgeplus + +# 查看 Wiki 页面 +gitlink-cli wiki +view --owner Gitlink --repo forgeplus --title "快速开始" + +# 创建 Wiki 页面 +gitlink-cli wiki +create --title "新页面" --content "页面正文" + +# 更新 Wiki 页面(覆盖内容) +gitlink-cli wiki +update --title "新页面" --cover "更新后的内容" --owner Gitlink --repo forgeplus + +# 追加内容到 Wiki 页面 +gitlink-cli wiki +update --title "新页面" --add "追加的内容" --owner Gitlink --repo forgeplus + +# 删除 Wiki 页面 +gitlink-cli wiki +delete --title "新页面" --owner Gitlink --repo forgeplus +``` + ### 搜索 ```bash @@ -318,6 +366,7 @@ gitlink-cli/ │ ├── ci/ # CI shortcuts │ ├── search/ # 搜索 shortcuts │ ├── user/ # 用户 shortcuts +│ ├── wiki/ # Wiki shortcuts │ └── register.go # 注册入口 ├── skills/ # AI Agent Skills │ ├── README.md # Skills 使用指南 diff --git a/doc/design.md b/doc/design.md index d25295b..0776803 100644 --- a/doc/design.md +++ b/doc/design.md @@ -40,8 +40,9 @@ gitlink-cli/ │ ├── common/ │ │ ├── types.go # Shortcut / Flag / RuntimeContext 定义 │ │ └── runner.go # CallAPI / PaginateAll / ResolveOwnerRepo -│ ├── repo/ # repo +create / +clone / +fork / +list / +info -│ ├── issue/ # issue +list / +create / +view / +close / +comment +│ ├── repo/ # repo +create / +clone / +fork / +list / +info / +batch-create / +batch-update +│ ├── issue/ # issue +list / +create / +view / +close / +comment / +batch-* (6 个批量命令) +│ ├── wiki/ # wiki +list / +view / +create / +update / +delete │ ├── pr/ # pr +list / +create / +view / +merge / +review │ ├── release/ # release +list / +create / +download │ ├── branch/ # branch +list / +protect / +unprotect @@ -78,8 +79,9 @@ gitlink-cli/ | 领域 | Shortcuts | 数量 | |------|-----------|------| -| repo | `+create` `+clone` `+fork` `+list` `+info` `+delete` `+settings` | 7 | -| issue | `+list` `+create` `+view` `+update` `+close` `+comment` `+assign` `+label` | 8 | +| repo | `+create` `+clone` `+fork` `+list` `+info` `+delete` `+settings` `+batch-create` `+batch-update` | 9 | +| issue | `+list` `+create` `+view` `+update` `+close` `+comment` `+assign` `+label` `+batch-close` `+batch-status` `+batch-priority` `+batch-assign` `+batch-label` `+batch-create` | 14 | +| wiki | `+list` `+view` `+create` `+update` `+delete` | 5 | | pr | `+list` `+create` `+view` `+merge` `+close` `+review` `+files` `+diff` | 8 | | release | `+list` `+create` `+view` `+delete` `+download` | 5 | | branch | `+list` `+create` `+delete` `+protect` `+unprotect` | 5 | @@ -437,7 +439,169 @@ skills/ --- -## 9 完整命令参考 +## 9 批量操作设计模式 + +Issue 和 Repo 两个领域均实现了批量操作命令,遵循统一的设计模式。 + +### 9.1 命令清单 + +| 领域 | 命令 | 用途 | 输入方式 | +|------|------|------|----------| +| issue | `+batch-close` | 批量关闭 | `--numbers` 或 `--from` CSV | +| issue | `+batch-status` | 批量更换状态 | `--state` + `--numbers`/`--from` | +| issue | `+batch-priority` | 批量更换优先级 | `--priority` + `--numbers`/`--from` | +| issue | `+batch-assign` | 批量更换负责人 | `--assignee` + `--numbers`/`--from` | +| issue | `+batch-label` | 批量更换标记 | `--label` + `--numbers`/`--from` | +| issue | `+batch-create` | 批量创建 Issue | `--titles` 或 `--from` CSV(支持 Bug/Feature 模板) | +| repo | `+batch-create` | 批量创建仓库 | `--names` 或 `--from` CSV | +| repo | `+batch-update` | 批量更新仓库 | `--names` 或 `--from` CSV | + +### 9.2 核心类型 + +```go +type BatchResult struct { + Number string `json:"number"` // Issue 编号或仓库名 + Action string `json:"action"` // 操作类型 + Status string `json:"status"` // 执行结果 + Error string `json:"error,omitempty"` +} + +type BatchSummary struct { + Repository string `json:"repository"` + Action string `json:"action"` + Value string `json:"value,omitempty"` + DryRun bool `json:"dry_run"` + Total int `json:"total"` + Succeeded int `json:"succeeded"` + Failed int `json:"failed"` + Results []BatchResult `json:"results"` +} +``` + +### 9.3 统一设计原则 + +| 原则 | 说明 | +|------|------| +| **输入灵活** | `--numbers`/`--names`(内联逗号分隔)和 `--from`(CSV 文件)可同时使用,自动去重合并 | +| **dry-run 统一** | 所有批量命令支持 `--dry-run`,预览模式下 status 为 `"planned"`,不发起写请求 | +| **错误不中断** | 单条失败不影响后续处理,全部执行完后返回完整汇总。有任何失败则 exit code = 1 | +| **输出统一** | 所有命令输出相同结构的 `BatchSummary` JSON | +| **修改前先 GET** | Issue 批量修改先 GET 当前 Issue 保留 subject/description,PATCH 时只替换目标字段。Repo 批量更新先 GET 获取 name + identifier | +| **参数容错** | 所有名称映射(状态、优先级、标签、负责人)同时支持字符串名和直接传数字 ID | + +### 9.4 Issue 参数值映射 + +**状态**:`new`(1) / `in-progress`(2) / `resolved`(3) / `closed`(5) / `rejected`(6) + +**优先级**:`low`(1) / `normal`(2) / `high`(3) / `urgent`(4) + +**标记**:使用项目级中文标签名映射到大整数 ID(如 `缺陷`→315526、`功能`→315527) + +**负责人**:传入 login 用户名,CLI 调用 `/users/{login}` API 转换为 `user_id` + +### 9.5 Issue 批量创建模板系统 + +`+batch-create` 支持三种输入模式: + +1. **CLI 直接输入**(`--titles`):逗号分隔标题,统一应用 `--priority`/`--label`/`--assignee`/`--state` +2. **自由 CSV**(`--from`):自由指定 title/body/priority/label/assignee/status 列 +3. **模板 CSV**(`--from` + `--template`): + - `--template bug`:自动生成 Bug 描述格式,自动设置缺陷标签 + - `--template feature`:自动生成功能描述格式,自动设置功能标签 + +### 9.6 Repo 批量操作注意事项 + +- **batch-create**:POST 路径 `/{login}/{name}` 中的 login 必须是当前登录用户,需先 `GET /users/me` +- **batch-update**:PATCH 请求体必须包含从 GET 获取的 `name` 和 `identifier`,否则 API 报错 +- **--private/--public 互斥**:batch-update 不允许同时设置两个标志 + +### 9.7 关键 API 字段差异 + +GitLink 基于 Redmine 但修改了大量字段名: + +| Redmine 标准字段 | GitLink 实际字段 | 格式 | +|-----------------|-----------------|------| +| `assigned_to_id` | `assigner_ids` | 数组 `[user_id]` | +| `tracker_id` | `issue_tag_ids` | 数组 `[tag_id]`(项目级大整数) | + +> GitLink API 对不认识的字段返回 200 而非报错,字段名错误会导致静默失败。必须通过浏览器 DevTools 抓取实际请求确认字段名和格式。 + +--- + +## 10 Wiki Shortcut 设计 + +Wiki 是独立的全新 Shortcut 领域,提供 5 个命令覆盖 Wiki 页面的 CRUD 操作。 + +### 10.1 双域名架构 + +Wiki API 部署在 gateway 域名上,与主站 API(www 域名)分离: + +``` + ┌─ www 域名 ─────────────────────┐ +resolveProjectID() → │ GET /{owner}/{repo}/detail.json │ → project_id + └────────────────────────────────┘ + + ┌─ gateway 域名 ───────────────────────────┐ +callWikiAPI*() → │ /wiki/open/* (不带 .json) │ → 解包 code/data + └──────────────────────────────────────────┘ +``` + +- **www 域名**(`https://www.gitlink.org.cn/api`):用于 detail API 获取 project_id,走默认 client +- **gateway 域名**(`https://gateway.gitlink.org.cn/api`):用于所有 wiki CRUD API,走独立 client + +### 10.2 Client 扩展 + +`internal/client/client.go` 新增 `SkipJSONSuffix` 字段: + +```go +type Client struct { + // ... 原有字段 ... + SkipJSONSuffix bool // 为 true 时不自动追加 .json 后缀(wiki gateway API 需要) +} +``` + +wiki 命令创建独立 client 实例,设置 `SkipJSONSuffix: true` 并使用 gateway BaseURL。 + +### 10.3 响应解包 + +Gateway API 使用不同的响应格式 `{code, data, msg}`(而非常规的 `{status, ...}`): + +```go +// unwrapGatewayResponse 解包 gateway 响应 +// 成功: code=200/201, 提取 data 字段 +// 失败: code=500/400/404, 返回 "[code] msg" 错误信息 +func unwrapGatewayResponse(raw []byte) ([]byte, error) +``` + +### 10.4 命令详情 + +| 命令 | HTTP 方法 | API 路径 | 关键参数 | +|------|----------|---------|---------| +| `wiki +list` | GET | `/wiki/open/wikiPages` | 无额外参数 | +| `wiki +view` | GET | `/wiki/open/getWiki` | `--title`(必填) | +| `wiki +create` | POST | `/wiki/open/createWiki` | `--title`(必填)`--content/--file` `--message` | +| `wiki +update` | PUT | `/wiki/open/updateWiki` | `--title`(必填)`--cover/--add` `--file` | +| `wiki +delete` | DELETE | `/wiki/open/deleteWiki` | `--title`(必填) | + +### 10.5 特殊处理 + +| 处理项 | 说明 | +|--------|------| +| **base64 编解码** | Wiki 内容在 API 中为 base64 编码,CLI 自动编解码,对用户透明 | +| **project_id 缓存** | `resolveProjectID()` 使用 `sync.Map` 缓存,同一 owner/repo 只调一次 API | +| **owner/repo 自动解析** | 在 git 仓库目录下可省略 `--owner`/`--repo` | +| **--update --add 模式** | 先 GET 现有内容 → 解码 → 追加 → 重新编码 → PUT 提交 | +| **嵌套对象过滤** | 表格输出时过滤无意义的嵌套对象字段(如 wiki_clone_link) | + +### 10.6 已知限制 + +- **delete 后端 bug**:GitLink 平台 `deleteWiki` API 只清空内容,不删除侧边栏条目 +- **gateway 域名硬编码**:wiki API 仅部署在 gateway,detail API 在 www,不可互换 +- **create/update pageName 差异**:create 接受原始中文 pageName,update 需要 URL 编码 + +--- + +## 11 完整命令参考 ``` gitlink-cli @@ -457,7 +621,9 @@ gitlink-cli │ ├── +list # 仓库列表 │ ├── +info # 仓库详情 │ ├── +delete # 删除仓库 -│ └── +settings # 仓库设置 +│ ├── +settings # 仓库设置 +│ ├── +batch-create # 批量创建仓库 +│ └── +batch-update # 批量更新仓库 ├── issue │ ├── +list # Issue 列表 │ ├── +create # 创建 Issue @@ -466,7 +632,13 @@ gitlink-cli │ ├── +close # 关闭 Issue │ ├── +comment # 添加评论 │ ├── +assign # 指派 -│ └── +label # 标签管理 +│ ├── +label # 标签管理 +│ ├── +batch-close # 批量关闭 Issue +│ ├── +batch-status # 批量更换状态 +│ ├── +batch-priority # 批量更换优先级 +│ ├── +batch-assign # 批量更换负责人 +│ ├── +batch-label # 批量更换标记 +│ └── +batch-create # 批量创建 Issue(含 Bug/Feature 模板) ├── pr │ ├── +list # PR 列表 │ ├── +create # 创建 PR @@ -501,6 +673,12 @@ gitlink-cli ├── user │ ├── +me # 当前用户 │ └── +info # 用户详情 +├── wiki +│ ├── +list # Wiki 页面列表 +│ ├── +view # 查看 Wiki 页面 +│ ├── +create # 创建 Wiki 页面 +│ ├── +update # 更新 Wiki 页面 +│ └── +delete # 删除 Wiki 页面 ├── search │ ├── +repos # 搜索仓库 │ ├── +issues # 搜索 Issue @@ -518,7 +696,7 @@ gitlink-cli --- -## 10 关键文件清单 +## 12 关键文件清单 实现时需要修改/创建的核心文件: @@ -541,8 +719,9 @@ gitlink-cli | `internal/registry/meta_data.json` | API 元数据 | | `shortcuts/common/types.go` | Shortcut 核心类型 | | `shortcuts/common/runner.go` | RuntimeContext | -| `shortcuts/repo/*.go` | 仓库 shortcuts | -| `shortcuts/issue/*.go` | Issue shortcuts | +| `shortcuts/repo/*.go` | 仓库 shortcuts(含 batch_create/batch_update) | +| `shortcuts/issue/*.go` | Issue shortcuts(含 batch.go + batch_create.go 批量操作) | +| `shortcuts/wiki/*.go` | Wiki shortcuts | | `shortcuts/pr/*.go` | PR shortcuts | | `shortcuts/register.go` | Shortcut 注册 | | `skills/gitlink-shared/SKILL.md` | 共享 Skill | @@ -550,7 +729,7 @@ gitlink-cli --- -## 11 开发计划 +## 13 开发计划 ### Phase 1: Foundation(第 1-2 周) @@ -609,7 +788,7 @@ gitlink-cli --- -## 12 验证方案 +## 14 验证方案 | 阶段 | 验证方式 | |------|----------| diff --git a/doc/gitlink_api_reference.md b/doc/gitlink_api_reference.md index fc54084..968ace4 100644 --- a/doc/gitlink_api_reference.md +++ b/doc/gitlink_api_reference.md @@ -15206,6 +15206,46 @@ GET /api/wikiExport/wikiExport-wrapper |» data|object|false|none||none| |» message|string|false|none||none| +--- + +## Gateway Wiki API(CLI 实际调用) + +gitlink-cli 的 wiki shortcut 实际调用的是 **gateway 域名**下的 `/wiki/open/*` 端点,而非上述 www 域名的 `/api/wiki/*` 端点。两者存在以下差异: + +| 差异项 | www 域名(APIfox 文档) | gateway 域名(CLI 实际使用) | +|--------|------------------------|---------------------------| +| Base URL | `https://www.gitlink.org.cn/api` | `https://gateway.gitlink.org.cn/api` | +| URL 前缀 | `/api/wiki/` | `/wiki/open/` | +| JSON 后缀 | 需要 `.json` | 不需要 `.json` | +| 响应格式 | 直接返回 data | `{"code": 200, "data": {...}, "msg": ""}` 包一层 | +| 错误判断 | `status` 字段 ≠ 200 | `code` 字段 ≠ 200/201 | + +### 实际调用路径对比 + +| 操作 | www 文档路径 | gateway 实际路径 | +|------|-------------|-----------------| +| 创建 | `POST /api/wiki/createWiki.json` | `POST /wiki/open/createWiki` | +| 删除 | `DELETE /api/wiki/deleteWiki.json` | `DELETE /wiki/open/deleteWiki` | +| 查看 | `GET /api/wiki/getWiki.json` | `GET /wiki/open/getWiki` | +| 更新 | `PUT /api/wiki/updateWiki.json` | `PUT /wiki/open/updateWiki` | +| 列表 | `GET /api/wiki/wikiPages.json` | `GET /wiki/open/wikiPages` | + +### 响应格式差异示例 + +**www 域名响应**(标准格式): +```json +{"data": {"title": "test", "content": "..."}} +``` + +**gateway 域名响应**(包一层): +```json +{"code": 200, "data": {"title": "test", "content": "..."}, "msg": "success"} +``` + +CLI 通过 `unwrapGatewayResponse()` 函数自动解包 gateway 格式,对用户透明。 + +> **注意**:project_id 仍需通过 www 域名的 `/api/{owner}/{repo}/detail.json` 获取,两个域名不可互换。 + # 流水线 ## GET 流水线列表 diff --git a/shortcuts/issue/batch.go b/shortcuts/issue/batch.go index 48d67af..2fef7ea 100644 --- a/shortcuts/issue/batch.go +++ b/shortcuts/issue/batch.go @@ -27,19 +27,40 @@ const ( statusRejected = 6 ) -// Tag name → GitLink tag ID mapping -// Collect IDs from web UI DevTools: change tag → capture PATCH payload → get issue_tag_ids value -var tagIDs = map[string]int{ - "缺陷": 315526, - "功能": 315527, - "文档": 315533, - "重复": 315525, - "疑问": 315528, - "支持": 315529, - "任务": 315530, - "测试": 315534, - "协助": 315531, - "搁置": 315532, +// Tracker constants +const ( + trackerBug = 1 + trackerFeature = 2 + trackerSupport = 3 + trackerDoc = 4 + trackerTest = 5 + trackerDuplicate = 6 + trackerQuestion = 7 +) + +var priorityNames = map[int]string{ + priorityLow: "low", + priorityNormal: "normal", + priorityHigh: "high", + priorityUrgent: "urgent", +} + +var statusNames = map[int]string{ + statusNew: "new", + statusInProgress: "in-progress", + statusResolved: "resolved", + statusClosed: "closed", + statusRejected: "rejected", +} + +var trackerNames = map[int]string{ + trackerBug: "bug", + trackerFeature: "feature", + trackerSupport: "support", + trackerDoc: "doc", + trackerTest: "test", + trackerDuplicate: "duplicate", + trackerQuestion: "question", } // BatchResult is a single item result in a batch operation. @@ -327,8 +348,7 @@ func runBatchAssign(ctx *common.RuntimeContext) error { summary.Results = append(summary.Results, result) continue } - body := map[string]interface{}{"assigner_ids": []interface{}{assigneeID}} - if _, err := ctx.CallAPI("PATCH", fmt.Sprintf("%s/issues/%s", v1RepoPath(ctx), number), body); err != nil { + if err := updateIssueField(ctx, number, map[string]interface{}{"assigned_to_id": assigneeID}); err != nil { result.Status = "failed" result.Error = err.Error() summary.Failed++ @@ -369,7 +389,7 @@ func runBatchLabel(ctx *common.RuntimeContext) error { return err } label := ctx.Arg("label") - trackerID, err := parseLabel(label) + trackerID, err := parseTracker(label) if err != nil { return err } @@ -400,7 +420,7 @@ func runBatchLabel(ctx *common.RuntimeContext) error { summary.Results = append(summary.Results, result) continue } - if err := updateIssueField(ctx, number, map[string]interface{}{"issue_tag_ids": []interface{}{trackerID}}); err != nil { + if err := updateIssueField(ctx, number, map[string]interface{}{"tracker_id": trackerID}); err != nil { result.Status = "failed" result.Error = err.Error() summary.Failed++ @@ -507,23 +527,28 @@ func parsePriority(p string) (int, error) { } } -func parseLabel(name string) (int, error) { - name = strings.TrimSpace(name) - if id, ok := tagIDs[name]; ok && id != 0 { - return id, nil +func parseTracker(label string) (int, error) { + switch strings.ToLower(strings.TrimSpace(label)) { + case "bug": + return trackerBug, nil + case "feature": + return trackerFeature, nil + case "support": + return trackerSupport, nil + case "doc": + return trackerDoc, nil + case "test": + return trackerTest, nil + case "duplicate": + return trackerDuplicate, nil + case "question": + return trackerQuestion, nil + default: + if id, err := strconv.Atoi(label); err == nil { + return id, nil + } + return 0, fmt.Errorf("invalid label %q: use bug, feature, support, doc, test, duplicate, or question", label) } - if id, err := strconv.Atoi(name); err == nil { - return id, nil - } - return 0, fmt.Errorf("label %q not found or tag ID not configured; valid names: %s", name, labelNames()) -} - -func labelNames() string { - names := make([]string, 0, len(tagIDs)) - for n := range tagIDs { - names = append(names, n) - } - return strings.Join(names, ", ") } func collectIssueNumbers(numbersValue, csvPath string) ([]string, error) {