Files
gitea-tea/internal/services/repository_setup.go
T
bircni 682c9f66a2 feat(repo): validate create templates, publish local projects, verify edits
repo create --license NOPE returned Gitea's HTTP 500 internals. License,
.gitignore and label templates are now checked against the server's
lists, case-insensitively, and complete with Tab. --source DIR adds the
new repository as a remote of a local Git repository and --push pushes
its current branch; --public is the explicit opposite of --private, and
-d/-g/-l and the new alias follow gh. --trust-model now offers the names
Gitea accepts. A prompted name is confirmed with the visibility, owner
and host, and creation reports "✓ Created repository ..." and the URL.

repo edit --default-branch nope exited 0 without changing anything; the
branch must now exist. Edit gains --visibility, --enable-issues/-wiki/
-pull-requests/-projects, --allow-merge-commit/-squash/-rebase,
--delete-branch-on-merge, --default-merge-style and --add-topic/
--remove-topic, and reports which settings it changed.

repo migrate names the repository after the source URL, validates
--service before prompting, and reports the import with the URL.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:51:49 +02:00

122 lines
5.0 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package services
import (
"context"
"errors"
"net/http"
"time"
gitea "gitea.dev/sdk"
"gitea.dev/tea/internal/transport"
)
// RepositoryTemplateOptions controls copying from a template repository.
type RepositoryTemplateOptions struct {
Name string `json:"name"`
Owner string `json:"owner"`
Description string `json:"description"`
DefaultBranch string `json:"default_branch,omitempty"`
Private bool `json:"private"`
Content bool `json:"git_content"`
GitHooks bool `json:"git_hooks"`
Avatar bool `json:"avatar"`
Labels bool `json:"labels"`
Topics bool `json:"topics"`
Webhooks bool `json:"webhooks"`
}
// Generate creates a repository from a template on the selected instance.
func (s Repositories) Generate(ctx context.Context, source string, opts RepositoryTemplateOptions) (Repository, error) {
if opts.Owner == "" {
account, err := CurrentAccount(ctx, s.Client)
if err != nil {
return Repository{}, err
}
opts.Owner = account
}
// TODO(sdk): CreateRepoFromTemplateOption has no default_branch.
var response gitea.Repository
if err := sendJSON(ctx, s.Client, http.MethodPost, "repos/"+source+"/generate", opts, &response); err != nil {
return Repository{}, err
}
return repositoryFrom(&response), nil
}
// Licenses lists the license templates that repository creation accepts.
func (s Repositories) Licenses(ctx context.Context) ([]string, error) {
licenses, _, err := call(ctx, s.Client, func(ctx context.Context, c *gitea.Client) ([]*gitea.LicensesTemplateListEntry, *gitea.Response, error) {
return c.Templates.ListLicenses(ctx)
})
return convertAll(licenses, func(license *gitea.LicensesTemplateListEntry) string { return license.Key }), err
}
// Gitignores lists the .gitignore templates that repository creation accepts.
func (s Repositories) Gitignores(ctx context.Context) ([]string, error) {
names, _, err := call(ctx, s.Client, func(ctx context.Context, c *gitea.Client) ([]string, *gitea.Response, error) {
return c.Templates.ListGitignores(ctx)
})
return names, err
}
// LabelTemplates lists the label sets that repository creation accepts.
func (s Repositories) LabelTemplates(ctx context.Context) ([]string, error) {
names, _, err := call(ctx, s.Client, func(ctx context.Context, c *gitea.Client) ([]string, *gitea.Response, error) {
return c.Templates.ListLabels(ctx)
})
return names, err
}
// RepositoryMigrationOptions is explicit server-side import input. Credentials stay in request bodies.
type RepositoryMigrationOptions struct {
Name string `json:"repo_name"`
Owner string `json:"repo_owner,omitempty"`
CloneURL string `json:"clone_addr"`
Service string `json:"service"`
AuthUser string `json:"auth_username,omitempty"`
AuthPassword string `json:"auth_password,omitempty"`
AuthToken string `json:"auth_token,omitempty"`
Mirror bool `json:"mirror"`
Private bool `json:"private"`
Description string `json:"description,omitempty"`
Wiki bool `json:"wiki"`
Milestones bool `json:"milestones"`
Labels bool `json:"labels"`
Issues bool `json:"issues"`
PullRequests bool `json:"pull_requests"`
Releases bool `json:"releases"`
MirrorInterval string `json:"mirror_interval,omitempty"`
LFS bool `json:"lfs"`
LFSEndpoint string `json:"lfs_endpoint,omitempty"`
Timeout time.Duration `json:"-"`
}
// Migrate imports a remote repository through Gitea's migration API.
func (s Repositories) Migrate(ctx context.Context, opts RepositoryMigrationOptions) (Repository, error) {
if opts.Timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, opts.Timeout)
defer cancel()
s.Client = s.Client.WithTimeout(opts.Timeout)
}
item, _, err := call(ctx, s.Client, func(ctx context.Context, c *gitea.Client) (*gitea.Repository, *gitea.Response, error) {
return c.Repositories.MigrateRepo(ctx, gitea.MigrateRepoOption{
RepoName: opts.Name, RepoOwner: opts.Owner, CloneAddr: opts.CloneURL, Service: gitea.GitServiceType(opts.Service),
AuthUsername: opts.AuthUser, AuthPassword: opts.AuthPassword, AuthToken: opts.AuthToken, Mirror: opts.Mirror,
Private: opts.Private, Description: opts.Description, Wiki: opts.Wiki, Milestones: opts.Milestones,
Labels: opts.Labels, Issues: opts.Issues, PullRequests: opts.PullRequests, Releases: opts.Releases,
MirrorInterval: opts.MirrorInterval, LFS: opts.LFS, LFSEndpoint: opts.LFSEndpoint,
})
})
if err != nil {
// The server may echo source credentials in its error message.
if failure, ok := errors.AsType[*transport.APIError](err); ok && (opts.AuthPassword != "" || opts.AuthToken != "") {
failure.Message = ""
}
return Repository{}, err
}
return repositoryFrom(item), nil
}