Merge pull request '修正 Issue 和 PR 列表筛选' (#117) from Mengz/gitlink-cli:mengz/list-filter-fixes into master
This commit is contained in:
commit
dd4ed35448
|
|
@ -255,6 +255,9 @@ gitlink-cli member +invite-link --owner Gitlink --repo forgeplus --role develope
|
|||
# List issues
|
||||
gitlink-cli issue +list --owner Gitlink --repo forgeplus
|
||||
|
||||
# Search and sort open issues
|
||||
gitlink-cli issue +list --owner Gitlink --repo forgeplus --state open --keyword login --sort-by issues.updated_on --sort-direction desc
|
||||
|
||||
# Create an issue
|
||||
gitlink-cli issue +create --owner Gitlink --repo forgeplus -t "Bug: Login failed" -b "Steps to reproduce..."
|
||||
|
||||
|
|
@ -325,6 +328,9 @@ gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42
|
|||
# List PRs
|
||||
gitlink-cli pr +list --owner Gitlink --repo forgeplus
|
||||
|
||||
# Search merged PRs by keyword
|
||||
gitlink-cli pr +list --owner Gitlink --repo forgeplus --state merged --keyword release --sort-by updated_at --sort-direction desc
|
||||
|
||||
# Create a PR (same-repo branch)
|
||||
gitlink-cli pr +create --owner Gitlink --repo forgeplus -t "feat: Search feature" --head feature/search --base master
|
||||
|
||||
|
|
|
|||
|
|
@ -266,6 +266,9 @@ gitlink-cli member +invite-link --owner Gitlink --repo forgeplus --role develope
|
|||
# 列出 Issue
|
||||
gitlink-cli issue +list --owner Gitlink --repo forgeplus
|
||||
|
||||
# 搜索并按更新时间倒序列出开启中的 Issue
|
||||
gitlink-cli issue +list --owner Gitlink --repo forgeplus --state open --keyword 登录 --sort-by issues.updated_on --sort-direction desc
|
||||
|
||||
# 创建 Issue
|
||||
gitlink-cli issue +create --owner Gitlink --repo forgeplus -t "Bug: 登录失败" -b "复现步骤..."
|
||||
|
||||
|
|
@ -335,6 +338,9 @@ gitlink-cli label +delete --owner Gitlink --repo forgeplus -i 42
|
|||
# 列出 PR
|
||||
gitlink-cli pr +list --owner Gitlink --repo forgeplus
|
||||
|
||||
# 按关键字搜索已合并 PR
|
||||
gitlink-cli pr +list --owner Gitlink --repo forgeplus --state merged --keyword release --sort-by updated_at --sort-direction desc
|
||||
|
||||
# 创建 PR(同仓库分支)
|
||||
gitlink-cli pr +create --owner Gitlink --repo forgeplus -t "feat: 搜索功能" --head feature/search --base master
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
# Issue/PR 列表筛选增强
|
||||
|
||||
本次变更修正并增强 `issue +list` 与 `pr +list` 的筛选能力。
|
||||
|
||||
此前 `issue +list --state open` 会向服务端发送 `state=open`,但 GitLink v1 Issue 列表接口实际使用 `category=opened/closed/all`,因此列表可能仍返回关闭 Issue。`pr +list --state open` 也没有映射到 PR 列表接口实际使用的 `status=0/1/2` 参数,Skill 文档中甚至需要提醒用户该参数可能只影响统计。现在两个命令都会保留原有 `--state` 用户体验,同时转换为服务端真实生效的参数。
|
||||
|
||||
新增筛选项:
|
||||
|
||||
- `issue +list` 支持 `--keyword`、`--participant`、`--author-id`、`--assignee-id`、`--milestone-id`、`--status-id`、`--tag-ids`、`--sort-by`、`--sort-direction`。
|
||||
- `pr +list` 支持 `--keyword`、`--priority-id`、`--tag-id`、`--milestone-id`、`--reviewer-id`、`--assignee-id`、`--sort-by`、`--sort-direction`。
|
||||
|
||||
单元测试覆盖了状态映射、筛选参数透传和 `all` 状态兼容;README、中文 README、Issue Skill 与 PR Skill 已同步更新。
|
||||
|
|
@ -119,6 +119,8 @@
|
|||
"flag.issue.batch_label.state": "Filter by issue state",
|
||||
"flag.issue.batch_list.limit": "Maximum issues to return, capped at 100",
|
||||
"flag.issue.batch_process.limit": "Maximum issues to process, capped at 100",
|
||||
"flag.issue.assignee_id": "Assignee user ID",
|
||||
"flag.issue.author_id": "Author user ID",
|
||||
"flag.issue.body": "Issue description",
|
||||
"flag.issue.label": "Label ID",
|
||||
"flag.issue.label_filter": "Filter by existing label",
|
||||
|
|
@ -128,7 +130,10 @@
|
|||
"flag.issue.new_title": "New title",
|
||||
"flag.issue.number": "Issue number (as shown in the web URL)",
|
||||
"flag.issue.older_than_days": "Only include issues inactive for at least this many days",
|
||||
"flag.issue.participant": "Participant filter: all, aboutme, authoredme, assignedme, atme",
|
||||
"flag.issue.state": "Filter by state: open, closed, all",
|
||||
"flag.issue.status_id": "Issue status ID",
|
||||
"flag.issue.tag_ids": "Comma-separated issue tag IDs",
|
||||
"flag.issue.title": "Issue title",
|
||||
"flag.lang": "Display language",
|
||||
"flag.limit": "Items per page",
|
||||
|
|
@ -137,17 +142,22 @@
|
|||
"flag.org.name": "Organization name",
|
||||
"flag.owner": "Repository owner (auto-detected from git remote)",
|
||||
"flag.page": "Page number",
|
||||
"flag.pr.assignee_id": "Assignee user ID",
|
||||
"flag.pr.base": "Target branch",
|
||||
"flag.pr.body": "PR description",
|
||||
"flag.pr.file": "Filter diff by file path",
|
||||
"flag.pr.head": "Source branch",
|
||||
"flag.pr.id": "PR number",
|
||||
"flag.pr.milestone_id": "Milestone ID",
|
||||
"flag.pr.merge_method": "Merge method: merge, rebase, squash",
|
||||
"flag.pr.priority_id": "Priority ID",
|
||||
"flag.pr.review_commit": "Commit SHA to attach the review to",
|
||||
"flag.pr.review_content": "Review content",
|
||||
"flag.pr.reviewer_id": "Reviewer user ID",
|
||||
"flag.pr.review_status": "Review status: common, approved, rejected",
|
||||
"flag.pr.review_status_filter": "Filter review status: common, approved, rejected",
|
||||
"flag.pr.state": "Filter: open, merged, closed",
|
||||
"flag.pr.tag_id": "Issue tag ID",
|
||||
"flag.pr.title": "PR title",
|
||||
"flag.pr.version_id": "Patchset version ID",
|
||||
"flag.release.body": "Release notes",
|
||||
|
|
@ -163,6 +173,8 @@
|
|||
"flag.repo.name": "Repository name",
|
||||
"flag.repo.private": "Make repository private (true/false)",
|
||||
"flag.search.keyword": "Search keyword",
|
||||
"flag.sort_by": "Sort field",
|
||||
"flag.sort_direction": "Sort direction: asc, desc",
|
||||
"flag.user": "User login (default: current user)",
|
||||
"flag.user.login": "User login name",
|
||||
"flag.webhook.active": "Whether the webhook is active: true or false",
|
||||
|
|
|
|||
|
|
@ -119,6 +119,8 @@
|
|||
"flag.issue.batch_label.state": "按议题状态筛选",
|
||||
"flag.issue.batch_list.limit": "最多返回的议题数,上限 100",
|
||||
"flag.issue.batch_process.limit": "最多处理的议题数,上限 100",
|
||||
"flag.issue.assignee_id": "负责人用户 ID",
|
||||
"flag.issue.author_id": "作者用户 ID",
|
||||
"flag.issue.body": "议题描述",
|
||||
"flag.issue.label": "标签 ID",
|
||||
"flag.issue.label_filter": "按已有标签筛选",
|
||||
|
|
@ -128,7 +130,10 @@
|
|||
"flag.issue.new_title": "新标题",
|
||||
"flag.issue.number": "议题编号(网页 URL 中显示的编号)",
|
||||
"flag.issue.older_than_days": "只包含至少这么多天未活动的议题",
|
||||
"flag.issue.participant": "参与范围:all、aboutme、authoredme、assignedme、atme",
|
||||
"flag.issue.state": "按状态筛选:open、closed、all",
|
||||
"flag.issue.status_id": "议题状态 ID",
|
||||
"flag.issue.tag_ids": "逗号分隔的议题标签 ID",
|
||||
"flag.issue.title": "议题标题",
|
||||
"flag.lang": "显示语言",
|
||||
"flag.limit": "每页条目数",
|
||||
|
|
@ -137,17 +142,22 @@
|
|||
"flag.org.name": "组织名称",
|
||||
"flag.owner": "仓库所有者(自动从 git remote 检测)",
|
||||
"flag.page": "页码",
|
||||
"flag.pr.assignee_id": "指派人用户 ID",
|
||||
"flag.pr.base": "目标分支",
|
||||
"flag.pr.body": "PR 描述",
|
||||
"flag.pr.file": "按文件路径筛选 diff",
|
||||
"flag.pr.head": "源分支",
|
||||
"flag.pr.id": "PR 编号",
|
||||
"flag.pr.milestone_id": "里程碑 ID",
|
||||
"flag.pr.merge_method": "合并方式:merge、rebase、squash",
|
||||
"flag.pr.priority_id": "优先级 ID",
|
||||
"flag.pr.review_commit": "关联评审的 Commit SHA",
|
||||
"flag.pr.review_content": "评审内容",
|
||||
"flag.pr.reviewer_id": "评审人用户 ID",
|
||||
"flag.pr.review_status": "评审状态:common、approved、rejected",
|
||||
"flag.pr.review_status_filter": "按评审状态筛选:common、approved、rejected",
|
||||
"flag.pr.state": "筛选:open、merged、closed",
|
||||
"flag.pr.tag_id": "议题标签 ID",
|
||||
"flag.pr.title": "PR 标题",
|
||||
"flag.pr.version_id": "补丁集版本 ID",
|
||||
"flag.release.body": "发布说明",
|
||||
|
|
@ -163,6 +173,8 @@
|
|||
"flag.repo.name": "仓库名称",
|
||||
"flag.repo.private": "设为私有仓库(true/false)",
|
||||
"flag.search.keyword": "搜索关键词",
|
||||
"flag.sort_by": "排序字段",
|
||||
"flag.sort_direction": "排序方向:asc、desc",
|
||||
"flag.user": "用户登录名(默认:当前用户)",
|
||||
"flag.user.login": "用户登录名",
|
||||
"flag.webhook.active": "Webhook 是否启用:true 或 false",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,19 @@ func v1RepoPath(ctx *common.RuntimeContext) string {
|
|||
return fmt.Sprintf("/v1/%s/%s", ctx.Owner, ctx.Repo)
|
||||
}
|
||||
|
||||
func normalizeIssueListState(state string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(state)) {
|
||||
case "open", "opened":
|
||||
return "opened"
|
||||
case "closed":
|
||||
return "closed"
|
||||
case "all", "":
|
||||
return "all"
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
type existingIssue struct {
|
||||
Subject string
|
||||
Description string
|
||||
|
|
@ -37,6 +50,15 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
Description: tr.T("cmd.issue.list.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "state", Short: "s", Usage: tr.T("flag.issue.state"), Default: "open"},
|
||||
{Name: "keyword", Short: "k", Usage: tr.T("flag.search.keyword")},
|
||||
{Name: "participant", Usage: tr.T("flag.issue.participant")},
|
||||
{Name: "author-id", Usage: tr.T("flag.issue.author_id")},
|
||||
{Name: "assignee-id", Usage: tr.T("flag.issue.assignee_id")},
|
||||
{Name: "milestone-id", Usage: tr.T("flag.issue.milestone")},
|
||||
{Name: "status-id", Usage: tr.T("flag.issue.status_id")},
|
||||
{Name: "tag-ids", Usage: tr.T("flag.issue.tag_ids")},
|
||||
{Name: "sort-by", Usage: tr.T("flag.sort_by")},
|
||||
{Name: "sort-direction", Usage: tr.T("flag.sort_direction")},
|
||||
{Name: "page", Short: "p", Usage: tr.T("flag.page"), Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: tr.T("flag.limit"), Default: "20"},
|
||||
},
|
||||
|
|
@ -48,7 +70,34 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
q.Set("page", ctx.Arg("page"))
|
||||
q.Set("limit", ctx.Arg("limit"))
|
||||
if s := ctx.Arg("state"); s != "" {
|
||||
q.Set("state", s)
|
||||
q.Set("category", normalizeIssueListState(s))
|
||||
}
|
||||
if keyword := ctx.Arg("keyword"); keyword != "" {
|
||||
q.Set("keyword", keyword)
|
||||
}
|
||||
if participant := ctx.Arg("participant"); participant != "" {
|
||||
q.Set("participant_category", participant)
|
||||
}
|
||||
if authorID := ctx.Arg("author-id"); authorID != "" {
|
||||
q.Set("author_id", authorID)
|
||||
}
|
||||
if assigneeID := ctx.Arg("assignee-id"); assigneeID != "" {
|
||||
q.Set("assigner_id", assigneeID)
|
||||
}
|
||||
if milestoneID := ctx.Arg("milestone-id"); milestoneID != "" {
|
||||
q.Set("milestone_id", milestoneID)
|
||||
}
|
||||
if statusID := ctx.Arg("status-id"); statusID != "" {
|
||||
q.Set("status_id", statusID)
|
||||
}
|
||||
if tagIDs := ctx.Arg("tag-ids"); tagIDs != "" {
|
||||
q.Set("issue_tag_ids", tagIDs)
|
||||
}
|
||||
if sortBy := ctx.Arg("sort-by"); sortBy != "" {
|
||||
q.Set("sort_by", sortBy)
|
||||
}
|
||||
if sortDirection := ctx.Arg("sort-direction"); sortDirection != "" {
|
||||
q.Set("sort_direction", sortDirection)
|
||||
}
|
||||
env, err := ctx.CallAPIWithQuery("GET", v1RepoPath(ctx)+"/issues", q)
|
||||
if err != nil {
|
||||
|
|
@ -599,4 +648,3 @@ func parseIssueID(value, flagName string) (int, error) {
|
|||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -104,8 +104,8 @@ func TestIssueList(t *testing.T) {
|
|||
if r.URL.Path != "/v1/owner/repo/issues.json" {
|
||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
if r.URL.Query().Get("state") != "open" {
|
||||
t.Fatalf("expected state=open, got %s", r.URL.Query().Get("state"))
|
||||
if r.URL.Query().Get("category") != "opened" {
|
||||
t.Fatalf("expected category=opened, got %s", r.URL.Query().Get("category"))
|
||||
}
|
||||
writeJSON(t, w, []interface{}{
|
||||
map[string]interface{}{"id": float64(1), "subject": "bug"},
|
||||
|
|
@ -119,6 +119,58 @@ func TestIssueList(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestIssueListWithFilters(t *testing.T) {
|
||||
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/v1/owner/repo/issues.json" {
|
||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
query := r.URL.Query()
|
||||
assertEqual(t, query.Get("category"), "closed")
|
||||
assertEqual(t, query.Get("keyword"), "release")
|
||||
assertEqual(t, query.Get("participant_category"), "assignedme")
|
||||
assertEqual(t, query.Get("author_id"), "10")
|
||||
assertEqual(t, query.Get("assigner_id"), "11")
|
||||
assertEqual(t, query.Get("milestone_id"), "12")
|
||||
assertEqual(t, query.Get("status_id"), "5")
|
||||
assertEqual(t, query.Get("issue_tag_ids"), "1,2")
|
||||
assertEqual(t, query.Get("sort_by"), "issues.updated_on")
|
||||
assertEqual(t, query.Get("sort_direction"), "desc")
|
||||
writeJSON(t, w, map[string]interface{}{"issues": []interface{}{}})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "list", map[string]string{
|
||||
"state": "closed",
|
||||
"keyword": "release",
|
||||
"participant": "assignedme",
|
||||
"author-id": "10",
|
||||
"assignee-id": "11",
|
||||
"milestone-id": "12",
|
||||
"status-id": "5",
|
||||
"tag-ids": "1,2",
|
||||
"sort-by": "issues.updated_on",
|
||||
"sort-direction": "desc",
|
||||
"page": "2",
|
||||
"limit": "50",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("list with filters failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssueListStateAll(t *testing.T) {
|
||||
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
assertEqual(t, r.URL.Query().Get("category"), "all")
|
||||
writeJSON(t, w, map[string]interface{}{"issues": []interface{}{}})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "list", map[string]string{"state": "all", "page": "1", "limit": "20"})
|
||||
if err != nil {
|
||||
t.Fatalf("list all failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// --- create ---
|
||||
|
||||
func TestIssueCreate(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,25 @@ import (
|
|||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
func v1RepoPath(ctx *common.RuntimeContext) string {
|
||||
return fmt.Sprintf("/v1/%s/%s", ctx.Owner, ctx.Repo)
|
||||
}
|
||||
|
||||
func normalizePullRequestListState(state string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(state)) {
|
||||
case "open", "opened":
|
||||
return "0"
|
||||
case "merged":
|
||||
return "1"
|
||||
case "closed":
|
||||
return "2"
|
||||
case "all", "":
|
||||
return ""
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
||||
tr := shortcutTranslator(translators...)
|
||||
return []*common.Shortcut{
|
||||
|
|
@ -18,6 +37,14 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
Description: tr.T("cmd.pr.list.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "state", Short: "s", Usage: tr.T("flag.pr.state"), Default: "open"},
|
||||
{Name: "keyword", Short: "k", Usage: tr.T("flag.search.keyword")},
|
||||
{Name: "priority-id", Usage: tr.T("flag.pr.priority_id")},
|
||||
{Name: "tag-id", Usage: tr.T("flag.pr.tag_id")},
|
||||
{Name: "milestone-id", Usage: tr.T("flag.pr.milestone_id")},
|
||||
{Name: "reviewer-id", Usage: tr.T("flag.pr.reviewer_id")},
|
||||
{Name: "assignee-id", Usage: tr.T("flag.pr.assignee_id")},
|
||||
{Name: "sort-by", Usage: tr.T("flag.sort_by")},
|
||||
{Name: "sort-direction", Usage: tr.T("flag.sort_direction")},
|
||||
{Name: "page", Short: "p", Usage: tr.T("flag.page"), Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: tr.T("flag.limit"), Default: "20"},
|
||||
},
|
||||
|
|
@ -28,10 +55,34 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
q := url.Values{}
|
||||
q.Set("page", ctx.Arg("page"))
|
||||
q.Set("limit", ctx.Arg("limit"))
|
||||
if s := ctx.Arg("state"); s != "" {
|
||||
q.Set("state", s)
|
||||
if s := normalizePullRequestListState(ctx.Arg("state")); s != "" {
|
||||
q.Set("status", s)
|
||||
}
|
||||
env, err := ctx.CallAPIWithQuery("GET", ctx.RepoPath()+"/pulls", q)
|
||||
if keyword := ctx.Arg("keyword"); keyword != "" {
|
||||
q.Set("keyword", keyword)
|
||||
}
|
||||
if priorityID := ctx.Arg("priority-id"); priorityID != "" {
|
||||
q.Set("priority_id", priorityID)
|
||||
}
|
||||
if tagID := ctx.Arg("tag-id"); tagID != "" {
|
||||
q.Set("issue_tag_id", tagID)
|
||||
}
|
||||
if milestoneID := ctx.Arg("milestone-id"); milestoneID != "" {
|
||||
q.Set("version_id", milestoneID)
|
||||
}
|
||||
if reviewerID := ctx.Arg("reviewer-id"); reviewerID != "" {
|
||||
q.Set("reviewer_id", reviewerID)
|
||||
}
|
||||
if assigneeID := ctx.Arg("assignee-id"); assigneeID != "" {
|
||||
q.Set("assign_user_id", assigneeID)
|
||||
}
|
||||
if sortBy := ctx.Arg("sort-by"); sortBy != "" {
|
||||
q.Set("sort_by", sortBy)
|
||||
}
|
||||
if sortDirection := ctx.Arg("sort-direction"); sortDirection != "" {
|
||||
q.Set("sort_direction", sortDirection)
|
||||
}
|
||||
env, err := ctx.CallAPIWithQuery("GET", v1RepoPath(ctx)+"/pulls", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -120,8 +171,8 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
},
|
||||
},
|
||||
{
|
||||
Name: "refuse",
|
||||
Description: "Refuse and close a pull request",
|
||||
Name: "refuse",
|
||||
Description: "Refuse and close a pull request",
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: tr.T("flag.pr.id"), Required: true},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -99,11 +99,11 @@ func TestPRList(t *testing.T) {
|
|||
if r.Method != "GET" {
|
||||
t.Fatalf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/owner/repo/pulls.json" {
|
||||
if r.URL.Path != "/v1/owner/repo/pulls.json" {
|
||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
if r.URL.Query().Get("state") != "open" {
|
||||
t.Fatalf("expected state=open, got %s", r.URL.Query().Get("state"))
|
||||
if r.URL.Query().Get("status") != "0" {
|
||||
t.Fatalf("expected status=0, got %s", r.URL.Query().Get("status"))
|
||||
}
|
||||
if r.URL.Query().Get("page") != "1" {
|
||||
t.Fatalf("expected page=1, got %s", r.URL.Query().Get("page"))
|
||||
|
|
@ -120,6 +120,61 @@ func TestPRList(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPRListWithFilters(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/v1/owner/repo/pulls.json" {
|
||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
query := r.URL.Query()
|
||||
assertEqual(t, query.Get("status"), "1")
|
||||
assertEqual(t, query.Get("keyword"), "release")
|
||||
assertEqual(t, query.Get("priority_id"), "2")
|
||||
assertEqual(t, query.Get("issue_tag_id"), "3")
|
||||
assertEqual(t, query.Get("version_id"), "4")
|
||||
assertEqual(t, query.Get("reviewer_id"), "5")
|
||||
assertEqual(t, query.Get("assign_user_id"), "6")
|
||||
assertEqual(t, query.Get("sort_by"), "updated_at")
|
||||
assertEqual(t, query.Get("sort_direction"), "desc")
|
||||
writeJSON(t, w, map[string]interface{}{"pulls": []interface{}{}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runPRShortcut(t, server, "list", map[string]string{
|
||||
"state": "merged",
|
||||
"keyword": "release",
|
||||
"priority-id": "2",
|
||||
"tag-id": "3",
|
||||
"milestone-id": "4",
|
||||
"reviewer-id": "5",
|
||||
"assignee-id": "6",
|
||||
"sort-by": "updated_at",
|
||||
"sort-direction": "desc",
|
||||
"page": "2",
|
||||
"limit": "50",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("list with filters failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPRListStateAllOmitsStatus(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/v1/owner/repo/pulls.json" {
|
||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
if got := r.URL.Query().Get("status"); got != "" {
|
||||
t.Fatalf("status should be omitted for all, got %q", got)
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{"pulls": []interface{}{}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runPRShortcut(t, server, "list", map[string]string{"state": "all", "page": "1", "limit": "20"})
|
||||
if err != nil {
|
||||
t.Fatalf("list all failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// --- create ---
|
||||
|
||||
func TestPRCreate(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ metadata:
|
|||
# 列出 Issue
|
||||
gitlink-cli issue +list --owner Gitlink --repo forgeplus --state open
|
||||
|
||||
# 搜索并排序 Issue
|
||||
gitlink-cli issue +list --owner Gitlink --repo forgeplus --state open --keyword 登录 --sort-by issues.updated_on --sort-direction desc
|
||||
|
||||
# 创建 Issue
|
||||
gitlink-cli issue +create --owner myuser --repo myrepo --title "Bug: 登录失败" --body "复现步骤:..."
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ metadata:
|
|||
# 列出 PR
|
||||
gitlink-cli pr +list --owner Gitlink --repo forgeplus --state open
|
||||
|
||||
# 搜索并排序 PR
|
||||
gitlink-cli pr +list --owner Gitlink --repo forgeplus --state merged --keyword release --sort-by updated_at --sort-direction desc
|
||||
|
||||
# 创建 PR(源分支必须有实际代码变更)
|
||||
gitlink-cli pr +create --title "feat: 新增搜索功能" --head feature/search --base master --body "实现了全文搜索"
|
||||
|
||||
|
|
@ -166,6 +169,6 @@ gitlink-cli api GET /v1/:owner/:repo/pulls/:id/versions/:version_id/diff
|
|||
- `pr +diff` 实际调用 `/pulls/:id/files` 端点,返回变更文件列表和 diff 内容
|
||||
- `pr +versions` / `pr +version-diff` 使用 v1 API,`--id` 为网页 URL `/pulls/N` 中的 PR 序号,`--version-id` 为 patchset/version id
|
||||
- 同一个 PR 分支继续 push 新 commit 会生成新的 patchset/version;正常根据 review 修改代码时,应优先在原 PR 分支继续 push,不要关闭 PR 重开
|
||||
- `pr +list` 的 `--state` 参数(open/merged/closed)仅影响统计计数,API 返回的列表可能包含所有状态的 PR
|
||||
- `pr +list` 的 `--state` 参数会映射为 GitLink API 的 `status` 筛选;需要查看所有 PR 时传 `--state all`
|
||||
- PR 状态值:`pull_request_status` 0=open, 1=merged, 2=closed
|
||||
- 关联已有 Issue 时,把 Issue 编号或 URL 写入 PR `--body`,或使用 `issue +comment` 留痕;不要用 Raw API 对 Issue 做不完整更新,否则可能清空 Issue 描述
|
||||
|
|
|
|||
Loading…
Reference in New Issue