Merge PR #268: feat(branch): add lifecycle shortcuts

# Conflicts:
#	README.zh-CN.md
#	shortcuts/branch/branch.go
This commit is contained in:
wbtiger 2026-07-14 22:49:57 +08:00
commit d77c12593e
6 changed files with 350 additions and 412 deletions

View File

@ -460,6 +460,10 @@ gitlink-cli pr +review --owner Gitlink --repo forgeplus -i 42 --status approved
```bash
# List branches
gitlink-cli branch +list --owner Gitlink --repo forgeplus
gitlink-cli branch +list --owner Gitlink --repo forgeplus --keyword fix --state deleted
# List all branches without pagination
gitlink-cli branch +all --owner Gitlink --repo forgeplus
# Create a branch
gitlink-cli branch +create --name feature/new-feature
@ -472,6 +476,10 @@ gitlink-cli branch +protect --name main
# Remove branch protection
gitlink-cli branch +unprotect --name main
# Set default branch or restore a deleted branch (preview first)
gitlink-cli branch +set-default --owner Gitlink --repo forgeplus --name main --dry-run
gitlink-cli branch +restore --owner Gitlink --repo forgeplus --id 7 --name feature/old --dry-run
```
### Release Management

View File

@ -107,24 +107,19 @@
| 🐛 Issue | 创建、更新、关闭、批量关闭/更新/删除、评论 Issue |
| 🔖 标签 | 创建、列出、更新、删除 Issue 标签 |
| 🔀 PR | 创建、合并、Review Pull Request查看变更文件 |
| 🧭 Compare | 对比分支、标签或提交,查看变更文件,筛选提交并汇总差异热点 |
| 👥 成员 | 列出、添加、移除仓库成员,调整角色,生成和接受邀请链接 |
| 📨 邀请 | 生成邀请链接、查看邀请信息、接受邀请、加入/退出项目 |
| 🌿 分支 | 创建、删除、保护分支 |
| 🏷️ 发布 | 创建、编辑、更新、查看、删除 Release |
| 🏢 组织 | 管理组织、列出成员、查看团队 |
| 🏢 组织 | 管理组织、成员、团队 |
| 🔧 CI | 查看构建、日志、CI/CD 操作 |
| ⚙️ Pipeline | 运行、查看、启停、删除流水线工作流并查询日志 |
| 🔔 消息通知设置 | 查看并更新个人消息通知投递偏好 |
| 📖 Wiki | 列出、查看、创建、更新、删除 Wiki 页面 |
| 🔔 通知 | 列出、已读、删除用户消息 |
| 🔍 搜索 | 搜索仓库、用户 |
| 📊 数据集 | 按项目查询科研数据集 |
| 👤 用户 | 查看用户资料、贡献热力图、活跃度与能力统计 |
| 👤 用户 | 查看用户资料和信息 |
| 📊 画像 | 用户开发能力、角色定位、专业定位、近期活动、贡献热力图统计 |
| 📋 项目管理 | Sprint 管理、看板、周报 |
| 🤖 工作流 | AI 驱动的 Issue 分类、PR Review、Release Notes |
| 🩺 Doctor | 一键体检配置、认证、仓库上下文与 API 连通性 |
## 安装与快速上手
@ -185,29 +180,6 @@ export GITLINK_TOKEN="your-token" # 或设置环境变量(适用于 CI/CD、
gitlink-cli repo +list
```
#### Shell 自动补全
安装后可以为常用 shell 生成自动补全脚本:
```bash
# Bash
mkdir -p ~/.local/share/bash-completion/completions
gitlink-cli completion bash > ~/.local/share/bash-completion/completions/gitlink-cli
# Zsh
gitlink-cli completion zsh > "${fpath[1]}/_gitlink-cli"
# Fish
mkdir -p ~/.config/fish/completions
gitlink-cli completion fish > ~/.config/fish/completions/gitlink-cli.fish
# PowerShell
gitlink-cli completion powershell > gitlink-cli.ps1
. ./gitlink-cli.ps1
```
如果当前终端不需要补全说明文本,可以追加 `--no-descriptions` 生成更精简的脚本。
### 快速上手AI Agent
> 以下步骤面向 AI Agent。部分步骤需要用户在浏览器中完成操作。
@ -259,17 +231,8 @@ gitlink-cli repo +list
# 查看仓库信息
gitlink-cli repo +info --owner Gitlink --repo forgeplus
# 使用 git 克隆仓库(对标 `gh repo clone`
gitlink-cli repo +clone --owner Gitlink --repo forgeplus
gitlink-cli repo +clone --owner Gitlink --repo forgeplus -d ./forgeplus -b develop
# 读取仓库 README
gitlink-cli repo +readme --owner Gitlink --repo forgeplus --ref master
gitlink-cli repo +readme --owner Gitlink --repo forgeplus --ref master --path docs
# 读取仓库任意文件
gitlink-cli repo +file --owner Gitlink --repo forgeplus --path go.mod --ref master
gitlink-cli repo +file --owner Gitlink --repo forgeplus --path .gitignore --content-only
# 列出仓库根目录或指定目录文件
gitlink-cli repo +tree --owner Gitlink --repo forgeplus --ref master
@ -301,74 +264,8 @@ gitlink-cli repo +unlike --owner Gitlink --repo forgeplus --project-id 123
# 创建仓库
gitlink-cli repo +create -n my-project -d "项目描述"
# 更新仓库设置(只修改指定字段)
gitlink-cli repo +edit --owner me --repo my-project -d "新描述" --website "https://example.org"
gitlink-cli repo +edit --owner me --repo my-project --private true
gitlink-cli repo +edit --owner me --repo my-project --default-branch main
# Fork 仓库
gitlink-cli repo +fork --owner Gitlink --repo forgeplus
# 列出可接收仓库转移的组织
gitlink-cli repo +transfer-orgs --owner Gitlink --repo forgeplus
# 预览仓库转移请求,不修改线上数据
gitlink-cli repo +transfer --owner Gitlink --repo forgeplus --target-owner my-org --dry-run
# 确认后发起仓库转移
gitlink-cli repo +transfer --owner Gitlink --repo forgeplus --target-owner my-org --yes
# 预览取消待处理的仓库转移
gitlink-cli repo +transfer-cancel --owner Gitlink --repo forgeplus --dry-run
# 确认后取消待处理的仓库转移
gitlink-cli repo +transfer-cancel --owner Gitlink --repo forgeplus --yes
```
### 消息通知设置
```bash
# 列出可用的消息通知设置分组和键
gitlink-cli message-settings +catalog
# 查看当前用户生效中的消息通知设置
gitlink-cli message-settings +view
# 只看另一个用户的仓库管理类消息设置
gitlink-cli message-settings +view --login Mengz --group ManageProject
# 预览关闭指定设置键的站内通知,不发送请求
gitlink-cli message-settings +update \
--channel notification \
--state off \
--keys Normal::Permission,ManageProject::Issue \
--dry-run
# 将预设应用到所有已知设置
gitlink-cli message-settings +preset --name notification-only --all
```
### 消息通知设置
```bash
# 列出可用的消息通知设置分组和键
gitlink-cli message-settings +catalog
# 查看当前用户生效中的消息通知设置
gitlink-cli message-settings +view
# 只看另一个用户的仓库管理类消息设置
gitlink-cli message-settings +view --login Mengz --group ManageProject
# 预览关闭指定设置键的站内通知,不发送请求
gitlink-cli message-settings +update \
--channel notification \
--state off \
--keys Normal::Permission,ManageProject::Issue \
--dry-run
# 将预设应用到所有已知设置
gitlink-cli message-settings +preset --name notification-only --all
```
### Webhook 管理
@ -391,13 +288,10 @@ gitlink-cli webhook +tasks --owner Gitlink --repo forgeplus --id 68
### Wiki 管理
```bash
# 列出 Wiki 页面(目录结构);省略 --project-id 时自动从仓库信息解析
gitlink-cli wiki +list --owner Gitlink --repo forgeplus
# 列出 Wiki 页面(目录结构)
gitlink-cli wiki +list --owner Gitlink --repo forgeplus --project-id 12345
# 查看 Wiki 页面
gitlink-cli wiki +view --owner Gitlink --repo forgeplus -n home
# 也可显式传 --project-id 以省去一次查询请求
gitlink-cli wiki +view --owner Gitlink --repo forgeplus --project-id 12345 -n home
# 创建 Wiki 页面
@ -413,25 +307,6 @@ gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n
gitlink-cli wiki +delete --owner Gitlink --repo forgeplus --project-id 12345 -n old-page
```
### 通知管理
```bash
# 列出当前用户未读系统消息
gitlink-cli notification +list --type notification --status unread
# 列出指定用户的 @我消息
gitlink-cli notification +list --user Mengz --type atme
# 标记消息为已读
gitlink-cli notification +read --type atme --ids 101,102
# 将全部未读系统消息标记为已读
gitlink-cli notification +read --type notification --ids -1
# 删除消息
gitlink-cli notification +delete --type notification --ids 101,102
```
### 成员管理
```bash
@ -452,29 +327,6 @@ gitlink-cli member +role --owner Gitlink --repo forgeplus --user-id 101 --role D
# 生成邀请链接
gitlink-cli member +invite-link --owner Gitlink --repo forgeplus --role developer --apply true
# 通过邀请码申请加入项目
gitlink-cli member +apply --code MPzQgH --role developer --dry-run
# 退出仓库成员关系
gitlink-cli member +quit --owner Gitlink --repo forgeplus --dry-run
gitlink-cli member +quit --owner Gitlink --repo forgeplus --yes
```
### 组织管理
```bash
# 列出组织
gitlink-cli org +list
# 查看组织详情
gitlink-cli org +info --id Gitlink
# 列出组织成员
gitlink-cli org +members --id Gitlink --page 1 --limit 20
# 列出组织团队
gitlink-cli org +teams --id Gitlink --page 1 --limit 20
```
### Issue 管理
@ -498,9 +350,6 @@ gitlink-cli issue +update --owner Gitlink --repo forgeplus --number 123 --priori
# 关闭 Issue
gitlink-cli issue +close --owner Gitlink --repo forgeplus -i 123
# 删除 Issue破坏性操作需 --yes 确认)
gitlink-cli issue +delete --owner Gitlink --repo forgeplus --number 123 --yes
# 预览批量关闭,不修改数据
gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --numbers 123,124 --dry-run
@ -553,35 +402,8 @@ gitlink-cli label +create --owner Gitlink --repo forgeplus -n bug -d "功能缺
# 更新标签(未指定的字段会被保留)
gitlink-cli label +update --owner Gitlink --repo forgeplus -i 42 -c "#00FF00"
# 安全删除标签
gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42 --dry-run
gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42 --yes
# 安全批量创建标签
gitlink-cli label +batch-create --owner Gitlink --repo forgeplus \
--labels 'bug:#ee0701:Bug 修复;feature:#0075ca:新功能' --dry-run
gitlink-cli label +batch-create --owner Gitlink --repo forgeplus \
--labels 'bug:#ee0701:Bug 修复;feature:#0075ca:新功能' --yes
# 安全批量删除标签
gitlink-cli label +batch-delete --owner Gitlink --repo forgeplus --ids 3,5,8 --dry-run
gitlink-cli label +batch-delete --owner Gitlink --repo forgeplus --ids 3,5,8 --yes
```
### Compare
```bash
# 对比两个分支、标签或提交
gitlink-cli compare +view --owner Gitlink --repo forgeplus --head feature/search --base master
# 列出两个版本之间的变更文件
gitlink-cli compare +files --owner Gitlink --repo forgeplus --head feature/search --base master
# 按作者或关键字筛选提交
gitlink-cli compare +commits --owner Gitlink --repo forgeplus --head feature/search --base master --author alice -k fix -l 10
# 汇总提交、热点文件、目录分布和扩展名分布
gitlink-cli compare +summary --owner Gitlink --repo forgeplus --head feature/search --base master --top-files 5
# 删除标签
gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42
```
### Pull Request
@ -598,11 +420,6 @@ gitlink-cli pr +create --owner Gitlink --repo forgeplus -t "feat: 新功能" --h
# 查看 PR
gitlink-cli pr +view --owner Gitlink --repo forgeplus -i 42
# 对于已合并或已关闭的 PRJSON 输出会尽量补齐 `created_at`、`merged_at`、`closed_at` 和 `closed_on`
# 在本地检出 PR 分支(对标 `gh pr checkout`;需在 git 克隆目录内执行)
gitlink-cli pr +checkout --owner Gitlink --repo forgeplus -i 42
gitlink-cli pr +checkout --owner Gitlink --repo forgeplus -i 42 -b review-42
# 合并 PR
gitlink-cli pr +merge --owner Gitlink --repo forgeplus -i 42
@ -625,33 +442,27 @@ gitlink-cli pr +reviews --owner Gitlink --repo forgeplus -i 42
# 创建 PR 审查(支持 dry-run 预览)
gitlink-cli pr +review --owner Gitlink --repo forgeplus -i 42 --status approved -c "LGTM" --dry-run
gitlink-cli pr +review --owner Gitlink --repo forgeplus -i 42 --status approved -c "LGTM"
# 查看行级审查评论和未解决讨论
gitlink-cli pr +review-comments --owner Gitlink --repo forgeplus -i 42 --state opened --need-respond true --full
# 创建行级审查评论或回复
gitlink-cli pr +review-comment --owner Gitlink --repo forgeplus -i 42 -b "请处理这个边界情况" --type problem --review-id 7 --line-code abc_1_2 --commit deadbeef --path main.go --dry-run
# 解决、编辑或删除审查评论
gitlink-cli pr +review-comment-update --owner Gitlink --repo forgeplus -i 42 --comment-id 99 --state resolved
gitlink-cli pr +review-comment-delete --owner Gitlink --repo forgeplus -i 42 --comment-id 99
```
### 工作流 Agent 命令
### 分支管理
```bash
# 只读获取 PR 审查上下文包仓库、PR、文件、Review、Issue、标签
gitlink-cli workflow +review-context --owner Gitlink --repo forgeplus --number 42 --format json
# 列出分支,支持关键词和删除分支过滤
gitlink-cli branch +list --owner Gitlink --repo forgeplus
gitlink-cli branch +list --owner Gitlink --repo forgeplus --keyword fix --state deleted
# 生成 PR 审查摘要
gitlink-cli workflow +pr-summary --owner Gitlink --repo forgeplus --number 42 --format markdown
# 无分页列出全部分支
gitlink-cli branch +all --owner Gitlink --repo forgeplus
# 生成仓库工作流报告
gitlink-cli workflow +repo-report --owner Gitlink --repo forgeplus --format markdown
# 创建 / 删除分支
gitlink-cli branch +create --owner Gitlink --repo forgeplus --name feature/new-feature
gitlink-cli branch +delete --owner Gitlink --repo forgeplus --name feature/old-feature
# 设置默认分支或恢复已删除分支(先 dry-run 预览)
gitlink-cli branch +set-default --owner Gitlink --repo forgeplus --name main --dry-run
gitlink-cli branch +restore --owner Gitlink --repo forgeplus --id 7 --name feature/old --dry-run
```
> `workflow +review-context` 只读取 GitLink 数据,不会评论、审批、拒绝、合并或修改标签。
### 发布管理
```bash
@ -672,26 +483,6 @@ gitlink-cli release +update --owner Gitlink --repo forgeplus -i <version_id> -b
gitlink-cli release +delete --owner Gitlink --repo forgeplus -i <version_id> --dry-run
```
### CI/CD 操作
```bash
# 查看构建列表
gitlink-cli ci +builds --owner Gitlink --repo forgeplus
# 查看构建日志
gitlink-cli ci +logs --owner Gitlink --repo forgeplus --build <build_id>
# 重启或停止构建
gitlink-cli ci +restart --owner Gitlink --repo forgeplus --build <build_id>
gitlink-cli ci +stop --owner Gitlink --repo forgeplus --build <build_id>
# 查看 CI 授权状态并安全启停仓库 CI
gitlink-cli ci +authorize --owner Gitlink --repo forgeplus
gitlink-cli ci +activate --owner Gitlink --repo forgeplus --dry-run
gitlink-cli ci +activate --owner Gitlink --repo forgeplus --yes
gitlink-cli ci +deactivate --owner Gitlink --repo forgeplus --dry-run
```
### 流水线管理
```bash
@ -714,25 +505,6 @@ gitlink-cli pipeline +disable --owner Gitlink --repo forgeplus --id 7 --workflow
gitlink-cli pipeline +delete --owner Gitlink --repo forgeplus --id 7 --dry-run
```
### 项目邀请管理
```bash
# 生成邀请链接
gitlink-cli invite +generate --owner Gitlink --repo forgeplus --role developer --is-apply true
# 查看邀请链接信息
gitlink-cli invite +show --owner Gitlink --repo forgeplus --invite-sign abc123
# 通过链接接受邀请
gitlink-cli invite +accept --owner Gitlink --repo forgeplus --invite-sign abc123
# 通过邀请码加入项目
gitlink-cli invite +join --code ABCDEF --role developer
# 退出项目
gitlink-cli invite +quit --owner Gitlink --repo forgeplus
```
### 忽略文件模板
```bash
@ -743,21 +515,6 @@ gitlink-cli ignore +list
gitlink-cli ignore +list --name Go
```
### 用户统计
```bash
# 用户资料与当前账户
gitlink-cli user +me
gitlink-cli user +info --login alice
# 贡献和活跃度分析
gitlink-cli user +activity --login alice
gitlink-cli user +headmap --login alice --year 2026
gitlink-cli user +develop --login alice --start-time 1717200000 --end-time 1719800000
gitlink-cli user +role --login alice
gitlink-cli user +major --login alice
```
### 搜索
```bash
@ -766,9 +523,6 @@ gitlink-cli search +repos -k "machine learning"
# 搜索用户
gitlink-cli search +users -k "zhangsan"
# 列出推荐/精选项目
gitlink-cli search +recommend
```
### 用户画像
@ -794,49 +548,6 @@ gitlink-cli profile +activity
gitlink-cli profile +contribution --user zhangsan --year 2025
```
### Workflow Agent 命令
`workflow` 提供面向维护者和 AI Agent 的规则化仓库分析能力,目前支持:
- `workflow +triage`
- `workflow +health`
- `workflow +pr-summary`
- `workflow +repo-report`
- `workflow +release-notes`
`workflow +pr-summary` 在未指定 `--format` 时默认输出 `table`
`workflow +repo-report``workflow +release-notes` 在未指定 `--format` 时默认输出 `markdown`
示例:
```bash
# Issue 分诊
gitlink-cli workflow +triage --title "安装失败" --body "运行 go install 时报错" --format table
# 仓库健康度
gitlink-cli workflow +health --owner Gitlink --repo gitlink-cli --stale-days 30 --format table
# PR 审阅摘要
gitlink-cli workflow +pr-summary --owner Gitlink --repo gitlink-cli --number 1 --format markdown
# 仓库工作流报告
gitlink-cli workflow +repo-report --owner Gitlink --repo gitlink-cli --format markdown
# 基于只读 compare fetch 生成 Release Notes
gitlink-cli workflow +release-notes --owner Gitlink --repo gitlink-cli --from-ref v0.1.0 --to-ref master --version v0.2.0 --format markdown
# 从本地 JSON 生成 Release Notes
gitlink-cli workflow +release-notes --from shortcuts/workflow/testdata/release_notes.json --format json
```
安全边界:
- 当前 workflow 命令默认只读,可读取 GitLink 数据或本地 JSON。
- 不依赖 LLM API。
- `workflow +pr-summary` 不评论、不 approve/reject、不合并 PR。
- `workflow +repo-report` 聚合健康度、Issue 分诊和 PR 摘要信号,不写远端。
- `workflow +release-notes` 只读取 compare 数据并渲染版本说明,不创建 Release 或评论。
### 数据集
`dataset` 管理并查询 GitLink 科研数据集(标题、描述、论文内容、许可证、所属项目)。
@ -858,21 +569,6 @@ gitlink-cli dataset +delete-attachment --owner me --repo proj --uuid <uuid> --ye
```
> 注意:`dataset +list`(平台数据集查询)已在生产 gitlink.org.cn 验证可用。按仓库的 `+view`/`+create`/`+update` 遵循已发布的 OpenAPI 契约,但生产环境尚未部署(当前返回 404待平台上线后即可生效。
### 环境自诊断Doctor
```bash
# 运行全部检查配置文件、配置取值、认证、仓库上下文、API 连通性
gitlink-cli doctor
# 结构化输出,适合脚本 / AI Agent 使用
gitlink-cli doctor --format json
# 离线模式:跳过需认证的 API 连通性检查
gitlink-cli doctor --skip-network
```
每项检查返回 `ok` / `warning` / `error` 及修复建议suggestionwarning 不影响使用。
### Raw API
Shortcuts 未覆盖的接口可通过 Raw API 直接调用:
@ -892,38 +588,8 @@ Get-Content issue.json | gitlink-cli api POST /Gitlink/forgeplus/issues --body-s
# 带查询参数
gitlink-cli api GET /Gitlink/forgeplus/commits --query 'page=1&limit=5'
# 鍗曟璇锋眰涓洿鎺ュ鐢?--owner / --repo 鍗犱綅绗?
gitlink-cli api GET /:owner/:repo/issues --owner Gitlink --repo gitlink-cli --query 'page=1&limit=5'
# 鍦?path / query / body / header 涓覆鏌撲竴娆℃€фā鏉垮彉閲?
gitlink-cli api POST /{{owner}}/{{repo}}/issues/{{number}}/journals \
--var owner=Gitlink --var repo=gitlink-cli --var number=42 --var actor=codex \
--query 'notify={{actor}}' \
--header 'X-Actor: {{actor}}' \
--body '{"notes":"handled by {{actor}}"}'
```
### Shell 自动补全
`gitlink-cli` 内置 bash / zsh / fish / PowerShell 补全:
```bash
# Bash加入 ~/.bashrc
source <(gitlink-cli completion bash)
# Zsh加入 ~/.zshrc
source <(gitlink-cli completion zsh)
# Fish
gitlink-cli completion fish | source
# PowerShell
gitlink-cli completion powershell | Out-String | Invoke-Expression
```
各 shell 的一次性安装方式见 `gitlink-cli completion <shell> --help`
## 全局参数
| 参数 | 说明 | 示例 |
@ -932,8 +598,6 @@ gitlink-cli completion powershell | Out-String | Invoke-Expression
| `--repo` | 仓库名称 | `--repo forgeplus` |
| `--format` | 输出格式json/table/yaml | `--format json` |
| `--debug` | 启用调试输出 | `--debug` |
| `--lang` | 界面语言en/zh | `--lang zh` |
| `--jq` | 按点分路径从输出中提取字段 | `--jq data.issues.0.subject` |
**自动上下文解析**:在 git 仓库目录下,`--owner` 和 `--repo` 会自动从 `git remote origin` 解析。
@ -970,12 +634,10 @@ git push gitlink
| `gitlink-issue` | Issue 操作(创建、更新、关闭、批量更新/删除、评论等) |
| `gitlink-pr` | Pull Request 操作创建、合并、Review 等) |
| `gitlink-member` | 仓库成员与邀请链接管理 |
| `gitlink-invite` | 项目邀请管理(生成链接、接受邀请、加入/退出项目) |
| `gitlink-release` | 发布管理(创建、编辑、更新、查看、删除等) |
| `gitlink-org` | 组织管理(成员、团队等) |
| `gitlink-ci` | CI/CD 操作(构建、日志等) |
| `gitlink-pipeline` | 流水线工作流操作(运行、日志、启停、删除等) |
| `gitlink-notification` | 用户消息(列表、标记已读、删除) |
| `gitlink-search` | 搜索功能(仓库、用户等) |
| `gitlink-user` | 用户管理(个人信息等) |
| `gitlink-pm` | 项目管理Sprint、看板、周报等 |
@ -1076,14 +738,6 @@ gitlink-cli repo +list # 直接可用
gitlink-cli auth status # 显示 "✓ Logged in via GITLINK_TOKEN environment variable"
```
脚本中复用当前生效的 token例如直接 `curl` CLI 尚未封装的端点):
```bash
curl -H "Authorization: Bearer $(gitlink-cli auth token)" https://www.gitlink.org.cn/api/v1/...
gitlink-cli auth status --show-token # 查看原始 token默认隐藏
echo $MY_TOKEN | gitlink-cli auth login --with-token # 非交互登录CI/脚本)
```
Token 优先级:`GITLINK_TOKEN` 环境变量 > keyring/文件存储的 token。不设置环境变量时完全兼容原有交互式登录。
### Q: npm 安装成功但 `gitlink-cli` 提示缺少二进制怎么办?

View File

@ -0,0 +1,28 @@
# Branch Lifecycle Shortcuts
## Background
GitLink OpenAPI exposes branch lifecycle capabilities that were not fully reachable from `gitlink-cli`: keyword/state branch listing, no-pagination listing, default branch switching, and deleted branch restoration.
## What Changed
Extended the `branch` shortcut group with documented OpenAPI coverage:
- `branch +list --keyword --state` maps to `GET /api/v1/{owner}/{repo}/branches.json` query parameters.
- `branch +all` maps to `GET /api/v1/{owner}/{repo}/branches/all.json`.
- `branch +set-default --name` maps to `PATCH /api/v1/{owner}/{repo}/branches/update_default_branch.json?name=...`.
- `branch +restore --id --name` maps to `POST /api/v1/{owner}/{repo}/branches/restore.json` with `branch_id` and `branch_name`.
Write operations support `--dry-run` so users and Agents can inspect the exact request before changing branch state.
## Validation
```bash
git diff --check
GOPROXY=https://goproxy.cn,direct go test ./shortcuts/branch ./shortcuts
go vet ./shortcuts/branch ./shortcuts
go run . branch +set-default --help
go run . branch +restore --help
GOPROXY=https://goproxy.cn,direct go test ./...
go vet ./...
```

View File

@ -3,6 +3,8 @@ package branch
import (
"fmt"
"net/url"
"strconv"
"strings"
"github.com/gitlink-org/gitlink-cli/internal/i18n"
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
@ -15,9 +17,10 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
Name: "list",
Description: tr.T("cmd.branch.list.short"),
Flags: []common.Flag{
{Name: "keyword", Short: "k", Usage: tr.T("flag.branch.keyword")},
{Name: "page", Short: "p", Usage: tr.T("flag.page"), Default: "1"},
{Name: "limit", Short: "l", Usage: tr.T("flag.limit"), Default: "20"},
{Name: "keyword", Short: "k", Usage: "Filter branches by keyword"},
{Name: "state", Short: "s", Usage: "Branch state: all, deleted, or empty for active branches"},
},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
@ -26,9 +29,15 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
q := url.Values{}
q.Set("page", ctx.Arg("page"))
q.Set("limit", ctx.Arg("limit"))
if keyword := ctx.Arg("keyword"); keyword != "" {
if keyword := strings.TrimSpace(ctx.Arg("keyword")); keyword != "" {
q.Set("keyword", keyword)
}
if state := strings.TrimSpace(ctx.Arg("state")); state != "" {
if err := validateBranchState(state); err != nil {
return err
}
q.Set("state", state)
}
env, err := ctx.CallAPIWithQuery("GET", "/v1"+ctx.RepoPath()+"/branches", q)
if err != nil {
return err
@ -38,8 +47,7 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
},
{
Name: "all",
Description: tr.T("cmd.branch.all.short"),
Flags: []common.Flag{},
Description: "List all branches without pagination",
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
@ -56,7 +64,7 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
Description: tr.T("cmd.branch.create.short"),
Flags: []common.Flag{
{Name: "name", Short: "n", Usage: tr.T("flag.branch.name"), Required: true},
{Name: "from", Short: "f", Usage: tr.T("flag.branch.from")},
{Name: "from", Short: "f", Usage: tr.T("flag.branch.from"), Default: "master"},
},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
@ -65,10 +73,7 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
name, _ := ctx.RequireArg("name")
from := ctx.Arg("from")
if from == "" {
var err error
if from, err = ctx.DefaultBranch(); err != nil {
return err
}
from = "master"
}
payload := map[string]interface{}{
"new_branch_name": name,
@ -102,42 +107,6 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
return ctx.Output(env)
},
},
{
Name: "all",
Description: tr.T("cmd.branch.all.short"),
Flags: []common.Flag{},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
env, err := ctx.CallAPI("GET", "/v1"+ctx.RepoPath()+"/branches/all", nil)
if err != nil {
return err
}
return ctx.Output(env)
},
},
{
Name: "set-default",
Description: tr.T("cmd.branch.set_default.short"),
Flags: []common.Flag{
{Name: "name", Short: "n", Usage: tr.T("flag.branch.name"), Required: true},
},
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
name, err := ctx.RequireArg("name")
if err != nil {
return err
}
env, err := ctx.CallAPI("PATCH", "/v1"+ctx.RepoPath()+"/branches/update_default_branch", map[string]interface{}{"name": name})
if err != nil {
return err
}
return ctx.Output(env)
},
},
{
Name: "protect",
Description: tr.T("cmd.branch.protect.short"),
@ -177,6 +146,25 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
return ctx.Output(env)
},
},
{
Name: "set-default",
Description: "Set repository default branch",
Flags: []common.Flag{
{Name: "name", Short: "n", Usage: tr.T("flag.branch.name"), Required: true},
{Name: "dry-run", Usage: "Preview the request without changing the default branch", Bool: true, Default: "false"},
},
Run: runSetDefault,
},
{
Name: "restore",
Description: "Restore a deleted branch",
Flags: []common.Flag{
{Name: "id", Short: "i", Usage: "Deleted branch ID", Required: true},
{Name: "name", Short: "n", Usage: tr.T("flag.branch.name"), Required: true},
{Name: "dry-run", Usage: "Preview the request without restoring the branch", Bool: true, Default: "false"},
},
Run: runRestore,
},
}
}
@ -186,3 +174,81 @@ func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator {
}
return i18n.Default()
}
func runSetDefault(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
name, err := ctx.RequireArg("name")
if err != nil {
return err
}
path := "/v1" + ctx.RepoPath() + "/branches/update_default_branch"
query := url.Values{}
query.Set("name", name)
if ctx.Arg("dry-run") == "true" {
return ctx.OutputData(map[string]interface{}{
"dry_run": true,
"action": "set_default_branch",
"method": "PATCH",
"path": path,
"query": query.Encode(),
})
}
env, err := ctx.CallAPIWithQuery("PATCH", path, query)
if err != nil {
return err
}
return ctx.Output(env)
}
func runRestore(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
id, err := parsePositiveInt(ctx.Arg("id"), "id")
if err != nil {
return err
}
name, err := ctx.RequireArg("name")
if err != nil {
return err
}
path := "/v1" + ctx.RepoPath() + "/branches/restore"
body := map[string]interface{}{
"branch_id": id,
"branch_name": name,
}
if ctx.Arg("dry-run") == "true" {
return ctx.OutputData(map[string]interface{}{
"dry_run": true,
"action": "restore_branch",
"method": "POST",
"path": path,
"body": body,
})
}
env, err := ctx.CallAPI("POST", path, body)
if err != nil {
return err
}
return ctx.Output(env)
}
func validateBranchState(state string) error {
switch state {
case "all", "deleted":
return nil
default:
return fmt.Errorf("invalid --state %q: use all or deleted", state)
}
}
func parsePositiveInt(value, name string) (int64, error) {
value = strings.TrimSpace(value)
parsed, err := strconv.ParseInt(value, 10, 64)
if err != nil || parsed <= 0 {
return 0, fmt.Errorf("invalid --%s %q: use a positive integer", name, value)
}
return parsed, nil
}

View File

@ -60,6 +60,56 @@ func TestBranchList(t *testing.T) {
}
}
func TestBranchListFilters(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/owner/repo/branches.json" {
t.Fatalf("unexpected path: %s", r.URL.Path)
}
if got := r.URL.Query().Get("keyword"); got != "feature" {
t.Fatalf("keyword = %q, want feature", got)
}
if got := r.URL.Query().Get("state"); got != "deleted" {
t.Fatalf("state = %q, want deleted", got)
}
writeJSON(w, map[string]interface{}{"total_count": 0, "branches": []interface{}{}})
}))
defer server.Close()
err := runShortcut(t, server, "list", map[string]string{
"page": "1", "limit": "20", "keyword": "feature", "state": "deleted",
})
if err != nil {
t.Fatalf("list with filters failed: %v", err)
}
}
func TestBranchListRejectsInvalidState(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Fatalf("invalid state should not call API, got %s %s", r.Method, r.URL.Path)
}))
defer server.Close()
err := runShortcut(t, server, "list", map[string]string{"page": "1", "limit": "20", "state": "open"})
if err == nil {
t.Fatal("expected validation error")
}
}
func TestBranchAll(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/branches/all.json" {
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
}
writeJSON(w, []interface{}{map[string]interface{}{"name": "master"}})
}))
defer server.Close()
err := runShortcut(t, server, "all", nil)
if err != nil {
t.Fatalf("all failed: %v", err)
}
}
// --- create ---
func TestBranchCreate(t *testing.T) {
@ -161,6 +211,69 @@ func TestBranchUnprotect(t *testing.T) {
}
}
func TestBranchSetDefault(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "PATCH" || r.URL.Path != "/v1/owner/repo/branches/update_default_branch.json" {
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
}
if got := r.URL.Query().Get("name"); got != "main" {
t.Fatalf("name query = %q, want main", got)
}
writeJSON(w, map[string]interface{}{"status": 0, "message": "success"})
}))
defer server.Close()
if err := runShortcut(t, server, "set-default", map[string]string{"name": "main"}); err != nil {
t.Fatalf("set-default failed: %v", err)
}
}
func TestBranchSetDefaultDryRunDoesNotCallAPI(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(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()
if err := runShortcut(t, server, "set-default", map[string]string{"name": "main", "dry-run": "true"}); err != nil {
t.Fatalf("set-default dry-run failed: %v", err)
}
}
func TestBranchRestore(t *testing.T) {
var payload map[string]interface{}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" || r.URL.Path != "/v1/owner/repo/branches/restore.json" {
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
t.Fatalf("decode body: %v", err)
}
writeJSON(w, map[string]interface{}{"status": 0, "message": "success"})
}))
defer server.Close()
if err := runShortcut(t, server, "restore", map[string]string{"id": "7", "name": "feature/deleted"}); err != nil {
t.Fatalf("restore failed: %v", err)
}
if payload["branch_id"] != float64(7) {
t.Fatalf("branch_id = %v, want 7", payload["branch_id"])
}
if payload["branch_name"] != "feature/deleted" {
t.Fatalf("branch_name = %v, want feature/deleted", payload["branch_name"])
}
}
func TestBranchRestoreValidation(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Fatalf("invalid restore should not call API, got %s %s", r.Method, r.URL.Path)
}))
defer server.Close()
if err := runShortcut(t, server, "restore", map[string]string{"id": "0", "name": "feature/deleted"}); err == nil {
t.Fatal("expected invalid id error")
}
}
// --- HTTP error paths ---
func TestBranchListHTTPError(t *testing.T) {

View File

@ -1,7 +1,7 @@
---
name: gitlink-branch
version: 1.0.0
description: "分支管理:创建、查看、删除、保护分支。当用户需要操作 GitLink 分支时触发。"
version: 1.1.0
description: "分支管理:创建、查看、过滤、删除、保护、设置默认分支、恢复已删除分支。当用户需要操作 GitLink 分支时触发。"
metadata:
requires:
bins: ["gitlink-cli"]
@ -21,10 +21,13 @@ metadata:
| Shortcut | 说明 | 操作类型 |
|----------|------|----------|
| `branch +list` | 列出仓库的所有分支 | Read |
| `branch +all` | 无分页列出仓库所有分支 | Read |
| `branch +create` | 创建新分支 | ⚠️ Write Operation |
| `branch +delete` | 删除分支 | 🔴 Destructive Operation |
| `branch +protect` | 设置分支保护规则 | ⚠️ Write Operation |
| `branch +unprotect` | 移除分支保护规则 | ⚠️ Write Operation |
| `branch +set-default` | 设置默认分支 | ⚠️ Write Operation |
| `branch +restore` | 恢复已删除分支 | ⚠️ Write Operation |
## 参数参考
@ -36,6 +39,17 @@ metadata:
| `--repo` | 是* | 仓库名称(可从 git remote 自动推断) |
| `--page, -p` | 否 | 页码(默认 `1` |
| `--limit, -l` | 否 | 每页条数(默认 `20` |
| `--keyword, -k` | 否 | 分支关键词过滤 |
| `--state, -s` | 否 | 分支状态:`all` 或 `deleted` |
| `--format` | 否 | 输出格式:`json`/`table`/`yaml` |
| `--debug` | 否 | 启用调试输出 |
### branch +all
| 参数 | 必填 | 说明 |
|------|------|------|
| `--owner` | 是* | 仓库所有者(可从 git remote 自动推断) |
| `--repo` | 是* | 仓库名称(可从 git remote 自动推断) |
| `--format` | 否 | 输出格式:`json`/`table`/`yaml` |
| `--debug` | 否 | 启用调试输出 |
@ -80,6 +94,29 @@ metadata:
| `--format` | 否 | 输出格式:`json`/`table`/`yaml` |
| `--debug` | 否 | 启用调试输出 |
### branch +set-default
| 参数 | 必填 | 说明 |
|------|------|------|
| `--name, -n` | 是 | 要设为默认分支的名称 |
| `--dry-run` | 否 | 预览请求,不修改默认分支 |
| `--owner` | 是* | 仓库所有者(可从 git remote 自动推断) |
| `--repo` | 是* | 仓库名称(可从 git remote 自动推断) |
| `--format` | 否 | 输出格式:`json`/`table`/`yaml` |
| `--debug` | 否 | 启用调试输出 |
### branch +restore
| 参数 | 必填 | 说明 |
|------|------|------|
| `--id, -i` | 是 | 已删除分支的 branch_id |
| `--name, -n` | 是 | 要恢复的分支名称 |
| `--dry-run` | 否 | 预览请求,不恢复分支 |
| `--owner` | 是* | 仓库所有者(可从 git remote 自动推断) |
| `--repo` | 是* | 仓库名称(可从 git remote 自动推断) |
| `--format` | 否 | 输出格式:`json`/`table`/`yaml` |
| `--debug` | 否 | 启用调试输出 |
> *如果在 GitLink 仓库目录下执行,`--owner` 和 `--repo` 可自动推断。
## 使用示例
@ -91,6 +128,12 @@ gitlink-cli branch +list
# 指定仓库并分页
gitlink-cli branch +list --owner Gitlink --repo forgeplus --page 1 --limit 10
# 搜索分支或查看已删除分支
gitlink-cli branch +list --owner Gitlink --repo forgeplus --keyword fix --state deleted
# 无分页列出所有分支
gitlink-cli branch +all --owner Gitlink --repo forgeplus
# 输出为 JSON
gitlink-cli branch +list --format json
@ -117,6 +160,14 @@ gitlink-cli branch +protect --name main --owner someone --repo myrepo
# 移除分支保护(仅简单分支名,含 / 的路径需通过 Web 操作)
gitlink-cli branch +unprotect --name main
# 设置默认分支(先 dry-run 预览)
gitlink-cli branch +set-default --name main --dry-run
gitlink-cli branch +set-default --name main
# 恢复已删除分支(先 dry-run 预览)
gitlink-cli branch +restore --id 7 --name feature/old --dry-run
gitlink-cli branch +restore --id 7 --name feature/old
```
## Workflow 注意事项
@ -157,6 +208,24 @@ gitlink-cli branch +unprotect --name main
2. 执行 `branch +unprotect --name <name>`
3. 输出结果。
### branch +set-defaultWrite Operation
> [!CAUTION]
> This is a **Write Operation** — confirm user intent.
1. 确认用户希望切换默认分支。
2. 先执行 `branch +set-default --name <name> --dry-run` 预览。
3. 用户确认后执行不带 `--dry-run` 的命令。
### branch +restoreWrite Operation
> [!CAUTION]
> This is a **Write Operation** — confirm user intent.
1. 通过 `branch +list --state deleted` 确认 `branch_id` 和分支名。
2. 先执行 `branch +restore --id <branch_id> --name <name> --dry-run` 预览。
3. 用户确认后执行不带 `--dry-run` 的命令。
> **注意:**`/` 的分支名(如 `feature/my-branch`)可能无法通过 CLI 解除保护(受限于 API 路由),需通过 Web 页面操作。
## References