mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 19:11:25 +01:00
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:
1 parent
12e415a7a7
commit
f02245449b
456 files changed
+18892
-34015
No files matched your search
@@ -1,190 +0,0 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/tea/modules/config"
|
||||
"gitea.dev/tea/modules/git"
|
||||
"gitea.dev/tea/modules/theme"
|
||||
"gitea.dev/tea/modules/utils"
|
||||
|
||||
"charm.land/huh/v2"
|
||||
"github.com/urfave/cli/v3"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
var errNotAGiteaRepo = errors.New("no Gitea login found; you might want to specify --repo (and --login) to work outside of a repository")
|
||||
|
||||
// ErrCommandCanceled is returned when the user explicitly cancels an interactive prompt.
|
||||
var ErrCommandCanceled = errors.New("command canceled")
|
||||
|
||||
// TeaContext contains all context derived during command initialization and wraps cli.Context
|
||||
type TeaContext struct {
|
||||
*cli.Command
|
||||
Login *config.Login // config data & client for selected login
|
||||
RepoSlug string // <owner>/<repo>, optional
|
||||
Owner string // repo owner as derived from context or provided in flag, optional
|
||||
Repo string // repo name as derived from context or provided in flag, optional
|
||||
Org string // organization name, optional
|
||||
IsGlobal bool // true if operating on global level
|
||||
Output string // value of output flag
|
||||
LocalRepo *git.TeaRepo // is set if flags specified a local repo via --repo, or if $PWD is a git repo
|
||||
}
|
||||
|
||||
// InitOptions controls which optional sources InitCommand may inspect.
|
||||
type InitOptions struct {
|
||||
// SkipLocalRepo avoids probing the current directory for a git repository.
|
||||
SkipLocalRepo bool
|
||||
}
|
||||
|
||||
// GetRemoteRepoHTMLURL returns the web-ui url of the remote repo,
|
||||
// after ensuring a remote repo is present in the context.
|
||||
func (ctx *TeaContext) GetRemoteRepoHTMLURL() (string, error) {
|
||||
if err := ctx.Ensure(CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimRight(ctx.Login.URL, "/") + "/" + ctx.Owner + "/" + ctx.Repo, nil
|
||||
}
|
||||
|
||||
// IsInteractiveMode returns true if the command is running in interactive mode
|
||||
// (no flags provided and stdout is a terminal)
|
||||
func (ctx *TeaContext) IsInteractiveMode() bool {
|
||||
return ctx.Command.NumFlags() == 0
|
||||
}
|
||||
|
||||
func shouldPromptFallbackLogin(login *config.Login, canPrompt bool) bool {
|
||||
return login != nil && !login.Default && canPrompt
|
||||
}
|
||||
|
||||
// InitCommand resolves the application context, and returns the active login, and if
|
||||
// available the repo slug. It does this by reading the config file for logins, parsing
|
||||
// the remotes of the .git repo specified in repoFlag or $PWD, and using overrides from
|
||||
// command flags. If a local git repo can't be found, repo slug values are unset.
|
||||
func InitCommand(cmd *cli.Command) (*TeaContext, error) {
|
||||
return InitCommandWithOptions(cmd, InitOptions{})
|
||||
}
|
||||
|
||||
// InitCommandWithOptions resolves the application context like InitCommand, with
|
||||
// optional controls for commands that do not need repository context.
|
||||
func InitCommandWithOptions(cmd *cli.Command, opts InitOptions) (*TeaContext, error) {
|
||||
// these flags are used as overrides to the context detection via local git repo
|
||||
repoFlag := cmd.String("repo")
|
||||
loginFlag := cmd.String("login")
|
||||
remoteFlag := cmd.String("remote")
|
||||
orgFlag := cmd.String("org")
|
||||
globalFlag := cmd.Bool("global")
|
||||
|
||||
var (
|
||||
c TeaContext
|
||||
err error
|
||||
repoPath string // empty means PWD
|
||||
repoFlagPathExists bool
|
||||
)
|
||||
|
||||
// check if repoFlag can be interpreted as path to local repo.
|
||||
if len(repoFlag) != 0 && !opts.SkipLocalRepo {
|
||||
if repoFlagPathExists, err = utils.DirExists(repoFlag); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if repoFlagPathExists {
|
||||
repoPath = repoFlag
|
||||
} else {
|
||||
c.RepoSlug = repoFlag
|
||||
}
|
||||
} else if len(repoFlag) != 0 {
|
||||
c.RepoSlug = repoFlag
|
||||
}
|
||||
|
||||
if len(remoteFlag) == 0 {
|
||||
remoteFlag = config.GetPreferences().FlagDefaults.Remote
|
||||
}
|
||||
|
||||
// Create env login before repo context detection so it participates in remote URL matching
|
||||
var extraLogins []config.Login
|
||||
envLogin := GetLoginByEnvVar()
|
||||
if envLogin != nil {
|
||||
if _, err := utils.ValidateAuthenticationMethod(envLogin.URL, envLogin.Token, "", "", false, "", ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extraLogins = append(extraLogins, *envLogin)
|
||||
}
|
||||
|
||||
if !opts.SkipLocalRepo {
|
||||
// try to read local git repo & extract context: if repoFlag specifies a valid path, read repo in that dir,
|
||||
// otherwise attempt PWD. if no repo is found, continue with default login
|
||||
if repoPath == "" {
|
||||
if repoPath, err = os.Getwd(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var localSlug string
|
||||
if c.LocalRepo, c.Login, localSlug, err = contextFromLocalRepo(repoPath, remoteFlag, extraLogins); err != nil {
|
||||
if err == errNotAGiteaRepo || err == git.ErrRepositoryNotExists {
|
||||
// we can deal with that, commands needing the optional values use ctx.Ensure()
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if c.RepoSlug == "" && localSlug != "" {
|
||||
c.RepoSlug = localSlug
|
||||
}
|
||||
}
|
||||
|
||||
// If env vars are set, always use the env login (but repo slug was already
|
||||
// resolved by contextFromLocalRepo with the env login in the match list)
|
||||
if envLogin != nil {
|
||||
c.Login = envLogin
|
||||
}
|
||||
|
||||
// override login from flag, or use default login if repo based detection failed
|
||||
if len(loginFlag) != 0 {
|
||||
if c.Login, err = config.GetLoginByName(loginFlag); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.Login == nil {
|
||||
return nil, fmt.Errorf("login name '%s' does not exist", loginFlag)
|
||||
}
|
||||
} else if c.Login == nil {
|
||||
if c.Login, err = config.GetDefaultLogin(); err != nil {
|
||||
if err.Error() == "No available login" {
|
||||
return nil, fmt.Errorf(`no gitea login configured. To start using tea, first run
|
||||
tea login add
|
||||
and then run your command again`)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Only prompt for confirmation if the fallback login is not explicitly set as default
|
||||
canPrompt := term.IsTerminal(int(os.Stdin.Fd())) && term.IsTerminal(int(os.Stdout.Fd()))
|
||||
if shouldPromptFallbackLogin(c.Login, canPrompt) {
|
||||
fallback := false
|
||||
if err := huh.NewConfirm().
|
||||
Title(fmt.Sprintf("NOTE: no login matched this repository. Fall back to login '%s'?", c.Login.Name)).
|
||||
Value(&fallback).
|
||||
WithTheme(theme.GetTheme()).
|
||||
Run(); err != nil {
|
||||
return nil, fmt.Errorf("get confirm failed: %w", err)
|
||||
}
|
||||
if !fallback {
|
||||
return nil, ErrCommandCanceled
|
||||
}
|
||||
} else if !c.Login.Default {
|
||||
fmt.Fprintf(os.Stderr, "NOTE: no login matched this repository, falling back to login '%s' in non-interactive mode.\n", c.Login.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// parse reposlug (owner falling back to login owner if reposlug contains only repo name)
|
||||
c.Owner, c.Repo = utils.GetOwnerAndRepo(c.RepoSlug, c.Login.User)
|
||||
c.Org = orgFlag
|
||||
c.IsGlobal = globalFlag
|
||||
c.Command = cmd
|
||||
c.Output = cmd.String("output")
|
||||
return &c, nil
|
||||
}
|
||||
Reference in new issue
Block a user