diff --git a/README.md b/README.md index b4c55e1..08cd1ff 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,10 @@ gitlink-cli repo +list # View repository info gitlink-cli repo +info --owner Gitlink --repo forgeplus +# 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 + # Create a repository gitlink-cli repo +create -n my-project -d "Project description" diff --git a/README.zh-CN.md b/README.zh-CN.md index f426948..e61b56f 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -162,6 +162,10 @@ gitlink-cli repo +list # 查看仓库信息 gitlink-cli repo +info --owner Gitlink --repo forgeplus +# 列出仓库根目录或指定目录文件 +gitlink-cli repo +tree --owner Gitlink --repo forgeplus --ref master +gitlink-cli repo +tree --owner Gitlink --repo forgeplus --path src --ref main + # 创建仓库 gitlink-cli repo +create -n my-project -d "项目描述" diff --git a/doc/design.md b/doc/design.md index d25295b..3a0e606 100644 --- a/doc/design.md +++ b/doc/design.md @@ -40,7 +40,7 @@ gitlink-cli/ │ ├── common/ │ │ ├── types.go # Shortcut / Flag / RuntimeContext 定义 │ │ └── runner.go # CallAPI / PaginateAll / ResolveOwnerRepo -│ ├── repo/ # repo +create / +clone / +fork / +list / +info +│ ├── repo/ # repo +create / +clone / +fork / +list / +info / +tree │ ├── issue/ # issue +list / +create / +view / +close / +comment │ ├── pr/ # pr +list / +create / +view / +merge / +review │ ├── release/ # release +list / +create / +download @@ -78,7 +78,7 @@ gitlink-cli/ | 领域 | Shortcuts | 数量 | |------|-----------|------| -| repo | `+create` `+clone` `+fork` `+list` `+info` `+delete` `+settings` | 7 | +| repo | `+create` `+clone` `+fork` `+list` `+info` `+tree` `+delete` `+settings` | 8 | | issue | `+list` `+create` `+view` `+update` `+close` `+comment` `+assign` `+label` | 8 | | pr | `+list` `+create` `+view` `+merge` `+close` `+review` `+files` `+diff` | 8 | | release | `+list` `+create` `+view` `+delete` `+download` | 5 | diff --git a/shortcuts/repo/repo.go b/shortcuts/repo/repo.go index 75091a4..4fe4e1e 100644 --- a/shortcuts/repo/repo.go +++ b/shortcuts/repo/repo.go @@ -52,6 +52,31 @@ func Shortcuts() []*common.Shortcut { return ctx.Output(env) }, }, + { + Name: "tree", + Description: "List repository files and directories", + Flags: []common.Flag{ + {Name: "path", Short: "p", Usage: "Directory path to list (default: repository root)"}, + {Name: "ref", Short: "r", Usage: "Branch, tag, or commit ref", Default: "master"}, + }, + Run: func(ctx *common.RuntimeContext) error { + if err := ctx.ResolveOwnerRepo(); err != nil { + return err + } + q := url.Values{} + ref := ctx.Arg("ref") + if ref == "" { + ref = "master" + } + q.Set("filepath", ctx.Arg("path")) + q.Set("ref", ref) + env, err := ctx.CallAPIWithQuery("GET", ctx.RepoPath()+"/sub_entries", q) + if err != nil { + return err + } + return ctx.Output(env) + }, + }, { Name: "create", Description: "Create a new repository", diff --git a/shortcuts/repo/repo_test.go b/shortcuts/repo/repo_test.go new file mode 100644 index 0000000..a139fc7 --- /dev/null +++ b/shortcuts/repo/repo_test.go @@ -0,0 +1,111 @@ +package repo + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gitlink-org/gitlink-cli/internal/client" + "github.com/gitlink-org/gitlink-cli/shortcuts/common" +) + +func TestRepoTreeListsRootOnDefaultRef(t *testing.T) { + var filepath string + var ref string + server := newRepoTestServer(t, func(w http.ResponseWriter, r *http.Request) { + assertRequest(t, r, "GET", "/owner/repo/sub_entries.json") + filepath = r.URL.Query().Get("filepath") + ref = r.URL.Query().Get("ref") + writeJSON(t, w, map[string]interface{}{ + "entries": []map[string]interface{}{ + {"name": "README.md", "type": "file"}, + }, + }) + }) + defer server.Close() + + if err := runRepoShortcut(t, server, "tree", map[string]string{}); err != nil { + t.Fatalf("tree shortcut failed: %v", err) + } + + assertEqual(t, filepath, "") + assertEqual(t, ref, "master") +} + +func TestRepoTreeUsesPathAndRef(t *testing.T) { + var filepath string + var ref string + server := newRepoTestServer(t, func(w http.ResponseWriter, r *http.Request) { + assertRequest(t, r, "GET", "/owner/repo/sub_entries.json") + filepath = r.URL.Query().Get("filepath") + ref = r.URL.Query().Get("ref") + writeJSON(t, w, map[string]interface{}{ + "entries": []map[string]interface{}{ + {"name": "main.go", "type": "file"}, + }, + }) + }) + defer server.Close() + + if err := runRepoShortcut(t, server, "tree", map[string]string{"path": "cmd", "ref": "main"}); err != nil { + t.Fatalf("tree shortcut failed: %v", err) + } + + assertEqual(t, filepath, "cmd") + assertEqual(t, ref, "main") +} + +func runRepoShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error { + t.Helper() + shortcut := findRepoShortcut(t, name) + ctx := &common.RuntimeContext{ + Client: &client.Client{ + HTTP: server.Client(), + BaseURL: server.URL, + }, + Owner: "owner", + Repo: "repo", + Format: "json", + Args: args, + } + return shortcut.Run(ctx) +} + +func findRepoShortcut(t *testing.T, name string) *common.Shortcut { + t.Helper() + for _, shortcut := range Shortcuts() { + if shortcut.Name == name { + return shortcut + } + } + t.Fatalf("shortcut %q not found", name) + return nil +} + +func newRepoTestServer(t *testing.T, handler http.HandlerFunc) *httptest.Server { + t.Helper() + return httptest.NewServer(handler) +} + +func writeJSON(t *testing.T, w http.ResponseWriter, payload interface{}) { + t.Helper() + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(payload); err != nil { + t.Fatalf("failed to write response: %v", err) + } +} + +func assertRequest(t *testing.T, r *http.Request, method, path string) { + t.Helper() + if r.Method != method || r.URL.Path != path { + t.Fatalf("got request %s %s, want %s %s", r.Method, r.URL.Path, method, path) + } +} + +func assertEqual(t *testing.T, got interface{}, want interface{}) { + t.Helper() + if got != want { + t.Fatalf("got %v (%T), want %v (%T)", got, got, want, want) + } +} diff --git a/skills/gitlink-repo/SKILL.md b/skills/gitlink-repo/SKILL.md index 9fabb8e..abae300 100644 --- a/skills/gitlink-repo/SKILL.md +++ b/skills/gitlink-repo/SKILL.md @@ -22,6 +22,7 @@ metadata: |----------|------|----------| | `repo +list` | 仓库列表 | 否(公开项目) | | `repo +info` | 仓库详情 | 否(公开项目) | +| `repo +tree` | 仓库文件树 | 否(公开项目) | | `repo +create` | 创建仓库 | 是 | | `repo +fork` | Fork 仓库 | 是 | | `repo +delete` | 删除仓库 | 是 | @@ -39,6 +40,10 @@ gitlink-cli repo +info # 列出用户的仓库 gitlink-cli repo +list --user zhangsan +# 列出仓库根目录或指定目录 +gitlink-cli repo +tree --owner Gitlink --repo forgeplus --ref master +gitlink-cli repo +tree --owner Gitlink --repo forgeplus --path src --ref main + # 创建仓库 gitlink-cli repo +create --name my-project --description "项目描述" diff --git a/skills/gitlink-repo/references/gitlink-repo-tree.md b/skills/gitlink-repo/references/gitlink-repo-tree.md new file mode 100644 index 0000000..fe8efb4 --- /dev/null +++ b/skills/gitlink-repo/references/gitlink-repo-tree.md @@ -0,0 +1,42 @@ +# repo +tree + +> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。 + +列出 GitLink 仓库根目录或指定目录下的文件和子目录。该命令封装 `sub_entries` API,适合项目结构检查、文档检查、科研复现性分析和 Agent 自动化报告。 + +## 命令 + +```bash +# 列出仓库根目录 +gitlink-cli repo +tree --owner someone --repo myrepo + +# 指定分支、标签或提交 +gitlink-cli repo +tree --owner someone --repo myrepo --ref main + +# 列出指定目录 +gitlink-cli repo +tree --owner someone --repo myrepo --path src --ref main + +# 输出为 JSON +gitlink-cli repo +tree --owner someone --repo myrepo --format json +``` + +## 参数 + +| 参数 | 必填 | 说明 | +|------|------|------| +| `--path, -p` | 否 | 要列出的目录路径,默认为仓库根目录 | +| `--ref, -r` | 否 | 分支、标签或提交引用,默认 `master` | +| `--owner` | 否 | 全局参数 - 仓库所有者,可从 git remote 自动解析 | +| `--repo` | 否 | 全局参数 - 仓库名称,可从 git remote 自动解析 | +| `--format` | 否 | 输出格式:`json`/`table`/`yaml` | +| `--debug` | 否 | 启用调试输出 | + +## Notes + +- GitLink 仓库常见默认分支是 `master`,镜像仓库也可能使用 `main`。如果根目录返回不存在,请显式指定 `--ref main` 或从 `repo +info` 的 `default_branch` 字段确认。 +- AI Agent 场景建议使用 `--format json`,便于读取 `data.entries` 中的文件名、路径、类型和 SHA。 + +## References + +- [gitlink-repo](../SKILL.md) +- [gitlink-shared](../../gitlink-shared/SKILL.md)