feat(commit): add commit inspection shortcuts
This commit is contained in:
parent
ef7a2c6ac1
commit
79fb167218
|
|
@ -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, view repo info, insights, and interactions |
|
||||
| 📦 Repo | List, create, fork, delete repositories, view repo info, commits, insights, and interactions |
|
||||
| 🐛 Issue | Create, update, close, batch close, comment on issues |
|
||||
| 🔖 Label | Create, list, update, delete issue labels |
|
||||
| 🔀 PR | Create, merge, review pull requests, view changed files |
|
||||
|
|
@ -219,6 +219,11 @@ gitlink-cli repo +list
|
|||
# View repository info
|
||||
gitlink-cli repo +info --owner Gitlink --repo forgeplus
|
||||
|
||||
# Inspect commits and blame
|
||||
gitlink-cli commit +list --owner Gitlink --repo forgeplus --sha master
|
||||
gitlink-cli commit +diff --owner Gitlink --repo forgeplus --sha <sha>
|
||||
gitlink-cli commit +blame --owner Gitlink --repo forgeplus --sha master --filepath README.md
|
||||
|
||||
# Read repository README
|
||||
gitlink-cli repo +readme --owner Gitlink --repo forgeplus --ref master
|
||||
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@
|
|||
|
||||
| 分类 | 能力 |
|
||||
|------|------|
|
||||
| 📦 仓库 | 列出、创建、Fork、删除仓库,查看仓库信息、洞察数据和互动状态 |
|
||||
| 📦 仓库 | 列出、创建、Fork、删除仓库,查看仓库信息、提交、洞察数据和互动状态 |
|
||||
| 🐛 Issue | 创建、更新、关闭、批量关闭、评论 Issue |
|
||||
| 🔖 标签 | 创建、列出、更新、删除 Issue 标签 |
|
||||
| 🔀 PR | 创建、合并、Review Pull Request,查看变更文件 |
|
||||
|
|
@ -230,6 +230,11 @@ gitlink-cli repo +list
|
|||
# 查看仓库信息
|
||||
gitlink-cli repo +info --owner Gitlink --repo forgeplus
|
||||
|
||||
# 查看提交、diff 和 blame
|
||||
gitlink-cli commit +list --owner Gitlink --repo forgeplus --sha master
|
||||
gitlink-cli commit +diff --owner Gitlink --repo forgeplus --sha <sha>
|
||||
gitlink-cli commit +blame --owner Gitlink --repo forgeplus --sha master --filepath README.md
|
||||
|
||||
# 读取仓库 README
|
||||
gitlink-cli repo +readme --owner Gitlink --repo forgeplus --ref master
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
# Commit Inspect Shortcuts
|
||||
|
||||
Added `gitlink-cli commit` for read-only commit inspection.
|
||||
|
||||
| Command | Endpoint |
|
||||
| --- | --- |
|
||||
| `commit +list` | `GET /v1/{owner}/{repo}/commits` |
|
||||
| `commit +files` | `GET /v1/{owner}/{repo}/commits/{sha}/files` |
|
||||
| `commit +diff` | `GET /v1/{owner}/{repo}/commits/{sha}/diff` |
|
||||
| `commit +blame` | `GET /v1/{owner}/{repo}/blame` |
|
||||
|
||||
Validation:
|
||||
|
||||
```bash
|
||||
GOPROXY=https://goproxy.cn,direct go test ./shortcuts/commit ./shortcuts
|
||||
go vet ./shortcuts/commit ./shortcuts
|
||||
go run . commit --help
|
||||
```
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package commit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
// Shortcuts returns repository commit inspection shortcuts.
|
||||
func Shortcuts() []*common.Shortcut {
|
||||
return []*common.Shortcut{
|
||||
{
|
||||
Name: "list",
|
||||
Description: "List repository commits",
|
||||
Flags: []common.Flag{
|
||||
{Name: "sha", Short: "s", Usage: "Branch, tag, or commit SHA"},
|
||||
{Name: "page", Short: "p", Usage: "Page number", Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: "Items per page", Default: "20"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
setQuery(q, "sha", ctx.Arg("sha"))
|
||||
setQuery(q, "page", ctx.Arg("page"))
|
||||
setQuery(q, "limit", ctx.Arg("limit"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", commitRepoPath(ctx)+"/commits", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "files",
|
||||
Description: "List changed files for a commit",
|
||||
Flags: []common.Flag{
|
||||
{Name: "sha", Short: "s", Usage: "Commit SHA", Required: true},
|
||||
{Name: "filepath", Short: "f", Usage: "Filter by file path"},
|
||||
{Name: "page", Short: "p", Usage: "Page number", Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: "Items per page", Default: "20"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
sha, err := ctx.RequireArg("sha")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
setQuery(q, "filepath", ctx.Arg("filepath"))
|
||||
setQuery(q, "page", ctx.Arg("page"))
|
||||
setQuery(q, "limit", ctx.Arg("limit"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf("%s/commits/%s/files", commitRepoPath(ctx), url.PathEscape(sha)), q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "diff",
|
||||
Description: "Show diff for a commit",
|
||||
Flags: []common.Flag{
|
||||
{Name: "sha", Short: "s", Usage: "Commit SHA", Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
sha, err := ctx.RequireArg("sha")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/commits/%s/diff", commitRepoPath(ctx), url.PathEscape(sha)), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "blame",
|
||||
Description: "Show blame information for a file",
|
||||
Flags: []common.Flag{
|
||||
{Name: "sha", Short: "s", Usage: "Branch, tag, or commit SHA", Required: true},
|
||||
{Name: "filepath", Short: "f", Usage: "File path", Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
sha, err := ctx.RequireArg("sha")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filepath, err := ctx.RequireArg("filepath")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("sha", sha)
|
||||
q.Set("filepath", filepath)
|
||||
env, err := ctx.CallAPIWithQuery("GET", commitRepoPath(ctx)+"/blame", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func commitRepoPath(ctx *common.RuntimeContext) string {
|
||||
return fmt.Sprintf("/v1/%s/%s", ctx.Owner, ctx.Repo)
|
||||
}
|
||||
|
||||
func setQuery(q url.Values, key, value string) {
|
||||
if value != "" {
|
||||
q.Set(key, value)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package commit
|
||||
|
||||
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 TestCommitList(t *testing.T) {
|
||||
server := newCommitServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/v1/owner/repo/commits.json")
|
||||
assertEqual(t, r.URL.Query().Get("sha"), "master")
|
||||
assertEqual(t, r.URL.Query().Get("page"), "2")
|
||||
writeJSON(t, w, map[string]interface{}{"total_count": 0, "commits": []interface{}{}})
|
||||
})
|
||||
defer server.Close()
|
||||
if err := runCommitShortcut(t, server, "list", map[string]string{"sha": "master", "page": "2", "limit": "20"}); err != nil {
|
||||
t.Fatalf("list failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitFiles(t *testing.T) {
|
||||
server := newCommitServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/v1/owner/repo/commits/abc/files.json")
|
||||
assertEqual(t, r.URL.Query().Get("filepath"), "README.md")
|
||||
writeJSON(t, w, map[string]interface{}{"files": []interface{}{}})
|
||||
})
|
||||
defer server.Close()
|
||||
if err := runCommitShortcut(t, server, "files", map[string]string{"sha": "abc", "filepath": "README.md"}); err != nil {
|
||||
t.Fatalf("files failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitDiff(t *testing.T) {
|
||||
server := newCommitServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/v1/owner/repo/commits/abc/diff.json")
|
||||
writeJSON(t, w, map[string]interface{}{"files": []interface{}{}})
|
||||
})
|
||||
defer server.Close()
|
||||
if err := runCommitShortcut(t, server, "diff", map[string]string{"sha": "abc"}); err != nil {
|
||||
t.Fatalf("diff failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitBlame(t *testing.T) {
|
||||
server := newCommitServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/v1/owner/repo/blame.json")
|
||||
assertEqual(t, r.URL.Query().Get("sha"), "master")
|
||||
assertEqual(t, r.URL.Query().Get("filepath"), "README.md")
|
||||
writeJSON(t, w, map[string]interface{}{"file_name": "README.md"})
|
||||
})
|
||||
defer server.Close()
|
||||
if err := runCommitShortcut(t, server, "blame", map[string]string{"sha": "master", "filepath": "README.md"}); err != nil {
|
||||
t.Fatalf("blame failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func runCommitShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error {
|
||||
t.Helper()
|
||||
var shortcut *common.Shortcut
|
||||
for _, s := range Shortcuts() {
|
||||
if s.Name == name {
|
||||
shortcut = s
|
||||
}
|
||||
}
|
||||
if shortcut == nil {
|
||||
t.Fatalf("shortcut %q not found", name)
|
||||
}
|
||||
return shortcut.Run(&common.RuntimeContext{Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL}, Owner: "owner", Repo: "repo", Format: "json", Args: args})
|
||||
}
|
||||
|
||||
func newCommitServer(t *testing.T, handler http.HandlerFunc) *httptest.Server {
|
||||
t.Helper()
|
||||
return httptest.NewServer(handler)
|
||||
}
|
||||
|
||||
func assertRequest(t *testing.T, r *http.Request, method, path string) {
|
||||
t.Helper()
|
||||
if r.Method != method || r.URL.Path != path {
|
||||
t.Fatalf("got %s %s, want %s %s", r.Method, r.URL.Path, method, path)
|
||||
}
|
||||
}
|
||||
|
||||
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("write json: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertEqual(t *testing.T, got, want interface{}) {
|
||||
t.Helper()
|
||||
if got != want {
|
||||
t.Fatalf("got %v (%T), want %v (%T)", got, got, want, want)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/gitlink-org/gitlink-cli/internal/i18n"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/branch"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/ci"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/commit"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/compare"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/dataset"
|
||||
|
|
@ -36,6 +37,7 @@ func RegisterAll(root *cobra.Command, translators ...*i18n.Translator) {
|
|||
}
|
||||
groups := map[string][]*common.Shortcut{
|
||||
"repo": repo.Shortcuts(tr),
|
||||
"commit": commit.Shortcuts(),
|
||||
"issue": issue.Shortcuts(tr),
|
||||
"label": label.Shortcuts(),
|
||||
"license": license.Shortcuts(),
|
||||
|
|
@ -60,6 +62,7 @@ func RegisterAll(root *cobra.Command, translators ...*i18n.Translator) {
|
|||
|
||||
descriptions := map[string]string{
|
||||
"repo": tr.T("cmd.repo.short"),
|
||||
"commit": "Commit inspection operations",
|
||||
"issue": tr.T("cmd.issue.short"),
|
||||
"label": "Issue label operations",
|
||||
"license": "License operations",
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ func TestRegisterAll(t *testing.T) {
|
|||
"repo", "issue", "label", "license", "pr", "profile", "release", "branch",
|
||||
"org", "user", "search", "ci", "workflow",
|
||||
"compare", "member", "milestone", "pipeline", "webhook",
|
||||
"dataset", "health", "ignore",
|
||||
"dataset", "health", "ignore", "commit",
|
||||
}
|
||||
|
||||
groupSet := map[string]bool{}
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ skills/
|
|||
|-------|------|----------|
|
||||
| **gitlink-shared** | 认证、全局参数、API 参考、安全规则、分支约定 | `auth login`, `auth status` |
|
||||
| **gitlink-repo** | 仓库管理与洞察 | `repo +list`, `repo +info`, `repo +languages`, `repo +contributors`, `repo +code-stats`, `repo +follow`, `repo +like` |
|
||||
| **gitlink-commit** | 提交审查 | `commit +list`, `commit +files`, `commit +diff`, `commit +blame` |
|
||||
| **gitlink-issue** | Issue 管理 | `issue +create`, `issue +list`, `issue +view`, `issue +close`, `issue +batch-close` |
|
||||
| **gitlink-pr** | Pull Request | `pr +list`, `pr +create`, `pr +view`, `pr +merge`, `pr +versions`, `pr +version-diff`, `pr +reviews`, `pr +review` |
|
||||
| **gitlink-member** | 仓库成员管理 | `member +list`, `member +add`, `member +batch-add`, `member +role`, `member +invite-link` |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
name: gitlink-commit
|
||||
version: 1.0.0
|
||||
description: "GitLink 提交审查:提交列表、单个提交文件、diff 与 blame 查询。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
cliHelp: "gitlink-cli commit --help"
|
||||
---
|
||||
|
||||
# gitlink-commit
|
||||
|
||||
**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md)。**
|
||||
|
||||
## Shortcuts
|
||||
|
||||
| Shortcut | 说明 |
|
||||
| --- | --- |
|
||||
| `commit +list` | 查询提交列表 |
|
||||
| `commit +files` | 查询单个提交变更文件 |
|
||||
| `commit +diff` | 查询单个提交 diff |
|
||||
| `commit +blame` | 查询文件 blame |
|
||||
|
||||
## 示例
|
||||
|
||||
```bash
|
||||
gitlink-cli commit +list --owner Gitlink --repo forgeplus --sha master --page 1 --limit 20
|
||||
gitlink-cli commit +files --owner Gitlink --repo forgeplus --sha <sha> --filepath README.md
|
||||
gitlink-cli commit +diff --owner Gitlink --repo forgeplus --sha <sha>
|
||||
gitlink-cli commit +blame --owner Gitlink --repo forgeplus --sha master --filepath README.md
|
||||
```
|
||||
|
||||
全部命令均为只读,适合代码审查、变更追踪、科研仓库复现和 Agent 预检。
|
||||
Loading…
Reference in New Issue