feat(repo): 补齐仓库文件读取、搜索与批量提交工作流
This commit is contained in:
parent
31accd64d2
commit
e6f7cfd186
|
|
@ -103,7 +103,7 @@ The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans
|
|||
|
||||
| Category | Capabilities |
|
||||
|----------|-------------|
|
||||
| 📦 Repo | List, create, fork, delete repositories, search files, batch commit file changes, view repo info, insights, and interactions |
|
||||
| 📦 Repo | List, create, fork, delete repositories, read and search files, batch commit file changes, view repo info, insights, and interactions |
|
||||
| 🐛 Issue | Create, update, close, batch close/update/delete, comment on issues |
|
||||
| 🔖 Label | Create, list, update, delete issue labels |
|
||||
| 🔀 PR | Create, merge, review pull requests, view changed files |
|
||||
|
|
@ -223,6 +223,9 @@ gitlink-cli repo +info --owner Gitlink --repo forgeplus
|
|||
# Read repository README
|
||||
gitlink-cli repo +readme --owner Gitlink --repo forgeplus --ref master
|
||||
|
||||
# Read one repository file with metadata
|
||||
gitlink-cli repo +file --owner Gitlink --repo forgeplus --path README.md --ref main
|
||||
|
||||
# List repository files at root or a directory
|
||||
gitlink-cli repo +tree --owner Gitlink --repo forgeplus --ref master
|
||||
gitlink-cli repo +tree --owner Gitlink --repo forgeplus --path src --ref main
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@
|
|||
|
||||
| 分类 | 能力 |
|
||||
|------|------|
|
||||
| 📦 仓库 | 列出、创建、Fork、删除仓库,搜索文件、批量提交文件变更,查看仓库信息、洞察数据和互动状态 |
|
||||
| 📦 仓库 | 列出、创建、Fork、删除仓库,读取和搜索文件、批量提交文件变更,查看仓库信息、洞察数据和互动状态 |
|
||||
| 🐛 Issue | 创建、更新、关闭、批量关闭/更新/删除、评论 Issue |
|
||||
| 🔖 标签 | 创建、列出、更新、删除 Issue 标签 |
|
||||
| 🔀 PR | 创建、合并、Review Pull Request,查看变更文件 |
|
||||
|
|
@ -234,6 +234,9 @@ gitlink-cli repo +info --owner Gitlink --repo forgeplus
|
|||
# 读取仓库 README
|
||||
gitlink-cli repo +readme --owner Gitlink --repo forgeplus --ref master
|
||||
|
||||
# 读取指定仓库文件及元数据
|
||||
gitlink-cli repo +file --owner Gitlink --repo forgeplus --path README.md --ref main
|
||||
|
||||
# 列出仓库根目录或指定目录文件
|
||||
gitlink-cli repo +tree --owner Gitlink --repo forgeplus --ref master
|
||||
gitlink-cli repo +tree --owner Gitlink --repo forgeplus --path src --ref main
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
# Repository File Search and Batch Commit Shortcuts
|
||||
# Repository File Read, Search, and Batch Commit Shortcuts
|
||||
|
||||
## Summary
|
||||
|
||||
This change adds repository file workflow shortcuts for users and automation agents that need to find files and commit multiple file changes without manually assembling Raw API calls.
|
||||
This change adds repository file workflow shortcuts for users and automation agents that need to read file content, find files, and commit multiple file changes without manually assembling Raw API calls.
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `gitlink-cli repo +file` | Read one repository file with content, commit info, and download metadata |
|
||||
| `gitlink-cli repo +files` | Search repository files by name with optional branch, tag, or commit filtering |
|
||||
| `gitlink-cli repo +commit-files` | Commit one file operation or a batch JSON operation list through the `contents/batch` API |
|
||||
|
||||
|
|
@ -19,6 +20,12 @@ Search files on a branch:
|
|||
gitlink-cli repo +files --owner Gitlink --repo forgeplus --search README --ref main
|
||||
```
|
||||
|
||||
Read one file from a branch:
|
||||
|
||||
```bash
|
||||
gitlink-cli repo +file --owner Gitlink --repo forgeplus --path README.md --ref main
|
||||
```
|
||||
|
||||
Update one text file:
|
||||
|
||||
```bash
|
||||
|
|
@ -74,8 +81,8 @@ or an object with a `files` array. The CLI supplies `branch`, `message`, optiona
|
|||
|
||||
## Tests
|
||||
|
||||
Unit tests cover file search query mapping, single-file request bodies, base64 local file reading, JSON batch operation files, dry-run behavior, and validation failures that must not perform an API request.
|
||||
Unit tests cover direct file reads through `sub_entries`, file search query mapping, single-file request bodies, base64 local file reading, JSON batch operation files, dry-run behavior, and validation failures that must not perform an API request.
|
||||
|
||||
## 中文说明
|
||||
|
||||
本次变更补齐了仓库文件工作流中常用的两个能力:先用 `repo +files` 按文件名和分支搜索仓库文件,再用 `repo +commit-files` 把单个或多个文件变更提交到目标分支。批量提交支持创建新分支、设置提交信息、指定作者和提交者、从本地文件读取内容、对二进制内容做 base64 编码,并提供 `--dry-run` 预览请求体,适合脚本、CI 和 AI Agent 在真正写入仓库前检查即将提交的内容。验证覆盖了端点路径、查询参数、请求体字段、JSON 批量文件、base64 编码和无效参数不触网等关键路径。
|
||||
本次变更把仓库文件工作流补成了一个更完整的闭环:先用 `repo +file` 直接读取指定文件内容和元数据,用 `repo +files` 按文件名和分支搜索仓库文件,再用 `repo +commit-files` 把单个或多个文件变更提交到目标分支。批量提交支持创建新分支、设置提交信息、指定作者和提交者、从本地文件读取内容、对二进制内容做 base64 编码,并提供 `--dry-run` 预览请求体,适合脚本、CI 和 AI Agent 在真正写入仓库前检查即将提交的内容。验证覆盖了文件读取、端点路径、查询参数、请求体字段、JSON 批量文件、base64 编码和无效参数不触网等关键路径。
|
||||
|
|
|
|||
|
|
@ -84,6 +84,15 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "file",
|
||||
Description: "Show repository file content and metadata",
|
||||
Flags: []common.Flag{
|
||||
{Name: "path", Short: "p", Usage: "Repository file path", Required: true},
|
||||
{Name: "ref", Short: "r", Usage: "Branch, tag, or commit SHA", Default: "master"},
|
||||
},
|
||||
Run: runFile,
|
||||
},
|
||||
{
|
||||
Name: "tree",
|
||||
Description: tr.T("cmd.repo.tree.short"),
|
||||
|
|
@ -95,15 +104,7 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
ref := ctx.Arg("ref")
|
||||
if ref == "" {
|
||||
ref = "master"
|
||||
}
|
||||
if path := ctx.Arg("path"); path != "" {
|
||||
q.Set("filepath", path)
|
||||
}
|
||||
q.Set("ref", ref)
|
||||
q := repoSubEntriesQuery(ctx.Arg("path"), ctx.Arg("ref"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", ctx.RepoPath()+"/sub_entries", q)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -314,6 +315,22 @@ type repoFileOperation struct {
|
|||
FilePath string `json:"file_path"`
|
||||
}
|
||||
|
||||
func runFile(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
filePath, err := requiredRepoString(ctx, "path")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := repoSubEntriesQuery(filePath, ctx.Arg("ref"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", ctx.RepoPath()+"/sub_entries", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func runFiles(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
|
|
@ -532,6 +549,19 @@ func requiredRepoString(ctx *common.RuntimeContext, name string) (string, error)
|
|||
return value, nil
|
||||
}
|
||||
|
||||
func repoSubEntriesQuery(path, ref string) url.Values {
|
||||
q := url.Values{}
|
||||
if path = strings.TrimSpace(path); path != "" {
|
||||
q.Set("filepath", path)
|
||||
}
|
||||
ref = strings.TrimSpace(ref)
|
||||
if ref == "" {
|
||||
ref = "master"
|
||||
}
|
||||
q.Set("ref", ref)
|
||||
return q
|
||||
}
|
||||
|
||||
func runLanguages(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -154,6 +154,42 @@ func TestRepoReadmeUsesRepositoryReadmeEndpoint(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRepoFileUsesSubEntriesFileEndpoint(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/owner/repo/sub_entries.json")
|
||||
assertEqual(t, r.URL.Query().Get("filepath"), "README.md")
|
||||
assertEqual(t, r.URL.Query().Get("ref"), "main")
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"entries": map[string]interface{}{
|
||||
"name": "README.md",
|
||||
"path": "README.md",
|
||||
"type": "file",
|
||||
"content": "# docs\n",
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "file", map[string]string{
|
||||
"path": "README.md",
|
||||
"ref": "main",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("file shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoFileRequiresPath(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("missing path should not call API, got: %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
if err := runShortcut(t, server, "file", map[string]string{"ref": "main"}); err == nil {
|
||||
t.Fatal("expected missing path error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoTreeListsRootOnDefaultRef(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/owner/repo/sub_entries.json")
|
||||
|
|
|
|||
Loading…
Reference in New Issue