feat!: rebuild tea on Cobra with explicit noun/verb commands (#1126)

- **switch tea to the Cobra command tree:** `internal/app` wires the packages from parts 1–4 into explicit noun/verb commands with `--repo`, `--json` / `--jq`, `--sudo` and `--debug`; `main.go` points at it. Build, release and docs tooling (`tools/cli-docs`, Makefile, goreleaser, snap, CI) and README / CONTRIBUTING are updated.
- **remove the urfave/cli command tree:** deletes `cmd/`, `modules/`, the old integration tests, contrib completion scripts and the generated `docs/CLI.md`, and drops their dependencies. This commit only deletes.

## Breaking changes

This is a breaking release. It requires Gitea 28 or newer; configuration moves to separate `config.yml` and `hosts.yml` files and requires fresh authentication, and command names, aliases, flags and output formats change. `docs/migration-1.0.md` maps every old command, flag and environment variable to its replacement.

## Issues

fixes #1118
fixes #1116
fixes #1115
fixes #1094
fixes #1091
fixes #1073
fixes #1052
fixes #1032
fixes #1023
fixes #966
fixes #908
fixes #833
fixes #831
fixes #817
fixes #804
fixes #779
fixes #770
fixes #737
fixes #697
fixes #726
fixes #677
fixes #672
fixes #633
fixes #632
fixes #560
fixes #556
fixes #523
fixes #516
fixes #510
fixes #504
fixes #500
fixes #482
fixes #406
fixes #403
fixes #373
fixes #351
fixes #344
fixes #326
fixes #325
fixes #323
fixes #246
fixes #240
fixes #232
fixes #197
fixes #172
fixes #141
fixes #69
fixes #1110
fixes #569
fixes #480
fixes #666
fixes #670
fixes #457
fixes #643
fixes #498
fixes #490
fixes #1069
fixes #834
fixes #602
fixes #328
fixes #417
fixes #571
fixes #1029
fixes #489
fixes #1050
fixes #162
fixes #430
fixes #1087
fixes #683

Reviewed-on: https://gitea.com/gitea/tea/pulls/1126
Reviewed-by: TheFox0x7 <95654+thefox0x7@noreply.gitea.com>
This commit is contained in:
bircni committed 2026-10-11 17:23:06 +00:00
1 parent 12e415a7a7
commit f02245449b
456 files changed
+18892 -34015

No files matched your search

-218
View File
@@ -1,218 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package interact
import (
"context"
"fmt"
"strings"
gitea "gitea.dev/sdk"
"gitea.dev/tea/modules/config"
"gitea.dev/tea/modules/print"
"gitea.dev/tea/modules/task"
"gitea.dev/tea/modules/theme"
"charm.land/huh/v2"
)
// IsQuitting checks if the user has aborted the interactive prompt
func IsQuitting(err error) bool {
return err == huh.ErrUserAborted
}
// CreateIssue interactively creates an issue
func CreateIssue(ctx context.Context, login *config.Login, owner, repo string) error {
owner, repo, err := promptRepoSlug(owner, repo)
if err != nil {
return err
}
printTitleAndContent("Target repo:", owner+"/"+repo)
var opts gitea.CreateIssueOption
if err := promptIssueProperties(ctx, login, owner, repo, &opts); err != nil {
return err
}
issue, err := task.CreateIssue(ctx, login, owner, repo, opts)
if err != nil {
return err
}
print.IssueDetails(issue, nil)
fmt.Println(issue.HTMLURL)
return nil
}
func promptIssueProperties(ctx context.Context, login *config.Login, owner, repo string, o *gitea.CreateIssueOption) error {
var milestoneName string
var err error
selectableChan := make(chan issueSelectables, 1)
go fetchIssueSelectables(ctx, login, owner, repo, selectableChan)
// title
if err := huh.NewInput().
Title("Issue title:").
Value(&o.Title).
Validate(huh.ValidateNotEmpty()).
WithTheme(theme.GetTheme()).
Run(); err != nil {
return err
}
printTitleAndContent("Issue title:", o.Title)
// description
if err := huh.NewForm(
huh.NewGroup(
huh.NewText().
Title("Issue description(markdown):").
ExternalEditor(config.GetPreferences().Editor).
EditorExtension("md").
Value(&o.Body),
),
).WithTheme(theme.GetTheme()).
Run(); err != nil {
return err
}
printTitleAndContent("Issue description(markdown):", o.Body)
// wait until selectables are fetched
selectables := <-selectableChan
if selectables.Err != nil {
return selectables.Err
}
// skip remaining props if we don't have permission to set them
if !selectables.Repo.Permissions.Push {
return nil
}
// assignees
if o.Assignees, err = promptMultiSelect("Assignees:", selectables.Assignees, "[other]"); err != nil {
return err
}
printTitleAndContent("Assignees:", strings.Join(o.Assignees, "\n"))
// milestone
if len(selectables.MilestoneList) != 0 {
if milestoneName, err = promptSelect("Milestone:", selectables.MilestoneList, "", "[none]", ""); err != nil {
return err
}
o.Milestone = selectables.MilestoneMap[milestoneName]
printTitleAndContent("Milestone:", milestoneName)
}
// labels
if len(selectables.LabelList) != 0 {
options := make([]huh.Option[int64], 0, len(selectables.LabelList))
labelsMap := make(map[int64]string, len(selectables.LabelList))
for _, l := range selectables.LabelList {
options = append(options, huh.Option[int64]{Key: l, Value: selectables.LabelMap[l]})
labelsMap[selectables.LabelMap[l]] = l
}
if err := huh.NewMultiSelect[int64]().
Title("Labels:").
Options(options...).
Value(&o.Labels).
Run(); err != nil {
return err
}
var labels []string
for _, labelID := range o.Labels {
labels = append(labels, labelsMap[labelID])
}
printTitleAndContent("Labels:", strings.Join(labels, "\n"))
}
// deadline
if o.Deadline, err = promptDatetime("Due date:"); err != nil {
return err
}
deadlineStr := "No due date"
if o.Deadline != nil && !o.Deadline.IsZero() {
deadlineStr = o.Deadline.Format("2006-01-02")
}
printTitleAndContent("Due date:", deadlineStr)
return nil
}
type issueSelectables struct {
Repo *gitea.Repository
Assignees []string
MilestoneList []string
MilestoneMap map[string]int64
LabelList []string
LabelMap map[string]int64
Err error
}
func fetchIssueSelectables(ctx context.Context, login *config.Login, owner, repo string, done chan issueSelectables) {
// TODO PERF make these calls concurrent
r := issueSelectables{}
c := login.Client()
r.Repo, _, r.Err = c.Repositories.GetRepo(ctx, owner, repo)
if r.Err != nil {
done <- r
return
}
// we can set the following properties only if we have write access to the repo
// so we fastpath this if not.
if !r.Repo.Permissions.Push {
done <- r
return
}
assignees, _, err := c.Repositories.GetAssignees(ctx, owner, repo)
if err != nil {
r.Err = err
done <- r
return
}
r.Assignees = make([]string, len(assignees))
for i, u := range assignees {
r.Assignees[i] = u.UserName
}
milestones, _, err := c.Repositories.ListMilestones(ctx, owner, repo, gitea.ListMilestoneOption{})
if err != nil {
r.Err = err
done <- r
return
}
r.MilestoneMap = make(map[string]int64)
r.MilestoneList = make([]string, len(milestones))
for i, m := range milestones {
r.MilestoneMap[m.Title] = m.ID
r.MilestoneList[i] = m.Title
}
r.LabelMap = make(map[string]int64)
r.LabelList = make([]string, 0)
for page := 1; ; {
labels, resp, err := c.Repositories.ListRepoLabels(ctx, owner, repo, gitea.ListLabelsOptions{
ListOptions: gitea.ListOptions{Page: page, PageSize: 50},
})
if err != nil {
r.Err = err
done <- r
return
}
for _, l := range labels {
r.LabelMap[l.Name] = l.ID
r.LabelList = append(r.LabelList, l.Name)
}
if resp == nil || resp.NextPage == 0 {
break
}
page = resp.NextPage
}
done <- r
}