Merge pull request '更新文档' (#5) from mc_branch into master

This commit is contained in:
mengcheng 2026-06-01 21:41:06 +08:00
commit 863e3e512a
5 changed files with 364 additions and 47 deletions

View File

@ -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 <build_id>
gitlink-cli ci +restart --owner Gitlink --repo forgeplus -i <build_id>
```
### 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

View File

@ -27,7 +27,8 @@
| 分类 | 能力 |
|------|------|
| 📦 仓库 | 列出、创建、Fork、删除仓库查看仓库信息 |
| 🐛 Issue | 创建、更新、关闭、批量关闭、评论 Issue |
| 🐛 Issue | 创建、更新、关闭、评论 Issue6 个批量操作(关闭/状态/优先级/负责人/标记/创建) |
| 📖 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 "issue1issue2issue3"
# 批量关闭
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 <version_id>
```
### 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 使用指南

View File

@ -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/descriptionPATCH 时只替换目标字段。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 域名上,与主站 APIwww 域名)分离:
```
┌─ 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 仅部署在 gatewaydetail API 在 www不可互换
- **create/update pageName 差异**create 接受原始中文 pageNameupdate 需要 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 验证方案
| 阶段 | 验证方式 |
|------|----------|

View File

@ -15206,6 +15206,46 @@ GET /api/wikiExport/wikiExport-wrapper
|» data|object|false|none||none|
|» message|string|false|none||none|
---
## Gateway Wiki APICLI 实际调用)
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 流水线列表

View File

@ -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) {