Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ func DetectForgeType(ctx context.Context, domain string, hc ...*http.Client) (Fo
baseURL := "https://" + domain

ft, err := detectFromHeaders(ctx, client, baseURL)
if err == nil && ft != Unknown {
if err != nil {
return Unknown, fmt.Errorf("could not detect forge type for %s: %w", baseURL, err)
}
if ft != Unknown {
return ft, nil
}

Expand Down
26 changes: 26 additions & 0 deletions detect_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package forges

import (
"context"
"errors"
"net/http"
"strings"
"testing"
)

type errTransport struct{ err error }

func (t errTransport) RoundTrip(*http.Request) (*http.Response, error) { return nil, t.err }

func TestDetectForgeTypeSurfacesTransportError(t *testing.T) {
netErr := errors.New("dial tcp: lookup forge.invalid: no such host")
hc := &http.Client{Transport: errTransport{err: netErr}}

_, err := DetectForgeType(context.Background(), "forge.invalid", hc)
if err == nil {
t.Fatal("expected error")
}
if !strings.Contains(err.Error(), netErr.Error()) {
t.Fatalf("expected transport error to be surfaced, got: %v", err)
}
}
7 changes: 2 additions & 5 deletions gitea/branches.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ func (f *giteaForge) Branches() forge.BranchService {
}

func (s *giteaBranchService) List(ctx context.Context, owner, repo string, opts forge.ListBranchOpts) ([]forge.Branch, error) {
perPage := opts.PerPage
if perPage <= 0 {
perPage = 30
}
perPage := pageSize(opts.PerPage)
page := opts.Page
if page <= 0 {
page = 1
Expand All @@ -47,7 +44,7 @@ func (s *giteaBranchService) List(ctx context.Context, owner, repo string, opts
}
all = append(all, branch)
}
if len(branches) < perPage || (opts.Limit > 0 && len(all) >= opts.Limit) {
if lastPage(resp, len(branches), perPage) || (opts.Limit > 0 && len(all) >= opts.Limit) {
break
}
page++
Expand Down
7 changes: 2 additions & 5 deletions gitea/ci.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,7 @@ func convertGiteaWorkflowJob(j *gitea.ActionWorkflowJob) forge.CIJob {
}

func (s *giteaCIService) ListRuns(_ context.Context, owner, repo string, opts forge.ListCIRunOpts) ([]forge.CIRun, error) {
perPage := opts.PerPage
if perPage <= 0 {
perPage = 20
}
perPage := pageSize(opts.PerPage)
page := opts.Page
if page <= 0 {
page = 1
Expand Down Expand Up @@ -104,7 +101,7 @@ func (s *giteaCIService) ListRuns(_ context.Context, owner, repo string, opts fo
for _, r := range resp.WorkflowRuns {
all = append(all, convertGiteaWorkflowRun(r))
}
if len(resp.WorkflowRuns) < perPage || (opts.Limit > 0 && len(all) >= opts.Limit) {
if lastPage(httpResp, len(resp.WorkflowRuns), perPage) || (opts.Limit > 0 && len(all) >= opts.Limit) {
break
}
gOpts.Page++
Expand Down
7 changes: 2 additions & 5 deletions gitea/collaborators.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ func (f *giteaForge) Collaborators() forge.CollaboratorService {
}

func (s *giteaCollaboratorService) List(ctx context.Context, owner, repo string, opts forge.ListCollaboratorOpts) ([]forge.Collaborator, error) {
perPage := opts.PerPage
if perPage <= 0 {
perPage = 50
}
perPage := pageSize(opts.PerPage)
page := opts.Page
if page <= 0 {
page = 1
Expand All @@ -47,7 +44,7 @@ func (s *giteaCollaboratorService) List(ctx context.Context, owner, repo string,
Permission: perm,
})
}
if len(users) < perPage || (opts.Limit > 0 && len(all) >= opts.Limit) {
if lastPage(resp, len(users), perPage) || (opts.Limit > 0 && len(all) >= opts.Limit) {
break
}
page++
Expand Down
24 changes: 24 additions & 0 deletions gitea/commit_statuses.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,30 @@ import (

const defaultPageSize = 50

// pageSize caps the requested page size at Gitea's default MAX_RESPONSE_ITEMS.
// Servers clamp larger values, which breaks len(results) < perPage loop exits.
func pageSize(perPage int) int {
if perPage <= 0 || perPage > defaultPageSize {
return defaultPageSize
}
return perPage
}

// lastPage reports whether a paginated response was the final page. It trusts
// the SDK-parsed Link headers when the server sent any, since Gitea clamps the
// page size to MAX_RESPONSE_ITEMS and a clamped page would otherwise look like
// a short final page. Falls back to the short-page heuristic when no Link
// header was sent.
func lastPage(resp *gitea.Response, got, perPage int) bool {
if got == 0 {
return true
}
if resp != nil && (resp.FirstPage > 0 || resp.PrevPage > 0 || resp.NextPage > 0 || resp.LastPage > 0) {
return resp.NextPage == 0
}
return got < perPage
}

type giteaCommitStatusService struct {
client *gitea.Client
}
Expand Down
7 changes: 2 additions & 5 deletions gitea/deploy_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ func (f *giteaForge) DeployKeys() forge.DeployKeyService {
}

func (s *giteaDeployKeyService) List(ctx context.Context, owner, repo string, opts forge.ListDeployKeyOpts) ([]forge.DeployKey, error) {
perPage := opts.PerPage
if perPage <= 0 {
perPage = 30
}
perPage := pageSize(opts.PerPage)
page := opts.Page
if page <= 0 {
page = 1
Expand All @@ -46,7 +43,7 @@ func (s *giteaDeployKeyService) List(ctx context.Context, owner, repo string, op
CreatedAt: k.Created,
})
}
if len(keys) < perPage || (opts.Limit > 0 && len(all) >= opts.Limit) {
if lastPage(resp, len(keys), perPage) || (opts.Limit > 0 && len(all) >= opts.Limit) {
break
}
page++
Expand Down
21 changes: 6 additions & 15 deletions gitea/gitea.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,7 @@ func (s *giteaRepoService) Get(ctx context.Context, owner, repo string) (*forge.
}

func (s *giteaRepoService) List(ctx context.Context, owner string, opts forge.ListRepoOpts) ([]forge.Repository, error) {
perPage := opts.PerPage
if perPage <= 0 {
perPage = defaultPageSize
}
perPage := pageSize(opts.PerPage)

// Try org endpoint first, fall back to user on 404.
repos, err := s.listOrgRepos(ctx, owner, perPage)
Expand Down Expand Up @@ -133,7 +130,7 @@ func (s *giteaRepoService) listOrgRepos(_ context.Context, owner string, perPage
for _, r := range gRepos {
all = append(all, convertGiteaRepo(r))
}
if len(gRepos) < perPage {
if lastPage(resp, len(gRepos), perPage) {
break
}
page++
Expand All @@ -157,7 +154,7 @@ func (s *giteaRepoService) listUserRepos(_ context.Context, owner string, perPag
for _, r := range gRepos {
all = append(all, convertGiteaRepo(r))
}
if len(gRepos) < perPage {
if lastPage(resp, len(gRepos), perPage) {
break
}
page++
Expand Down Expand Up @@ -300,10 +297,7 @@ func (s *giteaRepoService) Fork(ctx context.Context, owner, repo string, opts fo
}

func (s *giteaRepoService) ListForks(ctx context.Context, owner, repo string, opts forge.ListForksOpts) ([]forge.Repository, error) {
perPage := opts.PerPage
if perPage <= 0 {
perPage = defaultPageSize
}
perPage := pageSize(opts.PerPage)
page := opts.Page
if page <= 0 {
page = 1
Expand All @@ -323,7 +317,7 @@ func (s *giteaRepoService) ListForks(ctx context.Context, owner, repo string, op
for _, r := range forks {
all = append(all, convertGiteaRepo(r))
}
if len(forks) < perPage || (opts.Limit > 0 && len(all) >= opts.Limit) {
if lastPage(resp, len(forks), perPage) || (opts.Limit > 0 && len(all) >= opts.Limit) {
break
}
page++
Expand Down Expand Up @@ -369,10 +363,7 @@ func (s *giteaRepoService) ListContributors(ctx context.Context, owner, repo str
}

func (s *giteaRepoService) Search(ctx context.Context, opts forge.SearchRepoOpts) ([]forge.Repository, error) {
perPage := opts.PerPage
if perPage <= 0 {
perPage = 30
}
perPage := pageSize(opts.PerPage)
page := opts.Page
if page <= 0 {
page = 1
Expand Down
48 changes: 48 additions & 0 deletions gitea/gitea_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
forge "github.com/git-pkgs/forge"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
Expand Down Expand Up @@ -198,6 +199,53 @@ func TestGiteaListRepos(t *testing.T) {
assertEqual(t, "repos[1].FullName", "testorg/repo-b", repos[1].FullName)
}

func TestGiteaListReposPaginatesWhenServerClampsPageSize(t *testing.T) {
const serverCap = 2
total := 5

pages := (total + serverCap - 1) / serverCap

mux := http.NewServeMux()
mux.HandleFunc("GET /api/v1/version", giteaVersionHandler)
mux.HandleFunc("GET /api/v1/orgs/testorg/repos", func(w http.ResponseWriter, r *http.Request) {
page := 1
_, _ = fmt.Sscan(r.URL.Query().Get("page"), &page)

var links []string
links = append(links, `<?page=1>; rel="first"`, fmt.Sprintf(`<?page=%d>; rel="last"`, pages))
if page < pages {
links = append(links, fmt.Sprintf(`<?page=%d>; rel="next"`, page+1))
}
if page > 1 {
links = append(links, fmt.Sprintf(`<?page=%d>; rel="prev"`, page-1))
}
w.Header().Set("Link", strings.Join(links, ", "))

start := (page - 1) * serverCap
var out []map[string]any
for i := start; i < start+serverCap && i < total; i++ {
out = append(out, map[string]any{
"full_name": fmt.Sprintf("testorg/repo-%d", i),
"name": fmt.Sprintf("repo-%d", i),
"owner": map[string]any{"login": "testorg"},
})
}
_ = json.NewEncoder(w).Encode(out)
})

srv := httptest.NewServer(mux)
defer srv.Close()

f := New(srv.URL, "", nil)
repos, err := f.Repos().List(context.Background(), "testorg", forge.ListRepoOpts{PerPage: 100})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(repos) != total {
t.Fatalf("expected %d repos across pages, got %d", total, len(repos))
}
}

func TestGiteaListReposFallbackToUser(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/v1/version", giteaVersionHandler)
Expand Down
7 changes: 2 additions & 5 deletions gitea/issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,7 @@ func (s *giteaIssueService) Get(ctx context.Context, owner, repo string, number
}

func (s *giteaIssueService) List(ctx context.Context, owner, repo string, opts forge.ListIssueOpts) ([]forge.Issue, error) {
perPage := opts.PerPage
if perPage <= 0 {
perPage = 30
}
perPage := pageSize(opts.PerPage)
page := opts.Page
if page <= 0 {
page = 1
Expand Down Expand Up @@ -160,7 +157,7 @@ func (s *giteaIssueService) List(ctx context.Context, owner, repo string, opts f
for _, i := range issues {
all = append(all, convertGiteaIssue(i))
}
if len(issues) < perPage || (opts.Limit > 0 && len(all) >= opts.Limit) {
if lastPage(resp, len(issues), perPage) || (opts.Limit > 0 && len(all) >= opts.Limit) {
break
}
gOpts.Page++
Expand Down
7 changes: 2 additions & 5 deletions gitea/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,7 @@ func convertGiteaLabel(l *gitea.Label) forge.Label {
}

func (s *giteaLabelService) List(ctx context.Context, owner, repo string, opts forge.ListLabelOpts) ([]forge.Label, error) {
perPage := opts.PerPage
if perPage <= 0 {
perPage = defaultPageSize
}
perPage := pageSize(opts.PerPage)
page := opts.Page
if page <= 0 {
page = 1
Expand All @@ -50,7 +47,7 @@ func (s *giteaLabelService) List(ctx context.Context, owner, repo string, opts f
for _, l := range labels {
all = append(all, convertGiteaLabel(l))
}
if len(labels) < perPage || (opts.Limit > 0 && len(all) >= opts.Limit) {
if lastPage(resp, len(labels), perPage) || (opts.Limit > 0 && len(all) >= opts.Limit) {
break
}
page++
Expand Down
7 changes: 2 additions & 5 deletions gitea/milestones.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,7 @@ func convertGiteaMilestone(m *gitea.Milestone) forge.Milestone {
}

func (s *giteaMilestoneService) List(ctx context.Context, owner, repo string, opts forge.ListMilestoneOpts) ([]forge.Milestone, error) {
perPage := opts.PerPage
if perPage <= 0 {
perPage = 30
}
perPage := pageSize(opts.PerPage)
page := opts.Page
if page <= 0 {
page = 1
Expand Down Expand Up @@ -74,7 +71,7 @@ func (s *giteaMilestoneService) List(ctx context.Context, owner, repo string, op
for _, m := range milestones {
all = append(all, convertGiteaMilestone(m))
}
if len(milestones) < perPage || (opts.Limit > 0 && len(all) >= opts.Limit) {
if lastPage(resp, len(milestones), perPage) || (opts.Limit > 0 && len(all) >= opts.Limit) {
break
}
gOpts.Page++
Expand Down
11 changes: 4 additions & 7 deletions gitea/notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,7 @@ func convertGiteaNotification(n *gitea.NotificationThread) forge.Notification {
}

func (s *giteaNotificationService) List(ctx context.Context, opts forge.ListNotificationOpts) ([]forge.Notification, error) {
perPage := opts.PerPage
if perPage <= 0 {
perPage = 30
}
perPage := pageSize(opts.PerPage)
page := opts.Page
if page <= 0 {
page = 1
Expand Down Expand Up @@ -108,7 +105,7 @@ func (s *giteaNotificationService) listRepoNotifications(owner, repo string, pag
for _, n := range notifications {
all = append(all, convertGiteaNotification(n))
}
if len(notifications) < perPage || (limit > 0 && len(all) >= limit) {
if lastPage(resp, len(notifications), perPage) || (limit > 0 && len(all) >= limit) {
break
}
page++
Expand All @@ -119,7 +116,7 @@ func (s *giteaNotificationService) listRepoNotifications(owner, repo string, pag
func (s *giteaNotificationService) listAllNotifications(page, perPage int, statuses []gitea.NotifyStatus, limit int) ([]forge.Notification, error) {
var all []forge.Notification
for {
notifications, _, err := s.client.ListNotifications(gitea.ListNotificationOptions{
notifications, resp, err := s.client.ListNotifications(gitea.ListNotificationOptions{
ListOptions: gitea.ListOptions{Page: page, PageSize: perPage},
Status: statuses,
})
Expand All @@ -129,7 +126,7 @@ func (s *giteaNotificationService) listAllNotifications(page, perPage int, statu
for _, n := range notifications {
all = append(all, convertGiteaNotification(n))
}
if len(notifications) < perPage || (limit > 0 && len(all) >= limit) {
if lastPage(resp, len(notifications), perPage) || (limit > 0 && len(all) >= limit) {
break
}
page++
Expand Down
Loading
Loading