mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 19:11:25 +01:00
repo list and search repo printed bare names. They now render a table with NAME, DESCRIPTION, INFO (public or private, fork, mirror, template, archived, empty) and UPDATED under "Showing N of M repositories ...", an empty-state message on stderr, and tab-separated rows with the URL when piped. --type, --sort and --order complete and validate their values. repo view shows the visibility and kind badges, stars, forks, open issues and pull requests, the parent of a fork, the source of a mirror, the default branch and the clone URLs, followed by the README of the default branch (or --branch), rendered on a terminal. The repository JSON gains empty, stars_count, forks_count, open_issues_count, open_pr_count, topics and updated_at. Signed-off-by: bircni <bircni@icloud.com>
197 lines
8.0 KiB
Go
197 lines
8.0 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package services
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"slices"
|
|
"strings"
|
|
|
|
gitea "gitea.dev/sdk"
|
|
"gitea.dev/tea/internal/transport"
|
|
)
|
|
|
|
// Repositories implements Gitea repository operations.
|
|
type Repositories struct{ Client *transport.Client }
|
|
|
|
// Repository is the stable command representation of a repository.
|
|
type Repository struct {
|
|
ActionsEnabled bool `json:"actions_enabled"`
|
|
Website string `json:"website"`
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
FullName string `json:"full_name"`
|
|
Description string `json:"description"`
|
|
URL string `json:"url"`
|
|
Private bool `json:"private"`
|
|
DefaultBranch string `json:"default_branch"`
|
|
CloneURL string `json:"clone_url"`
|
|
SSHURL string `json:"ssh_url"`
|
|
Archived bool `json:"archived"`
|
|
Template bool `json:"template"`
|
|
Fork bool `json:"fork"`
|
|
Mirror bool `json:"mirror"`
|
|
Empty bool `json:"empty"`
|
|
Stars int `json:"stars_count"`
|
|
Forks int `json:"forks_count"`
|
|
OpenIssues int `json:"open_issues_count"`
|
|
OpenPulls int `json:"open_pr_count"`
|
|
Topics []string `json:"topics"`
|
|
UpdatedAt string `json:"updated_at"`
|
|
Parent *Repository `json:"parent,omitempty"`
|
|
// OriginalURL stays out of JSON output: migration sources may embed credentials.
|
|
OriginalURL string `json:"-"`
|
|
}
|
|
|
|
// RepositoryOptions supplies repository creation input.
|
|
type RepositoryOptions struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description,omitempty"`
|
|
Private bool `json:"private"`
|
|
AutoInit bool `json:"auto_init"`
|
|
DefaultBranch string `json:"default_branch,omitempty"`
|
|
Labels string `json:"issue_labels,omitempty"`
|
|
Gitignores string `json:"gitignores,omitempty"`
|
|
License string `json:"license,omitempty"`
|
|
Readme string `json:"readme,omitempty"`
|
|
Template bool `json:"template"`
|
|
TrustModel string `json:"trust_model,omitempty"`
|
|
ObjectFormat string `json:"object_format_name,omitempty"`
|
|
}
|
|
|
|
// RepositoryEdit distinguishes omitted settings from explicit false values.
|
|
type RepositoryEdit struct {
|
|
ActionsEnabled *bool `json:"has_actions,omitempty"`
|
|
Name *string `json:"name,omitempty"`
|
|
Website *string `json:"website,omitempty"`
|
|
DefaultBranch *string `json:"default_branch,omitempty"`
|
|
Description *string `json:"description,omitempty"`
|
|
Private *bool `json:"private,omitempty"`
|
|
Archived *bool `json:"archived,omitempty"`
|
|
Template *bool `json:"template,omitempty"`
|
|
}
|
|
|
|
// View retrieves one repository.
|
|
func (s Repositories) View(ctx context.Context, repo string) (Repository, error) {
|
|
item, err := s.get(ctx, repo)
|
|
if err != nil {
|
|
return Repository{}, err
|
|
}
|
|
return repositoryFrom(item), nil
|
|
}
|
|
|
|
func (s Repositories) get(ctx context.Context, repo string) (*gitea.Repository, error) {
|
|
return repoCall(ctx, s.Client, repo, func(ctx context.Context, c *gitea.Client, owner, name string) (*gitea.Repository, *gitea.Response, error) {
|
|
return c.Repositories.GetRepo(ctx, owner, name)
|
|
})
|
|
}
|
|
|
|
// readmeNames are the README file names that Gitea shows, in order of preference.
|
|
var readmeNames = []string{"readme.md", "readme.markdown", "readme", "readme.txt"}
|
|
|
|
// Readme returns the README in the top directory of the repository at ref: its file name and its
|
|
// content, or "" when there is none.
|
|
func (s Repositories) Readme(ctx context.Context, repo, ref string) (name, content string, err error) {
|
|
entries, err := repoCall(ctx, s.Client, repo, func(ctx context.Context, c *gitea.Client, owner, name string) ([]*gitea.ContentsResponse, *gitea.Response, error) {
|
|
return c.Repositories.ListContents(ctx, owner, name, ref, "")
|
|
})
|
|
if err != nil {
|
|
if apiErr, ok := errors.AsType[*transport.APIError](err); ok && apiErr.Status == http.StatusNotFound {
|
|
return "", "", nil
|
|
}
|
|
return "", "", err
|
|
}
|
|
rank := len(readmeNames)
|
|
for _, entry := range entries {
|
|
if i := slices.Index(readmeNames, strings.ToLower(entry.Name)); entry.Type == "file" && i >= 0 && i < rank {
|
|
name, rank = entry.Name, i
|
|
}
|
|
}
|
|
if name == "" {
|
|
return "", "", nil
|
|
}
|
|
data, err := repoCall(ctx, s.Client, repo, func(ctx context.Context, c *gitea.Client, owner, repo string) ([]byte, *gitea.Response, error) {
|
|
return c.Repositories.GetFile(ctx, owner, repo, ref, name)
|
|
})
|
|
return name, string(data), err
|
|
}
|
|
|
|
// RepositoryPermissions holds server-side access flags used only for local decisions.
|
|
// It is intentionally omitted from the stable Repository JSON schema.
|
|
type RepositoryPermissions struct {
|
|
Admin bool `json:"admin"`
|
|
Push bool `json:"push"`
|
|
Pull bool `json:"pull"`
|
|
}
|
|
|
|
// viewPermissions reads repository access flags without exposing them on Repository results.
|
|
func (s Repositories) viewPermissions(ctx context.Context, repo string) (RepositoryPermissions, error) {
|
|
item, err := s.get(ctx, repo)
|
|
if err != nil || item.Permissions == nil {
|
|
return RepositoryPermissions{}, err
|
|
}
|
|
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) {
|
|
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)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return convertAll(users, login), nil
|
|
}
|
|
|
|
// Create creates a repository for the account or the named organization.
|
|
func (s Repositories) Create(ctx context.Context, org string, opts RepositoryOptions) (Repository, error) {
|
|
options := gitea.CreateRepoOption{
|
|
Name: opts.Name, Description: opts.Description, Private: opts.Private, IssueLabels: opts.Labels,
|
|
AutoInit: opts.AutoInit, Template: opts.Template, Gitignores: opts.Gitignores, License: opts.License,
|
|
Readme: opts.Readme, DefaultBranch: opts.DefaultBranch, TrustModel: gitea.TrustModel(opts.TrustModel),
|
|
ObjectFormatName: opts.ObjectFormat,
|
|
}
|
|
item, _, err := call(ctx, s.Client, func(ctx context.Context, c *gitea.Client) (*gitea.Repository, *gitea.Response, error) {
|
|
if org != "" {
|
|
return c.Repositories.CreateOrgRepo(ctx, org, options)
|
|
}
|
|
return c.Repositories.CreateRepo(ctx, options)
|
|
})
|
|
if err != nil {
|
|
return Repository{}, err
|
|
}
|
|
return repositoryFrom(item), nil
|
|
}
|
|
|
|
// Edit updates only explicitly supplied resource fields.
|
|
func (s Repositories) Edit(ctx context.Context, repo string, opts RepositoryEdit) (Repository, error) {
|
|
return repoConvert(ctx, s.Client, repo, repositoryFrom, func(ctx context.Context, c *gitea.Client, owner, name string) (*gitea.Repository, *gitea.Response, error) {
|
|
return c.Repositories.EditRepo(ctx, owner, name, gitea.EditRepoOption{
|
|
HasActions: opts.ActionsEnabled, Name: opts.Name, Website: opts.Website, DefaultBranch: opts.DefaultBranch,
|
|
Description: opts.Description, Private: opts.Private, Archived: opts.Archived, Template: opts.Template,
|
|
})
|
|
})
|
|
}
|
|
|
|
// Delete removes the selected repository.
|
|
func (s Repositories) Delete(ctx context.Context, repo string) error {
|
|
return repoDo(ctx, s.Client, repo, func(ctx context.Context, c *gitea.Client, owner, name string) (*gitea.Response, error) {
|
|
return c.Repositories.DeleteRepo(ctx, owner, name)
|
|
})
|
|
}
|
|
|
|
// Fork creates a fork for the current user or selected organization.
|
|
func (s Repositories) Fork(ctx context.Context, repo, org string) (Repository, error) {
|
|
var options gitea.CreateForkOption
|
|
if org != "" {
|
|
options.Organization = &org
|
|
}
|
|
return repoConvert(ctx, s.Client, repo, repositoryFrom, func(ctx context.Context, c *gitea.Client, owner, name string) (*gitea.Repository, *gitea.Response, error) {
|
|
return c.Repositories.CreateFork(ctx, owner, name, options)
|
|
})
|
|
}
|