Files
Tea-Cli/modules/context/context.go
T
Zach Winter 73b6bf3e23 fix(context): clarify the fallback login prompt wording (#1061)
The prompt shown when no login matches the current repository reads:

```
NOTE: no gitea login detected, whether falling back to login 'X'?
```

Two problems, both raised by @magistra-aria in #817:

- **"whether"** is a conjunction that needs two stated alternatives, so it doesn't parse in front of a yes/no confirm.
- **"no gitea login detected"** is misleading. The condition is that no *configured login matched this repository's remote* — not that a Gitea instance is missing. Read literally it suggests the CLI expects gitea.com specifically, which is how at least one user (me) first misread it.

Reworded to say what actually happened, for both the interactive prompt and its non-interactive counterpart:

```
NOTE: no login matched this repository. Fall back to login 'X'?
```

Strings only, no logic change.

Refs #817

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Zach Winter <contact@zachwinter.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/1061
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Zach Winter <222839+zachwinter@noreply.gitea.com>
2026-07-26 01:38:10 +00:00

191 lines
6.6 KiB
Go

// 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
}