feat(list): --all 自动翻页,翻页助手对齐生产 API 资源键包裹形状
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
9749a4c832
commit
b82378bb4e
|
|
@ -0,0 +1,43 @@
|
|||
# list 命令 --all 自动翻页
|
||||
|
||||
## 背景
|
||||
|
||||
`issue +list`、`pr +list`、`branch +list`、`release +list` 此前一次只能取一页,
|
||||
用户或 AI Agent 想拿到全量列表必须手动循环 `--page`。代码中虽有 `PaginateAll`
|
||||
翻页助手,但它只识别 `data` 包裹键;而 GitLink 生产 API 的列表响应实际用
|
||||
资源名包裹数组(如 `{"total_count":N,"issues":[...]}`、`"pulls"`、`"branches"`、
|
||||
`"releases"`),导致该助手在真实端点上退化为「单对象」返回,从未被任何命令使用。
|
||||
|
||||
## 变更内容
|
||||
|
||||
- `internal/client`:翻页助手对齐生产响应形状
|
||||
- 新增 `PaginateAllKey(path, params, listKey)`:按指定资源键提取数组;
|
||||
`listKey` 为空时自动探测(顶层数组 / `data` 包裹 / 唯一数组字段)。
|
||||
- 遵循 `total_count`:达到总数即停止;另设最大页数护栏,防止
|
||||
忽略 `page` 参数的端点造成死循环。
|
||||
- `PaginateAll` 保持原签名,委托给 `PaginateAllKey`。
|
||||
- 四个 list 命令新增 `--all` 布尔参数(默认 false):
|
||||
- `issue +list --all`(合并结果同样应用 number/database_id 规范化)
|
||||
- `pr +list --all`、`branch +list --all`、`release +list --all`
|
||||
- 输出与单页响应同构:`{"total_count": N, "<资源名>": [...]}`。
|
||||
- 中英文 i18n 新增 `flag.all` 文案。
|
||||
|
||||
## 命令示例
|
||||
|
||||
```bash
|
||||
# 拉取仓库全部 open issue(自动翻页合并)
|
||||
gitlink-cli issue +list --state open --all --format json
|
||||
|
||||
# 全部分支 / 全部 PR / 全部 release
|
||||
gitlink-cli branch +list --all
|
||||
gitlink-cli pr +list --state all --all
|
||||
gitlink-cli release +list --all
|
||||
```
|
||||
|
||||
## 测试
|
||||
|
||||
- `internal/client/pagination_test.go`:资源键包裹多页合并、`total_count`
|
||||
截断(模拟忽略 page 的异常端点)、`data` 包裹、唯一数组字段自动探测、
|
||||
单对象回退、指定键缺失回退,共 6 个用例。
|
||||
- `shortcuts/issue`:`--all` 端到端用例验证按页请求序列与合并。
|
||||
- `go test ./...`、`go vet`、`gofmt` 全部通过。
|
||||
|
|
@ -7,8 +7,23 @@ import (
|
|||
"strconv"
|
||||
)
|
||||
|
||||
// maxPaginationPages caps auto-pagination as a safety guard against
|
||||
// endpoints that ignore the page parameter and keep returning data.
|
||||
const maxPaginationPages = 1000
|
||||
|
||||
// PaginateAll fetches all pages and returns combined results.
|
||||
// The list array is auto-detected inside the response body.
|
||||
func (c *Client) PaginateAll(path string, params url.Values) ([]json.RawMessage, error) {
|
||||
return c.PaginateAllKey(path, params, "")
|
||||
}
|
||||
|
||||
// PaginateAllKey fetches all pages, extracting the list array from the
|
||||
// response field named listKey (e.g. "issues", "pulls", "branches").
|
||||
// When listKey is empty the array is auto-detected: top-level arrays,
|
||||
// the conventional "data" wrapper, or a unique array-valued field.
|
||||
// Pagination stops when a page returns fewer items than the limit, when
|
||||
// total_count (if reported) is reached, or at the safety page cap.
|
||||
func (c *Client) PaginateAllKey(path string, params url.Values, listKey string) ([]json.RawMessage, error) {
|
||||
if params == nil {
|
||||
params = url.Values{}
|
||||
}
|
||||
|
|
@ -17,57 +32,91 @@ func (c *Client) PaginateAll(path string, params url.Values) ([]json.RawMessage,
|
|||
}
|
||||
|
||||
var all []json.RawMessage
|
||||
page := 1
|
||||
totalCount := -1
|
||||
|
||||
for {
|
||||
for page := 1; page <= maxPaginationPages; page++ {
|
||||
params.Set("page", strconv.Itoa(page))
|
||||
env, err := c.Get(path, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !env.OK {
|
||||
return nil, fmt.Errorf("API error on page %d", page)
|
||||
}
|
||||
|
||||
// Try to extract array from data
|
||||
var items []json.RawMessage
|
||||
switch data := env.Data.(type) {
|
||||
case []interface{}:
|
||||
for _, item := range data {
|
||||
raw, _ := json.Marshal(item)
|
||||
items = append(items, raw)
|
||||
}
|
||||
case map[string]interface{}:
|
||||
// Some endpoints wrap in {"data": [...], "total_count": N}
|
||||
if arr, ok := data["data"]; ok {
|
||||
if slice, ok := arr.([]interface{}); ok {
|
||||
for _, item := range slice {
|
||||
raw, _ := json.Marshal(item)
|
||||
items = append(items, raw)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Single object, not paginated
|
||||
raw, _ := json.Marshal(data)
|
||||
items, pageTotal, isList := extractListItems(env.Data, listKey)
|
||||
if !isList {
|
||||
if page == 1 {
|
||||
raw, _ := json.Marshal(env.Data)
|
||||
return []json.RawMessage{raw}, nil
|
||||
}
|
||||
break
|
||||
}
|
||||
if pageTotal >= 0 {
|
||||
totalCount = pageTotal
|
||||
}
|
||||
|
||||
if len(items) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
all = append(all, items...)
|
||||
|
||||
// Check if we got fewer items than limit
|
||||
if totalCount >= 0 && len(all) >= totalCount {
|
||||
break
|
||||
}
|
||||
limit, _ := strconv.Atoi(params.Get("limit"))
|
||||
if len(items) < limit {
|
||||
break
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
|
||||
return all, nil
|
||||
}
|
||||
|
||||
// extractListItems locates the list array inside a decoded response body.
|
||||
// It returns the items, the reported total_count (-1 when absent) and
|
||||
// whether a list array was found at all.
|
||||
func extractListItems(data interface{}, listKey string) ([]json.RawMessage, int, bool) {
|
||||
switch v := data.(type) {
|
||||
case []interface{}:
|
||||
return marshalItems(v), -1, true
|
||||
case map[string]interface{}:
|
||||
total := -1
|
||||
if tc, ok := v["total_count"].(float64); ok {
|
||||
total = int(tc)
|
||||
}
|
||||
if listKey != "" {
|
||||
if slice, ok := v[listKey].([]interface{}); ok {
|
||||
return marshalItems(slice), total, true
|
||||
}
|
||||
return nil, total, false
|
||||
}
|
||||
if slice, ok := v["data"].([]interface{}); ok {
|
||||
return marshalItems(slice), total, true
|
||||
}
|
||||
// Auto-detect: GitLink v1 list endpoints wrap the array in a
|
||||
// resource-named field ({"total_count":N,"issues":[...]}).
|
||||
var found []interface{}
|
||||
arrays := 0
|
||||
for _, val := range v {
|
||||
if slice, ok := val.([]interface{}); ok {
|
||||
arrays++
|
||||
found = slice
|
||||
}
|
||||
}
|
||||
if arrays == 1 {
|
||||
return marshalItems(found), total, true
|
||||
}
|
||||
return nil, total, false
|
||||
}
|
||||
return nil, -1, false
|
||||
}
|
||||
|
||||
func marshalItems(items []interface{}) []json.RawMessage {
|
||||
out := make([]json.RawMessage, 0, len(items))
|
||||
for _, item := range items {
|
||||
raw, _ := json.Marshal(item)
|
||||
out = append(out, raw)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,154 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPaginateAllKeyResourceWrappedPages(t *testing.T) {
|
||||
// GitLink v1 list shape: {"total_count":N, "issues":[...]}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
||||
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
||||
if limit != 2 {
|
||||
t.Fatalf("limit = %d, want 2", limit)
|
||||
}
|
||||
var items []map[string]interface{}
|
||||
switch page {
|
||||
case 1:
|
||||
items = []map[string]interface{}{{"id": 1}, {"id": 2}}
|
||||
case 2:
|
||||
items = []map[string]interface{}{{"id": 3}}
|
||||
default:
|
||||
t.Fatalf("unexpected page %d", page)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"total_count": 3,
|
||||
"issues": items,
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
c := &Client{HTTP: server.Client(), BaseURL: server.URL}
|
||||
params := url.Values{}
|
||||
params.Set("limit", "2")
|
||||
items, err := c.PaginateAllKey("/owner/repo/issues", params, "issues")
|
||||
if err != nil {
|
||||
t.Fatalf("PaginateAllKey: %v", err)
|
||||
}
|
||||
if len(items) != 3 {
|
||||
t.Fatalf("len(items) = %d, want 3", len(items))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaginateAllKeyStopsAtTotalCount(t *testing.T) {
|
||||
// A broken endpoint that keeps returning full pages must stop at total_count.
|
||||
calls := 0
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
calls++
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"total_count": 4,
|
||||
"pulls": []map[string]interface{}{{"id": 1}, {"id": 2}},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
c := &Client{HTTP: server.Client(), BaseURL: server.URL}
|
||||
params := url.Values{}
|
||||
params.Set("limit", "2")
|
||||
items, err := c.PaginateAllKey("/owner/repo/pulls", params, "pulls")
|
||||
if err != nil {
|
||||
t.Fatalf("PaginateAllKey: %v", err)
|
||||
}
|
||||
if len(items) != 4 {
|
||||
t.Fatalf("len(items) = %d, want 4", len(items))
|
||||
}
|
||||
if calls != 2 {
|
||||
t.Fatalf("calls = %d, want 2", calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaginateAllAutoDetectsUniqueArrayField(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"total_count": 1,
|
||||
"branches": []map[string]interface{}{{"name": "master"}},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
c := &Client{HTTP: server.Client(), BaseURL: server.URL}
|
||||
items, err := c.PaginateAll("/owner/repo/branches", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("PaginateAll: %v", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("len(items) = %d, want 1", len(items))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaginateAllDataWrapper(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprint(w, `{"data":[{"id":1}],"total_count":1}`)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
c := &Client{HTTP: server.Client(), BaseURL: server.URL}
|
||||
items, err := c.PaginateAll("/things", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("PaginateAll: %v", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("len(items) = %d, want 1", len(items))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaginateAllSingleObjectFallback(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprint(w, `{"id":42,"name":"solo"}`)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
c := &Client{HTTP: server.Client(), BaseURL: server.URL}
|
||||
items, err := c.PaginateAll("/thing", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("PaginateAll: %v", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("len(items) = %d, want 1", len(items))
|
||||
}
|
||||
var obj map[string]interface{}
|
||||
if err := json.Unmarshal(items[0], &obj); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
if obj["name"] != "solo" {
|
||||
t.Fatalf("name = %v, want solo", obj["name"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaginateAllKeyMissingKeyNotList(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprint(w, `{"id":42}`)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
c := &Client{HTTP: server.Client(), BaseURL: server.URL}
|
||||
items, err := c.PaginateAllKey("/thing", nil, "issues")
|
||||
if err != nil {
|
||||
t.Fatalf("PaginateAllKey: %v", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("len(items) = %d, want 1 (single-object fallback)", len(items))
|
||||
}
|
||||
}
|
||||
|
|
@ -114,6 +114,7 @@
|
|||
"error.missing_required_flag": "required flag --{name} is missing",
|
||||
"error.profile.user_required": "could not determine target user; pass --user or run gitlink-cli auth login",
|
||||
"error.unsupported_language": "unsupported language: {lang}",
|
||||
"flag.all": "Fetch all pages automatically (ignores --page)",
|
||||
"flag.api.batch_continue_on_error": "Continue running remaining batch requests after a failure",
|
||||
"flag.api.batch_dry_run": "Preview batch requests without sending remote requests",
|
||||
"flag.api.batch_file": "Read an API batch plan from a JSON file",
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@
|
|||
"error.missing_required_flag": "缺少必需参数 --{name}",
|
||||
"error.profile.user_required": "无法确定目标用户;请通过 --user 指定,或先运行 gitlink-cli auth login 登录",
|
||||
"error.unsupported_language": "不支持的语言:{lang}",
|
||||
"flag.all": "自动获取全部分页(忽略 --page)",
|
||||
"flag.api.batch_continue_on_error": "批处理请求失败后继续执行后续请求",
|
||||
"flag.api.batch_dry_run": "预览批处理请求,不发送远端请求",
|
||||
"flag.api.batch_file": "从 JSON 文件读取 API 批处理计划",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
Flags: []common.Flag{
|
||||
{Name: "page", Short: "p", Usage: tr.T("flag.page"), Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: tr.T("flag.limit"), Default: "20"},
|
||||
{Name: "all", Usage: tr.T("flag.all"), Bool: true, Default: "false"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -25,6 +26,13 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
q := url.Values{}
|
||||
q.Set("page", ctx.Arg("page"))
|
||||
q.Set("limit", ctx.Arg("limit"))
|
||||
if ctx.Arg("all") == "true" {
|
||||
items, err := ctx.PaginateAllKey("/v1"+ctx.RepoPath()+"/branches", q, "branches")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(common.NewListEnvelope("branches", items))
|
||||
}
|
||||
env, err := ctx.CallAPIWithQuery("GET", "/v1"+ctx.RepoPath()+"/branches", q)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -95,6 +95,29 @@ func (ctx *RuntimeContext) PaginateAll(path string, params url.Values) ([]json.R
|
|||
return ctx.Client.PaginateAll(path, params)
|
||||
}
|
||||
|
||||
// PaginateAllKey fetches all pages of a list endpoint whose response wraps
|
||||
// the array in the field named listKey (e.g. "issues", "pulls").
|
||||
func (ctx *RuntimeContext) PaginateAllKey(path string, params url.Values, listKey string) ([]json.RawMessage, error) {
|
||||
return ctx.Client.PaginateAllKey(path, params, listKey)
|
||||
}
|
||||
|
||||
// NewListEnvelope wraps combined pages in the same shape as a single-page
|
||||
// response: {"total_count": N, "<listKey>": [...]}.
|
||||
func NewListEnvelope(listKey string, items []json.RawMessage) *output.Envelope {
|
||||
decoded := make([]interface{}, 0, len(items))
|
||||
for _, item := range items {
|
||||
var v interface{}
|
||||
if err := json.Unmarshal(item, &v); err == nil {
|
||||
decoded = append(decoded, v)
|
||||
}
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"total_count": len(decoded),
|
||||
listKey: decoded,
|
||||
}
|
||||
return output.SuccessEnvelope(data, &output.Meta{TotalCount: len(decoded)})
|
||||
}
|
||||
|
||||
// Output prints the envelope in the configured format.
|
||||
func (ctx *RuntimeContext) Output(env *output.Envelope) error {
|
||||
return output.Print(env, ctx.Format)
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
{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"},
|
||||
{Name: "all", Usage: tr.T("flag.all"), Bool: true, Default: "false"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -101,6 +102,15 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
if sortDirection := ctx.Arg("sort-direction"); sortDirection != "" {
|
||||
q.Set("sort_direction", sortDirection)
|
||||
}
|
||||
if ctx.Arg("all") == "true" {
|
||||
items, err := ctx.PaginateAllKey(v1RepoPath(ctx)+"/issues", q, "issues")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env := common.NewListEnvelope("issues", items)
|
||||
normalizeIssueListIDs(env)
|
||||
return ctx.Output(env)
|
||||
}
|
||||
env, err := ctx.CallAPIWithQuery("GET", v1RepoPath(ctx)+"/issues", q)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -171,6 +171,42 @@ func TestIssueListStateAll(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestIssueListAllPaginates(t *testing.T) {
|
||||
var pages []string
|
||||
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)
|
||||
}
|
||||
page := r.URL.Query().Get("page")
|
||||
pages = append(pages, page)
|
||||
assertEqual(t, r.URL.Query().Get("limit"), "2")
|
||||
var issues []interface{}
|
||||
if page == "1" {
|
||||
issues = []interface{}{
|
||||
map[string]interface{}{"id": float64(1), "project_issues_index": float64(11)},
|
||||
map[string]interface{}{"id": float64(2), "project_issues_index": float64(12)},
|
||||
}
|
||||
} else {
|
||||
issues = []interface{}{
|
||||
map[string]interface{}{"id": float64(3), "project_issues_index": float64(13)},
|
||||
}
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"total_count": float64(3),
|
||||
"issues": issues,
|
||||
})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "list", map[string]string{"all": "true", "limit": "2"})
|
||||
if err != nil {
|
||||
t.Fatalf("list --all failed: %v", err)
|
||||
}
|
||||
if len(pages) != 2 || pages[0] != "1" || pages[1] != "2" {
|
||||
t.Fatalf("pages requested = %v, want [1 2]", pages)
|
||||
}
|
||||
}
|
||||
|
||||
// --- create ---
|
||||
|
||||
func TestIssueCreate(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
{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"},
|
||||
{Name: "all", Usage: tr.T("flag.all"), Bool: true, Default: "false"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -82,6 +83,13 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
if sortDirection := ctx.Arg("sort-direction"); sortDirection != "" {
|
||||
q.Set("sort_direction", sortDirection)
|
||||
}
|
||||
if ctx.Arg("all") == "true" {
|
||||
items, err := ctx.PaginateAllKey(v1RepoPath(ctx)+"/pulls", q, "pulls")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(common.NewListEnvelope("pulls", items))
|
||||
}
|
||||
env, err := ctx.CallAPIWithQuery("GET", v1RepoPath(ctx)+"/pulls", q)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
Flags: []common.Flag{
|
||||
{Name: "page", Short: "p", Usage: tr.T("flag.page"), Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: tr.T("flag.limit"), Default: "20"},
|
||||
{Name: "all", Usage: tr.T("flag.all"), Bool: true, Default: "false"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -28,6 +29,13 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
q := url.Values{}
|
||||
q.Set("page", ctx.Arg("page"))
|
||||
q.Set("limit", ctx.Arg("limit"))
|
||||
if ctx.Arg("all") == "true" {
|
||||
items, err := ctx.PaginateAllKey(ctx.RepoPath()+"/releases", q, "releases")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(common.NewListEnvelope("releases", items))
|
||||
}
|
||||
env, err := ctx.CallAPIWithQuery("GET", ctx.RepoPath()+"/releases", q)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
Loading…
Reference in New Issue