mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
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>
This commit is contained in:
9 files changed
+742
-308
No files matched your search
+152
-67
@@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/tea/internal/clierr"
|
||||
@@ -16,12 +17,9 @@ import (
|
||||
"gitea.dev/tea/internal/settings"
|
||||
"gitea.dev/tea/internal/terminal"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func repositoryRecord(item services.Repository) terminal.Record {
|
||||
return terminal.Record{Heading: []terminal.Cell{{Text: item.FullName, Bold: true}}, Lines: []string{item.URL}}
|
||||
}
|
||||
|
||||
func cloneURLForProtocol(cloneURL, sshURL, protocol string) (remote string, ssh bool, err error) {
|
||||
remote = cloneURL
|
||||
if protocol == "ssh" {
|
||||
@@ -73,72 +71,159 @@ func mirrorSourceURL(item services.Repository) string {
|
||||
}
|
||||
|
||||
func (a *application) addRepositoryMutations(group *cobra.Command) {
|
||||
group.AddCommand(a.repoCreateCommand(), a.repoMigrateCommand(), a.repoForkCommand(), a.repoCloneCommand())
|
||||
var description, repoName, website, defaultBranch string
|
||||
var private, template, repoArchived, actionsEnabled bool
|
||||
edit := &cobra.Command{Use: "edit [repository]", Short: "Edit repository settings", Args: usageArgs(cobra.MaximumNArgs(1)), RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := a.repositoryArgument(args); err != nil {
|
||||
return err
|
||||
}
|
||||
options := services.RepositoryEdit{}
|
||||
if cmd.Flags().Changed("name") {
|
||||
if strings.TrimSpace(repoName) == "" {
|
||||
return clierr.Wrap(clierr.Usage, errors.New("repository name cannot be empty"))
|
||||
}
|
||||
options.Name = &repoName
|
||||
}
|
||||
if cmd.Flags().Changed("website") {
|
||||
options.Website = &website
|
||||
}
|
||||
if cmd.Flags().Changed("default-branch") {
|
||||
if strings.TrimSpace(defaultBranch) == "" {
|
||||
return clierr.Wrap(clierr.Usage, errors.New("default branch cannot be empty"))
|
||||
}
|
||||
options.DefaultBranch = &defaultBranch
|
||||
}
|
||||
if cmd.Flags().Changed("archived") {
|
||||
options.Archived = &repoArchived
|
||||
}
|
||||
group.AddCommand(a.repoCreateCommand(), a.repoMigrateCommand(), a.repoForkCommand(), a.repoCloneCommand(), a.repoEditCommand())
|
||||
group.AddCommand(a.repoArchiveCommand(true), a.repoArchiveCommand(false), a.repoRenameCommand(), a.repoDeleteCommand())
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("description") {
|
||||
options.Description = &description
|
||||
}
|
||||
if cmd.Flags().Changed("private") {
|
||||
options.Private = &private
|
||||
}
|
||||
if cmd.Flags().Changed("as-template") {
|
||||
options.Template = &template
|
||||
}
|
||||
if cmd.Flags().Changed("actions") {
|
||||
options.ActionsEnabled = &actionsEnabled
|
||||
}
|
||||
if options == (services.RepositoryEdit{}) {
|
||||
return clierr.Wrap(clierr.Usage, errors.New("supply an edit flag; see tea repo edit --help"))
|
||||
}
|
||||
t, err := a.resolve(cmd.Context(), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if repoArchived {
|
||||
if err := a.confirm(cmd, "Archive repository "+t.Repo); err != nil {
|
||||
// repositorySwitches are the on/off settings of repo edit: each flag sets one field, and =false
|
||||
// turns the setting off.
|
||||
var repositorySwitches = []struct {
|
||||
flag, usage string
|
||||
field func(*services.RepositoryEdit) **bool
|
||||
}{
|
||||
{"private", "Make the repository private (=false makes it public)", func(e *services.RepositoryEdit) **bool { return &e.Private }},
|
||||
{"as-template", "Make the repository a template", func(e *services.RepositoryEdit) **bool { return &e.Template }},
|
||||
{"enable-issues", "Enable issues", func(e *services.RepositoryEdit) **bool { return &e.Issues }},
|
||||
{"enable-wiki", "Enable the wiki", func(e *services.RepositoryEdit) **bool { return &e.Wiki }},
|
||||
{"enable-pull-requests", "Enable pull requests", func(e *services.RepositoryEdit) **bool { return &e.PullRequests }},
|
||||
{"enable-projects", "Enable projects", func(e *services.RepositoryEdit) **bool { return &e.Projects }},
|
||||
{"actions", "Enable Actions", func(e *services.RepositoryEdit) **bool { return &e.ActionsEnabled }},
|
||||
{"allow-merge-commit", "Allow merging pull requests with a merge commit", func(e *services.RepositoryEdit) **bool { return &e.MergeCommits }},
|
||||
{"allow-squash", "Allow squash merging pull requests", func(e *services.RepositoryEdit) **bool { return &e.Squash }},
|
||||
{"allow-rebase", "Allow rebase merging pull requests", func(e *services.RepositoryEdit) **bool { return &e.Rebase }},
|
||||
{"delete-branch-on-merge", "Delete head branches after merging pull requests", func(e *services.RepositoryEdit) **bool { return &e.DeleteBranch }},
|
||||
{"archived", "Archive the repository (=false unarchives it)", func(e *services.RepositoryEdit) **bool { return &e.Archived }},
|
||||
}
|
||||
|
||||
func (a *application) repoEditCommand() *cobra.Command {
|
||||
var name, website, defaultBranch, description, visibility, mergeStyle string
|
||||
var addTopics, removeTopics []string
|
||||
switches := make([]bool, len(repositorySwitches))
|
||||
command := &cobra.Command{
|
||||
Use: "edit [<repository>]",
|
||||
Short: "Edit repository settings",
|
||||
Long: `Edit the settings of a repository. Only the flags you pass change; on/off settings such as
|
||||
--enable-wiki take =false to turn them off.`,
|
||||
Example: `# Change the description and the website
|
||||
$ tea repo edit --description "Command line tool" --website https://example.com
|
||||
|
||||
# Make a repository private and disable its wiki
|
||||
$ tea repo edit alice/project --visibility private --enable-wiki=false
|
||||
|
||||
# Squash pull requests by default and tag the repository
|
||||
$ tea repo edit --default-merge-style squash --add-topic cli --add-topic go`,
|
||||
Annotations: map[string]string{"tea.arguments": repositoryArguments},
|
||||
Args: usageArgs(cobra.MaximumNArgs(1)),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := a.repositoryArgument(args); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
item, err := (services.Repositories{Client: t.client}).Edit(cmd.Context(), t.Repo, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeRecord(a, cmd, item, repositoryRecord)
|
||||
}}
|
||||
edit.Flags().StringVar(&repoName, "name", "", "Rename the repository")
|
||||
edit.Flags().StringVar(&website, "website", "", "Repository website; empty clears")
|
||||
edit.Flags().StringVar(&defaultBranch, "default-branch", "", "Default branch name")
|
||||
edit.Flags().BoolVar(&repoArchived, "archived", false, "Archive repository; =false restores it")
|
||||
edit.Flags().StringVar(&description, "description", "", "Repository description")
|
||||
edit.Flags().BoolVar(&private, "private", false, "Make repository private (use --private=false to make public)")
|
||||
edit.Flags().BoolVar(&template, "as-template", false, "Make repository a template (use =false to disable)")
|
||||
edit.Flags().BoolVar(&actionsEnabled, "actions", false, "Enable Actions (use --actions=false to disable)")
|
||||
group.AddCommand(edit, a.repoArchiveCommand(true), a.repoArchiveCommand(false), a.repoRenameCommand(), a.repoDeleteCommand())
|
||||
changed := cmd.Flags().Changed
|
||||
options := services.RepositoryEdit{}
|
||||
for i, toggle := range repositorySwitches {
|
||||
if changed(toggle.flag) {
|
||||
*toggle.field(&options) = &switches[i]
|
||||
}
|
||||
}
|
||||
for _, field := range []struct {
|
||||
flag string
|
||||
value *string
|
||||
into **string
|
||||
}{{"name", &name, &options.Name}, {"default-branch", &defaultBranch, &options.DefaultBranch}, {"default-merge-style", &mergeStyle, &options.MergeStyle}} {
|
||||
if changed(field.flag) {
|
||||
if strings.TrimSpace(*field.value) == "" {
|
||||
return clierr.Wrap(clierr.Usage, fmt.Errorf("--%s cannot be empty", field.flag))
|
||||
}
|
||||
*field.into = field.value
|
||||
}
|
||||
}
|
||||
if changed("website") {
|
||||
options.Website = &website
|
||||
}
|
||||
if changed("description") {
|
||||
options.Description = &description
|
||||
}
|
||||
if changed("visibility") {
|
||||
if changed("private") {
|
||||
return clierr.Wrap(clierr.Usage, errors.New("--visibility and --private cannot be combined"))
|
||||
}
|
||||
private := visibility == "private"
|
||||
options.Private = &private
|
||||
}
|
||||
if err := requireNames("topic", "", addTopics, removeTopics); err != nil {
|
||||
return err
|
||||
}
|
||||
if options == (services.RepositoryEdit{}) && len(addTopics)+len(removeTopics) == 0 {
|
||||
return clierr.Wrap(clierr.Usage, errors.New("supply a setting to change; see tea repo edit --help"))
|
||||
}
|
||||
t, err := a.resolve(cmd.Context(), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service := services.Repositories{Client: t.client}
|
||||
if options.DefaultBranch != nil {
|
||||
if _, err := (services.Branches{Client: t.client}).View(cmd.Context(), t.Repo, defaultBranch); err != nil {
|
||||
return services.Classify(err, "branch", strconv.Quote(defaultBranch), t.Repo)
|
||||
}
|
||||
}
|
||||
if options.Archived != nil && *options.Archived {
|
||||
if err := a.confirmAction(cmd, terminal.Confirmation{Prompt: "Archive " + t.Repo + "? It becomes read-only.", NonTTY: "archive " + t.Repo}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
repo := t.Repo
|
||||
if options != (services.RepositoryEdit{}) {
|
||||
item, err := service.Edit(cmd.Context(), t.Repo, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repo = item.FullName
|
||||
}
|
||||
for _, topic := range addTopics {
|
||||
if err := service.AddTopic(cmd.Context(), repo, topic); err != nil {
|
||||
return fmt.Errorf("add topic %q: %w", topic, err)
|
||||
}
|
||||
}
|
||||
for _, topic := range removeTopics {
|
||||
if err := service.RemoveTopic(cmd.Context(), repo, topic); err != nil {
|
||||
return fmt.Errorf("remove topic %q: %w", topic, err)
|
||||
}
|
||||
}
|
||||
var edited []string
|
||||
local := cmd.LocalFlags()
|
||||
cmd.Flags().Visit(func(flag *pflag.Flag) {
|
||||
if local.Lookup(flag.Name) != nil {
|
||||
edited = append(edited, flag.Name)
|
||||
}
|
||||
})
|
||||
a.done(cmd, "Edited repository %s (%s)", repo, strings.Join(edited, ", "))
|
||||
if a.options.json == "" {
|
||||
return nil
|
||||
}
|
||||
item, err := service.View(cmd.Context(), repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.writeJSON(cmd, item)
|
||||
},
|
||||
}
|
||||
flags := command.Flags()
|
||||
flags.StringVar(&name, "name", "", "Rename the repository")
|
||||
_ = flags.MarkHidden("name")
|
||||
flags.StringVarP(&description, "description", "d", "", "Repository description")
|
||||
flags.StringVar(&website, "website", "", "Repository website (empty clears it)")
|
||||
flags.StringVar(&defaultBranch, "default-branch", "", "Default branch; it must exist")
|
||||
enumFlag(command, &visibility, "visibility", "", "", "Repository visibility", "public", "private")
|
||||
enumFlag(command, &mergeStyle, "default-merge-style", "", "", "Default merge style for pull requests", "merge", "rebase", "rebase-merge", "squash", "fast-forward-only")
|
||||
flags.StringSliceVar(&addTopics, "add-topic", nil, "Add topics (repeatable or comma-separated)")
|
||||
flags.StringSliceVar(&removeTopics, "remove-topic", nil, "Remove topics (repeatable or comma-separated)")
|
||||
for i, toggle := range repositorySwitches {
|
||||
flags.BoolVar(&switches[i], toggle.flag, false, toggle.usage)
|
||||
}
|
||||
_ = flags.MarkHidden("archived")
|
||||
_ = command.RegisterFlagCompletionFunc("default-branch", a.completeBranches())
|
||||
command.ValidArgsFunction = a.completeRepos()
|
||||
return command
|
||||
}
|
||||
|
||||
func (a *application) repoCloneCommand() *cobra.Command {
|
||||
|
||||
Reference in new issue
Block a user