feat(repo): add +forks and +top-counts

- repo +forks: list users who forked the repository (GET /forks,
  same community time-range flags as +watchers/+stargazers)
- repo +top-counts: repository top-line counts (tags, branches,
  commits, releases, size) via GET /top_counts

Production-verified on gitlink.org.cn (77 forks on a large repo;
counts payload matches web overview). 2 unit tests, README examples,
and bilingual i18n keys.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1os21ka23r9navae6mrro 2026-07-08 14:24:59 +00:00
parent c09645da62
commit b19d45fc6f
5 changed files with 60 additions and 0 deletions

View File

@ -233,6 +233,12 @@ gitlink-cli repo +languages --owner Gitlink --repo forgeplus
# List contributors
gitlink-cli repo +contributors --owner Gitlink --repo forgeplus
# List users who forked the repository
gitlink-cli repo +forks --owner Gitlink --repo forgeplus
# Top-line counts (tags, branches, commits, releases, size)
gitlink-cli repo +top-counts --owner Gitlink --repo forgeplus
# Show contributor code-line stats for a branch, tag, or commit
gitlink-cli repo +contributor-stats --owner Gitlink --repo forgeplus --ref master --pass-year 1

View File

@ -84,9 +84,11 @@
"cmd.repo.create.short": "Create a new repository",
"cmd.repo.delete.short": "Delete a repository",
"cmd.repo.fork.short": "Fork a repository",
"cmd.repo.forks.short": "List users who forked the repository",
"cmd.repo.info.short": "Show repository details",
"cmd.repo.list.short": "List repositories for a user or organization",
"cmd.repo.short": "Repository operations",
"cmd.repo.top_counts.short": "Show repository top-line counts (tags, branches, commits, releases, size)",
"cmd.repo.tree.short": "List repository files and directories",
"cmd.root.long": "Manage repositories, issues, pull requests, releases, CI and workflows on GitLink.",
"cmd.root.short": "GitLink CLI - command-line tool for GitLink",

View File

@ -84,9 +84,11 @@
"cmd.repo.create.short": "创建新仓库",
"cmd.repo.delete.short": "删除仓库",
"cmd.repo.fork.short": "Fork 仓库",
"cmd.repo.forks.short": "列出 fork 该仓库的用户",
"cmd.repo.info.short": "显示仓库详情",
"cmd.repo.list.short": "列出用户或组织的仓库",
"cmd.repo.short": "仓库操作",
"cmd.repo.top_counts.short": "显示仓库概览计数(标签/分支/提交/发行版/体积)",
"cmd.repo.tree.short": "列出仓库文件和目录",
"cmd.root.long": "用于管理 GitLink 上的仓库、议题、拉取请求、发布、CI 和工作流。",
"cmd.root.short": "GitLink CLI - GitLink 命令行工具",

View File

@ -151,6 +151,28 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
return runCommunityList(ctx, "stargazers")
},
},
{
Name: "forks",
Description: tr.T("cmd.repo.forks.short"),
Flags: communityListFlags(),
Run: func(ctx *common.RuntimeContext) error {
return runCommunityList(ctx, "forks")
},
},
{
Name: "top-counts",
Description: tr.T("cmd.repo.top_counts.short"),
Run: func(ctx *common.RuntimeContext) error {
if err := ctx.ResolveOwnerRepo(); err != nil {
return err
}
env, err := ctx.CallAPI("GET", ctx.RepoPath()+"/top_counts", nil)
if err != nil {
return err
}
return ctx.Output(env)
},
},
{
Name: "follow",
Description: "Follow a repository",

View File

@ -634,3 +634,31 @@ func assertEqual(t *testing.T, got interface{}, want interface{}) {
t.Fatalf("got %v (%T), want %v (%T)", got, got, want, want)
}
}
func TestRepoForksListsForkUsers(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" || r.URL.Path != "/owner/repo/forks.json" {
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
}
writeJSON(t, w, map[string]interface{}{"count": 0, "users": []interface{}{}})
}))
defer server.Close()
if err := runShortcut(t, server, "forks", map[string]string{}); err != nil {
t.Fatalf("forks failed: %v", err)
}
}
func TestRepoTopCountsUsesTopCountsEndpoint(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" || r.URL.Path != "/owner/repo/top_counts.json" {
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
}
writeJSON(t, w, map[string]interface{}{"tags_count": float64(1)})
}))
defer server.Close()
if err := runShortcut(t, server, "top-counts", map[string]string{}); err != nil {
t.Fatalf("top-counts failed: %v", err)
}
}