From 17c555620bc08ff2ee0ee78e5ed2463aad82315d Mon Sep 17 00:00:00 2001 From: co63oc <4617245+co63oc@users.noreply.github.com> Date: Tue, 9 Jun 2026 08:50:39 +0800 Subject: [PATCH] feat(shortcut): add shortcuts/ignore --- README.md | 10 +++ README.zh-CN.md | 10 +++ doc/changes/ignore-shortcut.md | 8 +++ shortcuts/ignore/ignore.go | 36 ++++++++++ shortcuts/ignore/ignore_test.go | 124 ++++++++++++++++++++++++++++++++ shortcuts/register.go | 3 + shortcuts/register_test.go | 2 +- 7 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 doc/changes/ignore-shortcut.md create mode 100644 shortcuts/ignore/ignore.go create mode 100644 shortcuts/ignore/ignore_test.go diff --git a/README.md b/README.md index b43dcd1..ccc730f 100644 --- a/README.md +++ b/README.md @@ -467,6 +467,16 @@ gitlink-cli pipeline +disable --owner Gitlink --repo forgeplus --id 7 --workflow gitlink-cli pipeline +delete --owner Gitlink --repo forgeplus --id 7 --dry-run ``` +### Ignore File Templates + +```bash +# List all available .gitignore templates +gitlink-cli ignore +list + +# Filter templates by name +gitlink-cli ignore +list --name Go +``` + ### Search ```bash diff --git a/README.zh-CN.md b/README.zh-CN.md index 265ac98..0bdfd28 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -445,6 +445,16 @@ gitlink-cli pipeline +disable --owner Gitlink --repo forgeplus --id 7 --workflow gitlink-cli pipeline +delete --owner Gitlink --repo forgeplus --id 7 --dry-run ``` +### 忽略文件模板 + +```bash +# 列出所有可用的 .gitignore 模板 +gitlink-cli ignore +list + +# 按名称筛选模板 +gitlink-cli ignore +list --name Go +``` + ### 搜索 ```bash diff --git a/doc/changes/ignore-shortcut.md b/doc/changes/ignore-shortcut.md new file mode 100644 index 0000000..35826a0 --- /dev/null +++ b/doc/changes/ignore-shortcut.md @@ -0,0 +1,8 @@ +# Ignore shortcut + +新增 `ignore` Shortcut 组,补齐 GitLink 忽略文件模板(`.gitignore`)查询: + +- `ignore +list` + +同时补充了单元测试、README 示例。 + diff --git a/shortcuts/ignore/ignore.go b/shortcuts/ignore/ignore.go new file mode 100644 index 0000000..9f5e406 --- /dev/null +++ b/shortcuts/ignore/ignore.go @@ -0,0 +1,36 @@ +package ignore + +import ( + "net/url" + + "github.com/gitlink-org/gitlink-cli/shortcuts/common" +) + +// Shortcuts returns ignore-file management shortcuts. +// +// These shortcuts provide access to the GitLink ignore-file registry, +// which lists all available .gitignore templates supported by the platform. +func Shortcuts() []*common.Shortcut { + return []*common.Shortcut{ + { + Name: "list", + Description: "List available ignore-file templates", + Flags: []common.Flag{ + {Name: "name", Short: "n", Usage: "Filter ignore templates by name"}, + }, + Run: runList, + }, + } +} + +func runList(ctx *common.RuntimeContext) error { + q := url.Values{} + if name := ctx.Arg("name"); name != "" { + q.Set("name", name) + } + env, err := ctx.CallAPIWithQuery("GET", "/ignores", q) + if err != nil { + return err + } + return ctx.Output(env) +} diff --git a/shortcuts/ignore/ignore_test.go b/shortcuts/ignore/ignore_test.go new file mode 100644 index 0000000..f1ad730 --- /dev/null +++ b/shortcuts/ignore/ignore_test.go @@ -0,0 +1,124 @@ +package ignore + +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 runShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error { + t.Helper() + shortcut := findShortcut(t, name) + ctx := &common.RuntimeContext{ + Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL}, + Owner: "owner", + Repo: "repo", + Format: "json", + Args: args, + } + if ctx.Args == nil { + ctx.Args = map[string]string{} + } + return shortcut.Run(ctx) +} + +func findShortcut(t *testing.T, name string) *common.Shortcut { + t.Helper() + for _, s := range Shortcuts() { + if s.Name == name { + return s + } + } + t.Fatalf("shortcut %q not found", name) + return nil +} + +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 TestIgnoreList(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + t.Fatalf("got method %s, want GET", r.Method) + } + if r.URL.Path != "/ignores.json" { + t.Fatalf("got path %s, want /ignores.json", r.URL.Path) + } + if got := r.URL.Query().Get("name"); got != "" { + t.Fatalf("expected no name filter, got %q", got) + } + writeJSON(t, w, map[string]interface{}{ + "ignores": []interface{}{ + map[string]interface{}{"id": 1, "name": "Go"}, + map[string]interface{}{"id": 2, "name": "Ada"}, + }, + }) + })) + defer server.Close() + + if err := runShortcut(t, server, "list", map[string]string{}); err != nil { + t.Fatalf("ignore list failed: %v", err) + } +} + +func TestIgnoreListWithNameFilter(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + t.Fatalf("got method %s, want GET", r.Method) + } + if r.URL.Path != "/ignores.json" { + t.Fatalf("got path %s, want /ignores.json", r.URL.Path) + } + if got := r.URL.Query().Get("name"); got != "Ada" { + t.Fatalf("expected name=Ada, got %q", got) + } + writeJSON(t, w, map[string]interface{}{ + "ignores": []interface{}{ + map[string]interface{}{"id": 2, "name": "Ada"}, + }, + }) + })) + defer server.Close() + + if err := runShortcut(t, server, "list", map[string]string{"name": "Ada"}); err != nil { + t.Fatalf("ignore list with name filter failed: %v", err) + } +} + +func TestIgnoreListHTTPError(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("server error")) + })) + defer server.Close() + + err := runShortcut(t, server, "list", map[string]string{}) + if err == nil { + t.Fatal("expected error for HTTP 500") + } +} + +func TestIgnoreListEmptyResult(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/ignores.json" { + t.Fatalf("got path %s, want /ignores.json", r.URL.Path) + } + writeJSON(t, w, map[string]interface{}{ + "ignores": []interface{}{}, + }) + })) + defer server.Close() + + if err := runShortcut(t, server, "list", map[string]string{"name": "NONEXISTENT"}); err != nil { + t.Fatalf("ignore list with empty result failed: %v", err) + } +} diff --git a/shortcuts/register.go b/shortcuts/register.go index 917bf85..8f8e26e 100644 --- a/shortcuts/register.go +++ b/shortcuts/register.go @@ -9,6 +9,7 @@ import ( "github.com/gitlink-org/gitlink-cli/shortcuts/common" "github.com/gitlink-org/gitlink-cli/shortcuts/compare" "github.com/gitlink-org/gitlink-cli/shortcuts/health" + "github.com/gitlink-org/gitlink-cli/shortcuts/ignore" "github.com/gitlink-org/gitlink-cli/shortcuts/issue" "github.com/gitlink-org/gitlink-cli/shortcuts/label" "github.com/gitlink-org/gitlink-cli/shortcuts/license" @@ -49,6 +50,7 @@ func RegisterAll(root *cobra.Command, translators ...*i18n.Translator) { "compare": compare.Shortcuts(), "webhook": webhook.Shortcuts(tr), "health": health.Shortcuts(tr), + "ignore": ignore.Shortcuts(), "workflow": workflow.Shortcuts(), } @@ -70,6 +72,7 @@ func RegisterAll(root *cobra.Command, translators ...*i18n.Translator) { "compare": "Compare branches, tags, or commits", "webhook": tr.T("cmd.webhook.short"), "health": "Project health data collection", + "ignore": tr.T("cmd.ignore.short"), "workflow": "AI agent workflow analysis", } diff --git a/shortcuts/register_test.go b/shortcuts/register_test.go index a8c8ce4..55410f4 100644 --- a/shortcuts/register_test.go +++ b/shortcuts/register_test.go @@ -14,7 +14,7 @@ func TestRegisterAll(t *testing.T) { "repo", "issue", "label", "license", "pr", "release", "branch", "org", "user", "search", "ci", "workflow", "compare", "member", "milestone", "pipeline", "webhook", - "health", + "health", "ignore", } groupSet := map[string]bool{}