mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
revert: tea 1.0 rewrite (#1122–#1126) (#1146)
Reverts #1126, #1125, #1124, #1123 and #1122. `go.mod` keeps the Go 1.26 line with toolchain `go1.26.9` (instead of the original `go1.26.6`) so the security fix from #1131 isn't lost. reopens #69 reopens #141 reopens #162 reopens #172 reopens #197 reopens #232 reopens #240 reopens #246 reopens #323 reopens #325 reopens #326 reopens #328 reopens #344 reopens #351 reopens #373 reopens #403 reopens #406 reopens #417 reopens #430 reopens #457 reopens #480 reopens #482 reopens #489 reopens #490 reopens #498 reopens #500 reopens #504 reopens #510 reopens #516 reopens #523 reopens #556 reopens #560 reopens #569 reopens #571 reopens #602 reopens #632 reopens #633 reopens #643 reopens #666 reopens #670 reopens #672 reopens #677 reopens #683 reopens #697 reopens #726 reopens #737 reopens #770 reopens #779 reopens #804 reopens #817 reopens #831 reopens #833 reopens #834 reopens #908 reopens #966 reopens #1023 reopens #1029 reopens #1032 reopens #1050 reopens #1052 reopens #1069 reopens #1073 reopens #1087 reopens #1091 reopens #1094 reopens #1110 reopens #1115 reopens #1116 reopens #1118 Reviewed-on: https://gitea.com/gitea/tea/pulls/1146 Reviewed-by: TheFox0x7 <95654+thefox0x7@noreply.gitea.com>
This commit is contained in:
1 parent
33bb12729e
commit
c5f674111a
604 files changed
+33711
-35545
No files matched your search
@@ -0,0 +1,31 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package utils
|
||||
|
||||
import "regexp"
|
||||
|
||||
// draftPrefixRe matches the prefixes Gitea recognizes as marking a pull
|
||||
// request as a draft: a leading "WIP:" or "[WIP]" (case-insensitive),
|
||||
// followed by any whitespace.
|
||||
var draftPrefixRe = regexp.MustCompile(`(?i)^(wip:\s*|\[wip\]\s*)`)
|
||||
|
||||
// HasDraftPrefix reports whether title already starts with a draft marker.
|
||||
func HasDraftPrefix(title string) bool {
|
||||
return draftPrefixRe.MatchString(title)
|
||||
}
|
||||
|
||||
// AddDraftPrefix returns title with a "WIP: " prefix, or title unchanged
|
||||
// if it already carries a recognized draft prefix.
|
||||
func AddDraftPrefix(title string) string {
|
||||
if HasDraftPrefix(title) {
|
||||
return title
|
||||
}
|
||||
return "WIP: " + title
|
||||
}
|
||||
|
||||
// StripDraftPrefix returns title with any recognized leading draft prefix
|
||||
// removed, or title unchanged if no prefix is present.
|
||||
func StripDraftPrefix(title string) string {
|
||||
return draftPrefixRe.ReplaceAllString(title, "")
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package utils
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDraftPrefix(t *testing.T) {
|
||||
cases := []struct {
|
||||
in string
|
||||
has bool
|
||||
stripped string
|
||||
withDraft string
|
||||
}{
|
||||
{"plain title", false, "plain title", "WIP: plain title"},
|
||||
{"WIP: already", true, "already", "WIP: already"},
|
||||
{"wip: lowercase", true, "lowercase", "wip: lowercase"},
|
||||
{"[WIP] bracketed", true, "bracketed", "[WIP] bracketed"},
|
||||
{"[wip] extra space", true, "extra space", "[wip] extra space"},
|
||||
{"Draft: not recognized", false, "Draft: not recognized", "WIP: Draft: not recognized"},
|
||||
{"", false, "", "WIP: "},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := HasDraftPrefix(c.in); got != c.has {
|
||||
t.Errorf("HasDraftPrefix(%q) = %v, want %v", c.in, got, c.has)
|
||||
}
|
||||
if got := StripDraftPrefix(c.in); got != c.stripped {
|
||||
t.Errorf("StripDraftPrefix(%q) = %q, want %q", c.in, got, c.stripped)
|
||||
}
|
||||
if got := AddDraftPrefix(c.in); got != c.withDraft {
|
||||
t.Errorf("AddDraftPrefix(%q) = %q, want %q", c.in, got, c.withDraft)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// ReadValueOptions contains options for reading a value from various sources
|
||||
type ReadValueOptions struct {
|
||||
// ResourceName is the name of the resource (e.g., "secret", "variable")
|
||||
ResourceName string
|
||||
// PromptMsg is the message to display when prompting interactively
|
||||
PromptMsg string
|
||||
// Hidden determines if the input should be hidden (for secrets/passwords)
|
||||
Hidden bool
|
||||
// AllowEmpty determines if empty values are allowed
|
||||
AllowEmpty bool
|
||||
}
|
||||
|
||||
// ReadValue reads a value from various sources in the following priority order:
|
||||
// 1. From a file specified by --file flag
|
||||
// 2. From stdin if --stdin flag is set
|
||||
// 3. From command arguments (second argument)
|
||||
// 4. Interactive prompt
|
||||
func ReadValue(cmd *cli.Command, opts ReadValueOptions) (string, error) {
|
||||
var value string
|
||||
|
||||
// 1. Read from file
|
||||
if filePath := cmd.String("file"); filePath != "" {
|
||||
content, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read file: %w", err)
|
||||
}
|
||||
value = strings.TrimSpace(string(content))
|
||||
} else if cmd.Bool("stdin") {
|
||||
// 2. Read from stdin
|
||||
content, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read from stdin: %w", err)
|
||||
}
|
||||
value = strings.TrimSpace(string(content))
|
||||
} else if cmd.Args().Len() >= 2 {
|
||||
// 3. Use provided argument
|
||||
value = cmd.Args().Get(1)
|
||||
} else {
|
||||
// 4. Interactive prompt
|
||||
if opts.PromptMsg == "" {
|
||||
opts.PromptMsg = fmt.Sprintf("Enter %s value", opts.ResourceName)
|
||||
}
|
||||
fmt.Printf("%s: ", opts.PromptMsg)
|
||||
|
||||
if opts.Hidden {
|
||||
// Hidden input for secrets/passwords
|
||||
byteValue, err := term.ReadPassword(int(syscall.Stdin))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read %s value: %w", opts.ResourceName, err)
|
||||
}
|
||||
fmt.Println() // Add newline after hidden input
|
||||
value = string(byteValue)
|
||||
} else {
|
||||
// Regular visible input - read entire line including spaces
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
input, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read %s value: %w", opts.ResourceName, err)
|
||||
}
|
||||
value = strings.TrimSpace(input)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate non-empty if required
|
||||
if !opts.AllowEmpty && value == "" {
|
||||
return "", fmt.Errorf("%s value cannot be empty", opts.ResourceName)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ArgsToIndices take issue/pull index as string and returns int64s
|
||||
func ArgsToIndices(args []string) ([]int64, error) {
|
||||
indices := make([]int64, len(args))
|
||||
for i, arg := range args {
|
||||
var err error
|
||||
if indices[i], err = ArgToIndex(arg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return indices, nil
|
||||
}
|
||||
|
||||
// ArgToIndex take issue/pull index as string and return int64
|
||||
func ArgToIndex(arg string) (int64, error) {
|
||||
return strconv.ParseInt(strings.TrimPrefix(arg, "#"), 10, 64)
|
||||
}
|
||||
|
||||
// NormalizeURL normalizes the input with a protocol
|
||||
func NormalizeURL(raw string) (*url.URL, error) {
|
||||
var prefix string
|
||||
if !strings.HasPrefix(raw, "http") {
|
||||
prefix = "https://"
|
||||
}
|
||||
return url.Parse(strings.TrimSuffix(prefix+raw, "/"))
|
||||
}
|
||||
|
||||
// GetOwnerAndRepo return repoOwner and repoName
|
||||
// based on relative path and default owner (if not in path)
|
||||
func GetOwnerAndRepo(repoPath, user string) (string, string) {
|
||||
if len(repoPath) == 0 {
|
||||
return "", ""
|
||||
}
|
||||
p := strings.Split(strings.TrimLeft(repoPath, "/"), "/")
|
||||
if len(p) >= 2 {
|
||||
return p[0], p[1]
|
||||
}
|
||||
return user, repoPath
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package utils
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestArgToIndex(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
arg string
|
||||
want int64
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Valid argument",
|
||||
arg: "#123",
|
||||
want: 123,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid argument",
|
||||
arg: "abc",
|
||||
want: 0,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Empty argument",
|
||||
arg: "",
|
||||
want: 0,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ArgToIndex(tt.arg)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ArgToIndex() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("ArgToIndex() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// PathExists returns whether the given file or directory exists or not
|
||||
func PathExists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
|
||||
// FileExist returns whether the given file exists or not
|
||||
func FileExist(fileName string) (bool, error) {
|
||||
return exists(fileName, false)
|
||||
}
|
||||
|
||||
// DirExists returns whether the given file exists or not
|
||||
func DirExists(path string) (bool, error) {
|
||||
return exists(path, true)
|
||||
}
|
||||
|
||||
func exists(path string, expectDir bool) (bool, error) {
|
||||
f, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return false, nil
|
||||
}
|
||||
var pathErr *os.PathError
|
||||
if errors.As(err, &pathErr) && errors.Is(pathErr.Err, syscall.ENOTDIR) {
|
||||
// a middle segment of path is a file, cannot traverse
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
isDir := f.IsDir()
|
||||
if isDir && !expectDir {
|
||||
return false, errors.New("a directory with the same name exists")
|
||||
} else if !isDir && expectDir {
|
||||
return false, errors.New("a file with the same name exists")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// AbsPathWithExpansion expand path beginning with "~/" to absolute path
|
||||
func AbsPathWithExpansion(p string) (string, error) {
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if p == "~" {
|
||||
return u.HomeDir, nil
|
||||
}
|
||||
if strings.HasPrefix(p, "~/") {
|
||||
return filepath.Join(u.HomeDir, p[2:]), nil
|
||||
}
|
||||
return filepath.Abs(p)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// Contains checks containment
|
||||
func Contains(haystack []string, needle string) bool {
|
||||
return IndexOf(haystack, needle) != -1
|
||||
}
|
||||
|
||||
// IndexOf returns the index of first occurrence of needle in haystack
|
||||
func IndexOf(haystack []string, needle string) int {
|
||||
for i, s := range haystack {
|
||||
if s == needle {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// IsKeyEncrypted checks if the key is encrypted
|
||||
func IsKeyEncrypted(sshKey string) (bool, error) {
|
||||
priv, err := os.ReadFile(sshKey)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
_, err = ssh.ParsePrivateKey(priv)
|
||||
if err != nil {
|
||||
if _, ok := err.(*ssh.PassphraseMissingError); ok {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// ValidateAuthenticationMethod checks the provided authentication method parameters
|
||||
func ValidateAuthenticationMethod(
|
||||
giteaURL string,
|
||||
token string,
|
||||
user string,
|
||||
passwd string,
|
||||
sshAgent bool,
|
||||
sshKey string,
|
||||
sshCertPrincipal string,
|
||||
) (*url.URL, error) {
|
||||
// Normalize URL
|
||||
serverURL, err := NormalizeURL(giteaURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse URL: %s", err)
|
||||
}
|
||||
|
||||
if !sshAgent && sshCertPrincipal == "" && sshKey == "" {
|
||||
// .. if we have enough information to authenticate
|
||||
if len(token) == 0 && (len(user)+len(passwd)) == 0 {
|
||||
return nil, fmt.Errorf("no token set")
|
||||
} else if len(user) != 0 && len(passwd) == 0 {
|
||||
return nil, fmt.Errorf("no password set")
|
||||
} else if len(user) == 0 && len(passwd) != 0 {
|
||||
return nil, fmt.Errorf("no user set")
|
||||
}
|
||||
}
|
||||
return serverURL, nil
|
||||
}
|
||||
Reference in new issue
Block a user