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

-226
View File
@@ -1,226 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"encoding/base64"
"fmt"
"strings"
"unicode"
)
// TeaCreateBranch creates a new branch in the repo, tracking from another branch.
func (r TeaRepo) TeaCreateBranch(localBranchName, remoteBranchName, remoteName string) error {
return r.backend.CreateTrackingBranch(localBranchName, remoteBranchName, remoteName)
}
// TeaCheckout checks out the given branch in the worktree.
func (r TeaRepo) TeaCheckout(ref ReferenceName) error {
return r.backend.Checkout(ref)
}
// TeaDeleteLocalBranch removes the given branch locally.
func (r TeaRepo) TeaDeleteLocalBranch(branch *Branch) error {
return r.backend.DeleteLocalBranch(branch.Name)
}
// TeaDeleteRemoteBranch removes the given branch on the given remote via git protocol.
func (r TeaRepo) TeaDeleteRemoteBranch(remoteName, remoteBranch string, auth *AuthMethod) error {
return r.backend.DeleteRemoteBranch(remoteName, remoteBranch, auth)
}
// TeaFindBranchBySha returns a branch that is at the the given SHA and syncs to the
// given remote repo.
func (r TeaRepo) TeaFindBranchBySha(sha, repoURL string) (b *Branch, err error) {
remote, err := r.GetRemote(repoURL)
if err != nil {
return nil, err
}
if remote == nil {
return nil, fmt.Errorf("no remote found for '%s'", repoURL)
}
remoteName := remote.Config().Name
refs, err := r.backend.ListReferences("refs/heads", "refs/remotes/"+remoteName)
if err != nil {
return nil, err
}
var remoteRefName ReferenceName
var localRefName ReferenceName
for _, ref := range refs {
if ref.Name().IsRemote() && ref.Hash().String() == sha {
remoteRefName = ref.Name()
}
if ref.Name().IsBranch() && ref.Hash().String() == sha {
localRefName = ref.Name()
}
}
if remoteRefName == "" || localRefName == "" {
return nil, nil
}
b = &Branch{Remote: remoteName, Name: localRefName.Short(), Merge: localRefName}
return b, b.Validate()
}
// TeaFindBranchByName returns a branch that is at the the given local and
// remote names and syncs to the given remote repo. This method is less precise
// than TeaFindBranchBySha(), but may be desirable if local and remote branch
// have diverged.
func (r TeaRepo) TeaFindBranchByName(branchName, repoURL string) (b *Branch, err error) {
remote, err := r.GetRemote(repoURL)
if err != nil {
return nil, err
}
if remote == nil {
return nil, fmt.Errorf("no remote found for '%s'", repoURL)
}
remoteName := remote.Config().Name
refs, err := r.backend.ListReferences("refs/heads", "refs/remotes/"+remoteName)
if err != nil {
return nil, err
}
var remoteRefName ReferenceName
var localRefName ReferenceName
remoteSearchingName := fmt.Sprintf("%s/%s", remoteName, branchName)
for _, ref := range refs {
if ref.Name().IsRemote() && ref.Name().Short() == remoteSearchingName {
remoteRefName = ref.Name()
}
if ref.Name().IsBranch() && ref.Name().Short() == branchName {
localRefName = ref.Name()
}
}
if remoteRefName == "" || localRefName == "" {
return nil, nil
}
b = &Branch{Remote: remoteName, Name: localRefName.Short(), Merge: localRefName}
return b, b.Validate()
}
// TeaFindBranchRemote gives the first remote that has a branch with the same name or sha,
// depending on what is passed in.
// This function is needed, as git does not always define branches in .git/config with remote entries.
// Priority order is: first match of sha and branch -> first match of branch -> first match of sha.
func (r TeaRepo) TeaFindBranchRemote(branchName, hash string) (*Remote, error) {
remotes, err := r.Remotes()
if err != nil {
return nil, err
}
switch {
case len(remotes) == 0:
return nil, nil
case len(remotes) == 1:
return remotes[0], nil
}
refs, err := r.backend.ListReferences("refs/remotes")
if err != nil {
return nil, err
}
remoteByName := make(map[string]*Remote, len(remotes))
for _, remote := range remotes {
remoteByName[remote.Config().Name] = remote
}
var shaMatch *Remote
var branchMatch *Remote
var fullMatch *Remote
for _, ref := range refs {
remoteName, remoteBranch, ok := splitRemoteRef(ref.Name())
if !ok {
continue
}
remote := remoteByName[remoteName]
if remote == nil {
continue
}
if branchMatch == nil && branchName != "" && branchName == remoteBranch {
branchMatch = remote
}
if shaMatch == nil && hash != "" && hash == ref.Hash().String() {
shaMatch = remote
}
if fullMatch == nil && branchName != "" && branchName == remoteBranch && hash != "" && hash == ref.Hash().String() {
fullMatch = remote
break
}
}
switch {
case fullMatch != nil:
return fullMatch, nil
case branchMatch != nil:
return branchMatch, nil
case shaMatch != nil:
return shaMatch, nil
default:
return nil, nil
}
}
// TeaGetCurrentBranchNameAndSHA return the name and sha of the branch witch is currently active.
func (r TeaRepo) TeaGetCurrentBranchNameAndSHA() (string, string, error) {
localHead, err := r.Head()
if err != nil {
return "", "", err
}
if !localHead.Name().IsBranch() {
return "", "", fmt.Errorf("active ref is no branch")
}
return localHead.Name().Short(), localHead.Hash().String(), nil
}
// PushToCreatAgitFlowPR pushes the given head to the refs/for/<base>/<topic> ref on the remote to create an agit flow PR.
func (r TeaRepo) PushToCreatAgitFlowPR(remoteName, head, base, topic, title, description string, auth *AuthMethod) error {
if !strings.HasPrefix(head, "refs/") {
head = "refs/heads/" + head
}
pushOptions := make(map[string]string)
if len(title) > 0 {
pushOptions["title"] = b64Encode(title)
}
if len(description) > 0 {
pushOptions["description"] = b64Encode(description)
}
return r.backend.PushToAgitFlowPR(remoteName, head, base, topic, pushOptions, auth)
}
func splitRemoteRef(ref ReferenceName) (remoteName, branchName string, ok bool) {
if !ref.IsRemote() {
return "", "", false
}
parts := strings.SplitN(ref.Short(), "/", 2)
if len(parts) != 2 {
return "", "", false
}
return parts[0], parts[1], true
}
// b64Encode implements base64 encode for string if necessary.
func b64Encode(s string) string {
if strings.Contains(s, "\n") || !isASCII(s) {
return "{base64}" + base64.StdEncoding.EncodeToString([]byte(s))
}
return s
}
// isASCII indicates string contains only ASCII.
func isASCII(s string) bool {
for i := 0; i < len(s); i++ {
if s[i] > unicode.MaxASCII {
return false
}
}
return true
}