diff --git a/internal/app/completion.go b/internal/app/completion.go index c72ce160..f596b3f6 100644 --- a/internal/app/completion.go +++ b/internal/app/completion.go @@ -5,10 +5,8 @@ package app import ( "context" - "encoding/json" "errors" "maps" - "net/http" "slices" "strconv" "strings" @@ -18,7 +16,6 @@ import ( "gitea.dev/tea/internal/services" "gitea.dev/tea/internal/settings" "gitea.dev/tea/internal/terminal" - "gitea.dev/tea/internal/transport" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -337,16 +334,6 @@ func (a *application) completionTarget(ctx context.Context, requireRepo bool) (t return t, nil } -// fetchJSON decodes the response to a GET of endpoint, for values that no service lists. -func fetchJSON(ctx context.Context, client *transport.Client, endpoint string, out any) error { - return services.API{Client: client}.Visit(ctx, services.APIOptions{Method: http.MethodGet, Endpoint: endpoint}, func(response *http.Response) error { - if response.StatusCode != http.StatusOK { - return nil // Visit reports the status. - } - return json.NewDecoder(response.Body).Decode(out) - }) -} - // completeIssues completes issue numbers in state (open, closed or all) with their titles. func (a *application) completeIssues(state string) cobra.CompletionFunc { return a.apiCompletion(true, func(ctx context.Context, t target, _ string) ([]cobra.Completion, error) { @@ -390,13 +377,8 @@ func (a *application) completeMilestones() cobra.CompletionFunc { // completeUsers completes comma-separated usernames that can be assigned in the repository. func (a *application) completeUsers() cobra.CompletionFunc { return commaSeparated(a.apiCompletion(true, func(ctx context.Context, t target, _ string) ([]cobra.Completion, error) { - type user struct { - Login string `json:"login"` - FullName string `json:"full_name"` - } - var users []user - err := fetchJSON(ctx, t.client, "repos/"+t.Repo+"/assignees", &users) - return describe(users, func(item user) (string, string) { return item.Login, item.FullName }), err + users, err := services.Repositories{Client: t.client}.Assignees(ctx, t.Repo) + return describe(users, func(item services.Assignee) (string, string) { return item.Username, item.FullName }), err })) } @@ -416,12 +398,8 @@ func (a *application) completeBranches() cobra.CompletionFunc { // completeTags completes the repository's tags. func (a *application) completeTags() cobra.CompletionFunc { return a.apiCompletion(true, func(ctx context.Context, t target, _ string) ([]cobra.Completion, error) { - type tag struct { - Name string `json:"name"` - } - var tags []tag - err := fetchJSON(ctx, t.client, "repos/"+t.Repo+"/tags?limit="+strconv.Itoa(completionLimit), &tags) - return describe(tags, func(item tag) (string, string) { return item.Name, "" }), err + tags, err := services.Repositories{Client: t.client}.Tags(ctx, t.Repo, completionLimit, 1) + return describe(tags, func(name string) (string, string) { return name, "" }), err }) } diff --git a/internal/services/repositories.go b/internal/services/repositories.go index 18596319..e033d57f 100644 --- a/internal/services/repositories.go +++ b/internal/services/repositories.go @@ -145,15 +145,39 @@ func (s Repositories) viewPermissions(ctx context.Context, repo string) (Reposit return RepositoryPermissions{Admin: item.Permissions.Admin, Push: item.Permissions.Push, Pull: item.Permissions.Pull}, nil } -// assignees lists usernames that can be assigned on repository issues. -func (s Repositories) assignees(ctx context.Context, repo string) ([]string, error) { +// Assignee is a user that can be assigned on repository issues. +type Assignee struct { + Username string `json:"username"` + FullName string `json:"full_name"` +} + +// Assignees lists the users that can be assigned on repository issues. +func (s Repositories) Assignees(ctx context.Context, repo string) ([]Assignee, error) { users, err := repoCall(ctx, s.Client, repo, func(ctx context.Context, c *gitea.Client, owner, name string) ([]*gitea.User, *gitea.Response, error) { return c.Repositories.GetAssignees(ctx, owner, name) }) + return convertAll(users, func(user *gitea.User) Assignee { return Assignee{Username: user.UserName, FullName: user.FullName} }), err +} + +// assignees lists usernames that can be assigned on repository issues. +func (s Repositories) assignees(ctx context.Context, repo string) ([]string, error) { + users, err := s.Assignees(ctx, repo) if err != nil { return nil, err } - return convertAll(users, login), nil + names := make([]string, len(users)) + for i, user := range users { + names[i] = user.Username + } + return names, nil +} + +// Tags lists a page of the repository's tag names. +func (s Repositories) Tags(ctx context.Context, repo string, limit, page int) ([]string, error) { + tags, err := repoCall(ctx, s.Client, repo, func(ctx context.Context, c *gitea.Client, owner, name string) ([]*gitea.Tag, *gitea.Response, error) { + return c.Repositories.ListRepoTags(ctx, owner, name, gitea.ListRepoTagsOptions{ListOptions: listOptions(limit, page)}) + }) + return convertAll(tags, func(tag *gitea.Tag) string { return tag.Name }), err } // Create creates a repository for the account or the named organization. diff --git a/internal/services/repositories_test.go b/internal/services/repositories_test.go index ad77d37d..8b4dd2c4 100644 --- a/internal/services/repositories_test.go +++ b/internal/services/repositories_test.go @@ -51,10 +51,16 @@ func TestRepositoryForkSendsOrganizationOnlyWhenSelected(t *testing.T) { } } -func TestRepositoryAssigneesAndMissingPermissions(t *testing.T) { +func TestRepositoryAssigneesTagsAndMissingPermissions(t *testing.T) { client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/api/v1/repos/alice/project/assignees" { - _, _ = w.Write([]byte(`[{"login":"alice"},{"login":"bob"}]`)) + switch r.URL.Path { + case "/api/v1/repos/alice/project/assignees": + _, _ = w.Write([]byte(`[{"login":"alice","full_name":"Alice A."},{"login":"bob"}]`)) + return + case "/api/v1/repos/alice/project/tags": + assert.Equal(t, "2", r.URL.Query().Get("page")) + assert.Equal(t, "5", r.URL.Query().Get("limit")) + _, _ = w.Write([]byte(`[{"name":"v1.0"},{"name":"v0.9"}]`)) return } _, _ = w.Write([]byte(`{"id":1}`)) @@ -63,6 +69,12 @@ func TestRepositoryAssigneesAndMissingPermissions(t *testing.T) { names, err := service.assignees(context.Background(), "alice/project") require.NoError(t, err) require.Equal(t, []string{"alice", "bob"}, names) + users, err := service.Assignees(context.Background(), "alice/project") + require.NoError(t, err) + require.Equal(t, []Assignee{{Username: "alice", FullName: "Alice A."}, {Username: "bob"}}, users) + tags, err := service.Tags(context.Background(), "alice/project", 5, 2) + require.NoError(t, err) + require.Equal(t, []string{"v1.0", "v0.9"}, tags) permissions, err := service.viewPermissions(context.Background(), "alice/project") require.NoError(t, err) require.Equal(t, RepositoryPermissions{}, permissions)