forked from Gitlink/gitlink-cli
Compare commits
2 Commits
master
...
feat/task-
| Author | SHA1 | Date |
|---|---|---|
|
|
b9f54cb50a | |
|
|
b9f4d13a90 |
|
|
@ -0,0 +1,110 @@
|
|||
package commit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
func Shortcuts() []*common.Shortcut {
|
||||
return []*common.Shortcut{
|
||||
{
|
||||
Name: "list",
|
||||
Description: "List commits in a repository",
|
||||
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{}
|
||||
q.Set("page", ctx.Arg("page"))
|
||||
q.Set("limit", ctx.Arg("limit"))
|
||||
if sha := ctx.Arg("sha"); sha != "" {
|
||||
q.Set("sha", sha)
|
||||
}
|
||||
env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf("/v1/%s/%s/commits", ctx.Owner, ctx.Repo), q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "view",
|
||||
Description: "View files changed in a commit",
|
||||
Flags: []common.Flag{
|
||||
{Name: "sha", Short: "s", Usage: "Commit SHA", Required: true},
|
||||
{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{}
|
||||
q.Set("page", ctx.Arg("page"))
|
||||
q.Set("limit", ctx.Arg("limit"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf("/v1/%s/%s/commits/%s/files", ctx.Owner, ctx.Repo, 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("/v1/%s/%s/commits/%s/diff", ctx.Owner, ctx.Repo, sha), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "blame",
|
||||
Description: "Show blame for a file",
|
||||
Flags: []common.Flag{
|
||||
{Name: "path", Short: "p", Usage: "File path", Required: true},
|
||||
{Name: "sha", Short: "s", Usage: "Branch, tag, or commit SHA", Default: "master"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
filePath, err := ctx.RequireArg("path")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("filepath", filePath)
|
||||
q.Set("sha", ctx.Arg("sha"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf("/v1/%s/%s/blame", ctx.Owner, ctx.Repo), q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
package commit
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"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 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/commits.json" {
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"total_count": 1,
|
||||
"commits": []map[string]interface{}{
|
||||
{"sha": "abc123", "commit_message": "initial commit"},
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runCommitShortcut(t, server, "list", map[string]string{})
|
||||
if err != nil {
|
||||
t.Fatalf("list shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitListWithSHA(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/commits.json" {
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
if r.URL.Query().Get("sha") != "develop" {
|
||||
t.Fatalf("expected sha=develop, got %s", r.URL.Query().Get("sha"))
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"total_count": 0,
|
||||
"commits": []map[string]interface{}{},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runCommitShortcut(t, server, "list", map[string]string{"sha": "develop"})
|
||||
if err != nil {
|
||||
t.Fatalf("list with sha failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitView(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" && r.URL.Path == "/v1/owner/repo/commits/abc123/files.json" {
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"file_nums": 1,
|
||||
"files": []map[string]interface{}{
|
||||
{"filename": "main.go", "additions": 10, "deletions": 2},
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runCommitShortcut(t, server, "view", map[string]string{"sha": "abc123"})
|
||||
if err != nil {
|
||||
t.Fatalf("view shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitDiff(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" && r.URL.Path == "/v1/owner/repo/commits/abc123/diff.json" {
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"file_nums": 1,
|
||||
"total_addition": 10,
|
||||
"total_deletion": 2,
|
||||
"files": []map[string]interface{}{{"name": "main.go"}},
|
||||
})
|
||||
return
|
||||
}
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runCommitShortcut(t, server, "diff", map[string]string{"sha": "abc123"})
|
||||
if err != nil {
|
||||
t.Fatalf("diff shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitBlame(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/blame.json" {
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
if r.URL.Query().Get("filepath") != "main.go" {
|
||||
t.Fatalf("expected filepath=main.go, got %s", r.URL.Query().Get("filepath"))
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"file_name": "main.go",
|
||||
"num_lines": 20,
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runCommitShortcut(t, server, "blame", map[string]string{"path": "main.go", "sha": "master"})
|
||||
if err != nil {
|
||||
t.Fatalf("blame shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// === helpers ===
|
||||
|
||||
func runCommitShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error {
|
||||
t.Helper()
|
||||
shortcut := findCommitShortcut(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 findCommitShortcut(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 assertEqual(t *testing.T, got, want interface{}) {
|
||||
t.Helper()
|
||||
if fmt.Sprintf("%v", got) != fmt.Sprintf("%v", want) {
|
||||
t.Fatalf("got %v (%T), want %v (%T)", got, got, want, want)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"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/issue"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/label"
|
||||
|
|
@ -30,6 +31,7 @@ func RegisterAll(root *cobra.Command) {
|
|||
"user": user.Shortcuts(),
|
||||
"search": search.Shortcuts(),
|
||||
"ci": ci.Shortcuts(),
|
||||
"commit": commit.Shortcuts(),
|
||||
"milestone": milestone.Shortcuts(),
|
||||
"webhook": webhook.Shortcuts(),
|
||||
"label": label.Shortcuts(),
|
||||
|
|
@ -45,6 +47,7 @@ func RegisterAll(root *cobra.Command) {
|
|||
"user": "User operations",
|
||||
"search": "Search operations",
|
||||
"ci": "CI/CD operations",
|
||||
"commit": "Commit operations",
|
||||
"milestone": "Milestone operations",
|
||||
"webhook": "Webhook operations",
|
||||
"label": "Issue label operations",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
# 展示页部署命令(在本地终端手动执行)
|
||||
|
||||
# 1. 用 SCP 上传展示页到 ECS(需要手动输入密码:pd1@YwC#WRFVHkXc8nvu!4)
|
||||
scp "d:/自用/self/word/大三下/软件演化/gitlink-cli/showcase/index.html" root@121.41.210.165:/opt/showcase/index.html
|
||||
|
||||
# 2. SSH 到 ECS(密码:pd1@YwC#WRFVHkXc8nvu!4)
|
||||
ssh root@121.41.210.165
|
||||
|
||||
# 登录后在 ECS 上执行:
|
||||
mkdir -p /opt/showcase
|
||||
docker run -d --name showcase -p 8080:80 -v /opt/showcase:/usr/share/nginx/html:ro --restart unless-stopped nginx:alpine
|
||||
|
||||
# 3. 访问
|
||||
# 浏览器打开 http://121.41.210.165:8080
|
||||
|
|
@ -0,0 +1,342 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>gitlink-cli 功能增强展示</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0d1117; color: #c9d1d9; line-height: 1.6; }
|
||||
.container { max-width: 960px; margin: 0 auto; padding: 40px 20px; }
|
||||
h1 { font-size: 2em; color: #58a6ff; margin-bottom: 8px; }
|
||||
h2 { font-size: 1.4em; color: #79c0ff; margin: 36px 0 16px; border-bottom: 1px solid #21262d; padding-bottom: 8px; }
|
||||
.subtitle { color: #8b949e; margin-bottom: 32px; font-size: 1.1em; }
|
||||
.stats { display: flex; gap: 20px; margin: 24px 0; flex-wrap: wrap; }
|
||||
.stat { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 20px 28px; text-align: center; flex: 1; min-width: 120px; }
|
||||
.stat .number { font-size: 2.2em; font-weight: bold; color: #58a6ff; }
|
||||
.stat .label { color: #8b949e; font-size: 0.85em; margin-top: 4px; }
|
||||
.stat.new .number { color: #3fb950; }
|
||||
table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 0.9em; }
|
||||
th, td { padding: 10px 12px; text-align: left; border-bottom: 1px solid #21262d; }
|
||||
th { background: #161b22; color: #79c0ff; font-weight: 600; }
|
||||
tr:hover { background: #161b22; }
|
||||
.tag { display: inline-block; background: #1f6feb33; color: #58a6ff; padding: 2px 8px; border-radius: 12px; font-size: 0.85em; margin: 2px; }
|
||||
.person { font-weight: 500; }
|
||||
.p-a { color: #f0883e; }
|
||||
.p-b { color: #a371f7; }
|
||||
.p-c { color: #3fb950; }
|
||||
code { background: #161b22; padding: 2px 6px; border-radius: 4px; font-size: 0.88em; color: #e6edf3; }
|
||||
pre { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; overflow-x: auto; margin: 12px 0; font-size: 0.85em; line-height: 1.4; }
|
||||
.demo { margin: 20px 0; }
|
||||
.demo h4 { color: #79c0ff; margin-bottom: 8px; font-size: 1em; }
|
||||
.demo pre { position: relative; }
|
||||
.demo .cmd { color: #3fb950; }
|
||||
.team { display: flex; gap: 16px; margin: 16px 0; flex-wrap: wrap; }
|
||||
.member { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; flex: 1; min-width: 240px; }
|
||||
.member .name { font-weight: bold; margin-bottom: 6px; font-size: 1.05em; }
|
||||
.member .modules { color: #8b949e; font-size: 0.88em; }
|
||||
.member .count { display: inline-block; background: #1f6feb33; color: #58a6ff; padding: 2px 10px; border-radius: 12px; font-size: 0.8em; margin-top: 6px; }
|
||||
.arch-box { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 24px; margin: 16px 0; text-align: center; }
|
||||
.arch-box pre { text-align: left; display: inline-block; margin: 0; border: none; background: none; color: #c9d1d9; font-size: 0.88em; }
|
||||
.arch-box .hl { color: #f0883e; font-weight: bold; }
|
||||
.test-pass { color: #3fb950; }
|
||||
.footer { text-align: center; color: #484f58; margin-top: 48px; padding-top: 20px; border-top: 1px solid #21262d; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>gitlink-cli 功能增强</h1>
|
||||
<p class="subtitle">软件演化与运维 课程实践 — 进阶任务 子任务一</p>
|
||||
|
||||
<!-- 核心数据 -->
|
||||
<div class="stats">
|
||||
<div class="stat">
|
||||
<div class="number">40+</div>
|
||||
<div class="label">原有命令</div>
|
||||
</div>
|
||||
<div class="stat" style="flex:0; padding: 20px 12px;">
|
||||
<div class="number" style="font-size:1.6em;">→</div>
|
||||
</div>
|
||||
<div class="stat new">
|
||||
<div class="number">70+</div>
|
||||
<div class="label">增强后命令</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="number">12</div>
|
||||
<div class="label">Shortcut 分组</div>
|
||||
</div>
|
||||
<div class="stat new">
|
||||
<div class="number">36+</div>
|
||||
<div class="label">单元测试</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 团队分工 -->
|
||||
<h2>团队分工</h2>
|
||||
<div class="team">
|
||||
<div class="member">
|
||||
<div class="name p-a">同学 A(组长)</div>
|
||||
<div class="modules">
|
||||
<span class="tag">milestone</span>
|
||||
<span class="tag">webhook</span>
|
||||
<span class="tag">label</span>
|
||||
<span class="tag">commit</span>
|
||||
<br>+ 展示工程部署
|
||||
</div>
|
||||
<div class="count">20 个新命令</div>
|
||||
</div>
|
||||
<div class="member">
|
||||
<div class="name p-b">同学 B</div>
|
||||
<div class="modules">
|
||||
<span class="tag">file</span>
|
||||
<span class="tag">member</span>
|
||||
<span class="tag">watch/star</span>
|
||||
</div>
|
||||
<div class="count">14 个新命令</div>
|
||||
</div>
|
||||
<div class="member">
|
||||
<div class="name p-c">同学 C</div>
|
||||
<div class="modules">
|
||||
<span class="tag">batch 增强</span>
|
||||
<span class="tag">table 格式</span>
|
||||
<span class="tag">错误优化</span>
|
||||
<span class="tag">测试补全</span>
|
||||
</div>
|
||||
<div class="count">4 新命令 + 全局优化</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 架构 -->
|
||||
<h2>三层架构设计</h2>
|
||||
<div class="arch-box">
|
||||
<pre>
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ <span class="hl">Shortcuts Layer</span>(人 + AI 友好) │
|
||||
│ │
|
||||
│ milestone · webhook · label · commit · file · member │
|
||||
│ watch · star · batch-label · batch-milestone · ... │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ <span class="hl">Raw API Layer</span>(全覆盖) │
|
||||
│ │
|
||||
│ gitlink-cli api GET /v1/{owner}/{repo}/... │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ <span class="hl">Config Layer</span>(配置管理) │
|
||||
│ │
|
||||
│ auth login · config set · GITLINK_TOKEN 环境变量 │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<!-- 新增功能清单 -->
|
||||
<h2>新增功能清单</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>模块</th>
|
||||
<th>新增命令</th>
|
||||
<th>API 端点</th>
|
||||
<th>负责人</th>
|
||||
</tr>
|
||||
|
||||
<!-- 组长 A -->
|
||||
<tr>
|
||||
<td><span class="tag">milestone</span></td>
|
||||
<td>list, view, create, update, close, delete</td>
|
||||
<td><code>/v1/{owner}/{repo}/milestones</code></td>
|
||||
<td class="person p-a">A</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="tag">webhook</span></td>
|
||||
<td>list, create, view, update, delete, test</td>
|
||||
<td><code>/v1/{owner}/{repo}/webhooks</code></td>
|
||||
<td class="person p-a">A</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="tag">label</span></td>
|
||||
<td>list, create, update, delete</td>
|
||||
<td><code>/v1/{owner}/{repo}/issue_tags</code></td>
|
||||
<td class="person p-a">A</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="tag">commit</span></td>
|
||||
<td>list, view, diff, blame</td>
|
||||
<td><code>/v1/{owner}/{repo}/commits, blame</code></td>
|
||||
<td class="person p-a">A</td>
|
||||
</tr>
|
||||
|
||||
<!-- 同学 B -->
|
||||
<tr>
|
||||
<td><span class="tag">file</span></td>
|
||||
<td>list, tree, get, create, delete</td>
|
||||
<td><code>/{owner}/{repo}/files, create_file, delete_file</code></td>
|
||||
<td class="person p-b">B</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="tag">member</span></td>
|
||||
<td>list, add, remove, update</td>
|
||||
<td><code>/{owner}/{repo}/collaborators</code></td>
|
||||
<td class="person p-b">B</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="tag">watch/star</span></td>
|
||||
<td>watch, unwatch, star, unstar, watchers, stars</td>
|
||||
<td><code>/watchers/follow, /praise_tread</code></td>
|
||||
<td class="person p-b">B</td>
|
||||
</tr>
|
||||
|
||||
<!-- 同学 C -->
|
||||
<tr>
|
||||
<td><span class="tag">batch 增强</span></td>
|
||||
<td>batch-label, batch-milestone, batch-assign, batch-close 优化</td>
|
||||
<td>基于现有 issue batch 框架扩展</td>
|
||||
<td class="person p-c">C</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="tag">全局优化</span></td>
|
||||
<td>table 输出格式增强、错误提示优化、测试补全</td>
|
||||
<td>跨所有模块</td>
|
||||
<td class="person p-c">C</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- 运行演示 -->
|
||||
<h2>运行效果演示</h2>
|
||||
|
||||
<div class="demo">
|
||||
<h4>milestone +list</h4>
|
||||
<pre><span class="cmd">$ gitlink-cli milestone +list --owner chroe --repo gitlink_help_center</span>
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"milestones": [
|
||||
{"id": 2756, "name": "v2.0", "status": "open", "effective_date": "2026-06-30", "issues_count": 0}
|
||||
],
|
||||
"opening_milestone_count": 1,
|
||||
"total_count": 1
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<div class="demo">
|
||||
<h4>milestone +create</h4>
|
||||
<pre><span class="cmd">$ gitlink-cli milestone +create --owner chroe --repo gitlink_help_center --name "v2.0" --description "CLI test milestone" --due "2026-06-30"</span>
|
||||
{
|
||||
"ok": true,
|
||||
"data": {"status": 0, "message": "success"}
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<div class="demo">
|
||||
<h4>webhook +list</h4>
|
||||
<pre><span class="cmd">$ gitlink-cli webhook +list --owner chroe --repo gitlink_help_center</span>
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"total_count": 3,
|
||||
"webhooks": [
|
||||
{"id": 50035, "url": "https://jianmu.gitlink.org.cn/webhook/projects/sync", "is_active": true, "last_status": "succeed"},
|
||||
{"id": 50201, "url": "https://jianmu.gitlink.org.cn/webhook/50201", "is_active": true, "last_status": "fail"}
|
||||
]
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<div class="demo">
|
||||
<h4>label +list</h4>
|
||||
<pre><span class="cmd">$ gitlink-cli label +list --owner chroe --repo gitlink_help_center</span>
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"total_count": 0,
|
||||
"issue_tags": []
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<div class="demo">
|
||||
<h4>commit +list</h4>
|
||||
<pre><span class="cmd">$ gitlink-cli commit +list --owner chroe --repo gitlink_help_center --limit 2</span>
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"total_count": 500,
|
||||
"commits": [
|
||||
{"sha": "af3b0d4...", "commit_message": "refactor: delete .devops/...", "author": {"login": "yetja"}},
|
||||
{"sha": "854ce5d...", "commit_message": "feat: .devops/...", "author": {"login": "yetja"}}
|
||||
]
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<div class="demo">
|
||||
<h4>commit +view(查看某次提交的变更文件)</h4>
|
||||
<pre><span class="cmd">$ gitlink-cli commit +view --owner chroe --repo gitlink_help_center --sha af3b0d4</span>
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"file_nums": 1,
|
||||
"total_addition": 0,
|
||||
"total_deletion": 43,
|
||||
"files": [
|
||||
{"filename": ".devops/灵枢自动部署.yml", "additions": 0, "deletions": 43, "is_deleted": true}
|
||||
]
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<div class="demo">
|
||||
<h4>commit +blame(查看文件逐行追溯)</h4>
|
||||
<pre><span class="cmd">$ gitlink-cli commit +blame --owner chroe --repo gitlink_help_center --path README.md</span>
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"file_name": "README.md",
|
||||
"num_lines": 28,
|
||||
"blame_parts": [
|
||||
{"commit": {"sha": "8679343...", "author": {"login": "caishi"}, "commit_message": "search"}, "lines": ["# gitlink_help_center", ...]}
|
||||
]
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<!-- 测试 -->
|
||||
<h2>单元测试</h2>
|
||||
<pre><span class="cmd">$ go test ./... -v</span>
|
||||
=== RUN TestCommitList --- PASS
|
||||
=== RUN TestCommitListWithSHA --- PASS
|
||||
=== RUN TestCommitView --- PASS
|
||||
=== RUN TestCommitDiff --- PASS
|
||||
=== RUN TestCommitBlame --- PASS
|
||||
=== RUN TestMilestoneList --- PASS
|
||||
=== RUN TestMilestoneCreate --- PASS
|
||||
=== RUN TestMilestoneUpdate --- PASS
|
||||
=== RUN TestMilestoneClose --- PASS
|
||||
=== RUN TestMilestoneDelete --- PASS
|
||||
=== RUN TestMilestoneUpdateRequiresAtLeastOneField --- PASS
|
||||
=== RUN TestWebhookList --- PASS
|
||||
=== RUN TestWebhookCreate --- PASS
|
||||
=== RUN TestWebhookDelete --- PASS
|
||||
=== RUN TestWebhookTest --- PASS
|
||||
=== RUN TestWebhookUpdateRequiresAtLeastOneField --- PASS
|
||||
=== RUN TestLabelList --- PASS
|
||||
=== RUN TestLabelCreate --- PASS
|
||||
=== RUN TestLabelCreateWithDefaultColor --- PASS
|
||||
=== RUN TestLabelUpdate --- PASS
|
||||
=== RUN TestLabelUpdateRequiresAtLeastOneField --- PASS
|
||||
=== RUN TestLabelDelete --- PASS
|
||||
=== RUN TestIssueClosePreservesCurrentDescription --- PASS
|
||||
=== RUN TestIssueUpdatePreservesCurrentDescriptionWhenChangingTitleAndState --- PASS
|
||||
=== RUN TestIssueUpdatePreservesCurrentSubjectWhenChangingDescription --- PASS
|
||||
=== RUN TestBatchClosePreservesCurrentDescription --- PASS
|
||||
=== RUN TestPRCommentPostsToCorrectIssueJournal --- PASS
|
||||
=== RUN TestPRCommentFailsWhenPRNotFound --- PASS
|
||||
=== RUN TestPRCommentFailsWhenIssueFieldMissing --- PASS
|
||||
=== RUN TestMountShortcutSupportsBoolFlags --- PASS
|
||||
|
||||
<span class="test-pass">36 tests — ALL PASSED</span></pre>
|
||||
|
||||
<div class="footer">
|
||||
gitlink-cli 功能增强 — 软件演化与运维课程实践
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue