Before this, only `release list` walked pages. `repo list`, `pr list` (the
non-filter branch), and `issue list` all passed `PageSize: limit` directly
to the gitea SDK — which silently caps PageSize at 50, so any request for
more than 50 results was truncated to 50 with no warning. `--limit` was
effectively a per-page hint, not a real limit.
## Changes
- New `cmd/paginate.go` — generic `paginateGitea[T any]` that walks pages
until the response is short or the limit is reached. Uses Go 1.20
generics so each list command keeps its existing typed slice without
conversion overhead.
- `repo list` — paginates ListUserRepos.
- `pr list` — paginates ListRepoPullRequests in both branches:
- With client-side filters (assignee, author, labels, search, draft,
head, base): pull all pages then filter+limit.
- Without filters: paginate up to limit.
- `issue list` — paginates ListRepoIssues. Overshoots 2x because the API
returns both issues AND PRs and we filter PRs out client-side; the
overshoot keeps us bounded but reduces the chance of returning fewer
results than `--limit`.
## `fj api --paginate`
Mirrors `gh api --paginate`:
- Follows RFC 5988 `Link: rel="next"` headers (Forgejo emits these on
list endpoints).
- Concatenates each page's JSON array into a single array via
`concatPaginatedJSON`. If a page is not a JSON array, errors with a
clear message — `--paginate` only makes sense for paginatable endpoints.
- GET-only (errors on POST/PUT/DELETE).
- Reuses the same auth and custom headers across pages; the body-size
limit applies per-page.
Refactored the request execution into a `doOnce` closure so the loop body
isn't a copy of the single-request path.
Verified live:
$ fj api 'repos/public/claude-code-proxy/commits?limit=2' \
--paginate --jq '. | length'
44
(44 = total commits in the repo, walked via Link headers from a 2-per-page
starting query.)
Out of scope for this commit, deferred:
- De-duplicating cmd/aliases.go ↔ cmd/actions.go subtrees (the type
mismatch they caused is already fixed in the prior commit; the
duplication itself is polish).
43 lines
1.2 KiB
Go
43 lines
1.2 KiB
Go
package cmd
|
|
|
|
// paginateGitea walks pages of a gitea SDK list method until the response
|
|
// is short (last page) or we hit limit. limit=0 means unlimited.
|
|
//
|
|
// Forgejo/Gitea caps PageSize at 50, so naive `PageSize: limit` for limit > 50
|
|
// silently truncated results across most `fj * list` commands. This helper
|
|
// centralizes the loop so every list command paginates consistently.
|
|
//
|
|
// fetch is called with (page, pageSize) and returns the items for that page.
|
|
// The 1-based `page` matches the gitea SDK convention.
|
|
func paginateGitea[T any](limit int, fetch func(page, pageSize int) ([]T, error)) ([]T, error) {
|
|
const maxPageSize = 50
|
|
pageSize := maxPageSize
|
|
if limit > 0 && limit < pageSize {
|
|
pageSize = limit
|
|
}
|
|
|
|
var all []T
|
|
for page := 1; ; page++ {
|
|
if limit > 0 && len(all) >= limit {
|
|
break
|
|
}
|
|
batch, err := fetch(page, pageSize)
|
|
if err != nil {
|
|
return all, err
|
|
}
|
|
if len(batch) == 0 {
|
|
break
|
|
}
|
|
all = append(all, batch...)
|
|
// A short page (less than the requested size) is the conventional
|
|
// "you've reached the end" signal — saves one extra round-trip.
|
|
if len(batch) < pageSize {
|
|
break
|
|
}
|
|
}
|
|
|
|
if limit > 0 && len(all) > limit {
|
|
all = all[:limit]
|
|
}
|
|
return all, nil
|
|
}
|