refactor: code cleanup across codebase (#947)

## Summary

- Extract duplicate \`getReleaseByTag\` into shared \`cmd/releases/utils.go\`
- Replace \`log.Fatal\` calls with proper error returns in config and login commands; \`GetLoginByToken\`/\`GetLoginsByHost\`/\`GetLoginByHost\` now return errors
- Remove dead \`portChan\` channel in \`modules/auth/oauth.go\`
- Fix YAML integer detection to use \`strconv.ParseInt\` (correctly handles negatives and large ints)
- Fix \`path.go\` error handling to use \`errors.As\` + \`syscall.ENOTDIR\` instead of string comparison
- Extract repeated credential helper key into local variable in \`SetupHelper\`
- Use existing \`isRemoteDeleted()\` in \`pull_clean.go\` instead of duplicating the logic
- Fix ~30 error message casing violations to follow Go conventions
- Use \`fmt.Errorf\` consistently instead of string concatenation in \`generic.go\`

Reviewed-on: https://gitea.com/gitea/tea/pulls/947
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Bo-Yi Wu (吳柏毅) <appleboy.tw@gmail.com>
Co-authored-by: Nicolas <bircni@icloud.com>
Co-committed-by: Nicolas <bircni@icloud.com>
This commit is contained in:
Nicolas
2026-04-08 03:38:49 +00:00
committed by Bo-Yi Wu (吳柏毅)
parent 662e339bf9
commit f538c05282
36 changed files with 152 additions and 140 deletions

View File

@@ -10,6 +10,7 @@ import (
"path/filepath" "path/filepath"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/cmd/releases"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
@@ -37,15 +38,15 @@ func runReleaseAttachmentCreate(_ stdctx.Context, cmd *cli.Command) error {
client := ctx.Login.Client() client := ctx.Login.Client()
if ctx.Args().Len() < 2 { if ctx.Args().Len() < 2 {
return fmt.Errorf("No release tag or assets specified.\nUsage:\t%s", ctx.Command.UsageText) return fmt.Errorf("no release tag or assets specified.\nUsage:\t%s", ctx.Command.UsageText)
} }
tag := ctx.Args().First() tag := ctx.Args().First()
if len(tag) == 0 { if len(tag) == 0 {
return fmt.Errorf("Release tag needed to create attachment") return fmt.Errorf("release tag needed to create attachment")
} }
release, err := getReleaseByTag(ctx.Owner, ctx.Repo, tag, client) release, err := releases.GetReleaseByTag(ctx.Owner, ctx.Repo, tag, client)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/cmd/releases"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
@@ -42,12 +43,12 @@ func runReleaseAttachmentDelete(_ stdctx.Context, cmd *cli.Command) error {
client := ctx.Login.Client() client := ctx.Login.Client()
if ctx.Args().Len() < 2 { if ctx.Args().Len() < 2 {
return fmt.Errorf("No release tag or attachment names specified.\nUsage:\t%s", ctx.Command.UsageText) return fmt.Errorf("no release tag or attachment names specified.\nUsage:\t%s", ctx.Command.UsageText)
} }
tag := ctx.Args().First() tag := ctx.Args().First()
if len(tag) == 0 { if len(tag) == 0 {
return fmt.Errorf("Release tag needed to delete attachment") return fmt.Errorf("release tag needed to delete attachment")
} }
if !ctx.Bool("confirm") { if !ctx.Bool("confirm") {
@@ -55,7 +56,7 @@ func runReleaseAttachmentDelete(_ stdctx.Context, cmd *cli.Command) error {
return nil return nil
} }
release, err := getReleaseByTag(ctx.Owner, ctx.Repo, tag, client) release, err := releases.GetReleaseByTag(ctx.Owner, ctx.Repo, tag, client)
if err != nil { if err != nil {
return err return err
} }
@@ -75,7 +76,7 @@ func runReleaseAttachmentDelete(_ stdctx.Context, cmd *cli.Command) error {
} }
} }
if attachment == nil { if attachment == nil {
return fmt.Errorf("Release does not have attachment named '%s'", name) return fmt.Errorf("release does not have attachment named '%s'", name)
} }
_, err = client.DeleteReleaseAttachment(ctx.Owner, ctx.Repo, release.ID, attachment.ID) _, err = client.DeleteReleaseAttachment(ctx.Owner, ctx.Repo, release.ID, attachment.ID)

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/cmd/releases"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/print"
@@ -42,10 +43,10 @@ func RunReleaseAttachmentList(_ stdctx.Context, cmd *cli.Command) error {
tag := ctx.Args().First() tag := ctx.Args().First()
if len(tag) == 0 { if len(tag) == 0 {
return fmt.Errorf("Release tag needed to list attachments") return fmt.Errorf("release tag needed to list attachments")
} }
release, err := getReleaseByTag(ctx.Owner, ctx.Repo, tag, client) release, err := releases.GetReleaseByTag(ctx.Owner, ctx.Repo, tag, client)
if err != nil { if err != nil {
return err return err
} }
@@ -59,21 +60,3 @@ func RunReleaseAttachmentList(_ stdctx.Context, cmd *cli.Command) error {
return print.ReleaseAttachmentsList(attachments, ctx.Output) return print.ReleaseAttachmentsList(attachments, ctx.Output)
} }
func getReleaseByTag(owner, repo, tag string, client *gitea.Client) (*gitea.Release, error) {
rl, _, err := client.ListReleases(owner, repo, gitea.ListReleasesOptions{
ListOptions: gitea.ListOptions{Page: -1},
})
if err != nil {
return nil, err
}
if len(rl) == 0 {
return nil, fmt.Errorf("Repo does not have any release")
}
for _, r := range rl {
if r.TagName == tag {
return r, nil
}
}
return nil, fmt.Errorf("Release tag does not exist")
}

View File

@@ -76,9 +76,13 @@ func runRepoClone(ctx stdctx.Context, cmd *cli.Command) error {
owner, repo = utils.GetOwnerAndRepo(url.Path, login.User) owner, repo = utils.GetOwnerAndRepo(url.Path, login.User)
if url.Host != "" { if url.Host != "" {
login = config.GetLoginByHost(url.Host) var lookupErr error
login, lookupErr = config.GetLoginByHost(url.Host)
if lookupErr != nil {
return lookupErr
}
if login == nil { if login == nil {
return fmt.Errorf("No login configured matching host '%s', run `tea login add` first", url.Host) return fmt.Errorf("no login configured matching host '%s', run 'tea login add' first", url.Host)
} }
debug.Printf("Matched login '%s' for host '%s'", login.Name, url.Host) debug.Printf("Matched login '%s' for host '%s'", login.Name, url.Host)
} }

View File

@@ -46,7 +46,7 @@ func runAddComment(_ stdctx.Context, cmd *cli.Command) error {
args := ctx.Args() args := ctx.Args()
if args.Len() == 0 { if args.Len() == 0 {
return fmt.Errorf("Please specify issue / pr index") return fmt.Errorf("please specify issue / pr index")
} }
idx, err := utils.ArgToIndex(ctx.Args().First()) idx, err := utils.ArgToIndex(ctx.Args().First())

View File

@@ -44,7 +44,7 @@ func (f CsvFlag) GetValues(cmd *cli.Command) ([]string, error) {
if f.AvailableFields != nil && val != "" { if f.AvailableFields != nil && val != "" {
for _, field := range selection { for _, field := range selection {
if !utils.Contains(f.AvailableFields, field) { if !utils.Contains(f.AvailableFields, field) {
return nil, fmt.Errorf("Invalid field '%s'", field) return nil, fmt.Errorf("invalid field '%s'", field)
} }
} }
} }

View File

@@ -5,6 +5,7 @@ package flags
import ( import (
"errors" "errors"
"fmt"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
@@ -167,7 +168,7 @@ func ParseState(stateStr string) (gitea.StateType, error) {
case "closed": case "closed":
return gitea.StateClosed, nil return gitea.StateClosed, nil
default: default:
return "", errors.New("unknown state '" + stateStr + "'") return "", fmt.Errorf("unknown state '%s'", stateStr)
} }
} }
@@ -184,6 +185,6 @@ func ParseIssueKind(kindStr string, defaultKind gitea.IssueType) (gitea.IssueTyp
case "pull", "pulls", "pr": case "pull", "pulls", "pr":
return gitea.IssueTypePull, nil return gitea.IssueTypePull, nil
default: default:
return "", errors.New("unknown kind '" + kindStr + "'") return "", fmt.Errorf("unknown kind '%s'", kindStr)
} }
} }

View File

@@ -165,7 +165,7 @@ func GetIssuePRCreateFlags(ctx *context.TeaContext) (*gitea.CreateIssueOption, e
} }
ms, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, milestoneName) ms, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, milestoneName)
if err != nil { if err != nil {
return nil, fmt.Errorf("Milestone '%s' not found", milestoneName) return nil, fmt.Errorf("milestone '%s' not found", milestoneName)
} }
opts.Milestone = ms.ID opts.Milestone = ms.ID
} }

View File

@@ -37,5 +37,5 @@ func runLabels(ctx context.Context, cmd *cli.Command) error {
} }
func runLabelsDetails(cmd *cli.Command) error { func runLabelsDetails(cmd *cli.Command) error {
return fmt.Errorf("Not yet implemented") return fmt.Errorf("not yet implemented")
} }

View File

@@ -5,8 +5,7 @@ package login
import ( import (
"context" "context"
"errors" "fmt"
"log"
"code.gitea.io/tea/modules/config" "code.gitea.io/tea/modules/config"
@@ -27,7 +26,7 @@ var CmdLoginDelete = cli.Command{
func RunLoginDelete(_ context.Context, cmd *cli.Command) error { func RunLoginDelete(_ context.Context, cmd *cli.Command) error {
logins, err := config.GetLogins() logins, err := config.GetLogins()
if err != nil { if err != nil {
log.Fatal(err) return err
} }
var name string var name string
@@ -37,7 +36,7 @@ func RunLoginDelete(_ context.Context, cmd *cli.Command) error {
} else if len(logins) == 1 { } else if len(logins) == 1 {
name = logins[0].Name name = logins[0].Name
} else { } else {
return errors.New("Please specify a login name") return fmt.Errorf("please specify a login name")
} }
return config.DeleteLogin(name) return config.DeleteLogin(name)

View File

@@ -5,7 +5,6 @@ package login
import ( import (
"context" "context"
"log"
"os" "os"
"os/exec" "os/exec"
@@ -34,7 +33,7 @@ func runLoginEdit(_ context.Context, _ *cli.Command) error {
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
log.Fatal(err.Error()) return err
} }
} }
return open.Start(config.GetConfigPath()) return open.Start(config.GetConfigPath())

View File

@@ -7,7 +7,6 @@ import (
"bufio" "bufio"
"context" "context"
"fmt" "fmt"
"log"
"net/url" "net/url"
"os" "os"
"strings" "strings"
@@ -93,7 +92,7 @@ var CmdLoginHelper = cli.Command{
} }
if len(wants["host"]) == 0 { if len(wants["host"]) == 0 {
log.Fatal("Hostname is required") return fmt.Errorf("hostname is required")
} else if len(wants["protocol"]) == 0 { } else if len(wants["protocol"]) == 0 {
wants["protocol"] = "http" wants["protocol"] = "http"
} }
@@ -104,20 +103,24 @@ var CmdLoginHelper = cli.Command{
var lookupErr error var lookupErr error
userConfig, lookupErr = config.GetLoginByName(loginName) userConfig, lookupErr = config.GetLoginByName(loginName)
if lookupErr != nil { if lookupErr != nil {
log.Fatal(lookupErr) return lookupErr
} }
if userConfig == nil { if userConfig == nil {
log.Fatalf("Login '%s' not found", loginName) return fmt.Errorf("login '%s' not found", loginName)
} }
} else { } else {
userConfig = config.GetLoginByHost(wants["host"]) var lookupErr error
userConfig, lookupErr = config.GetLoginByHost(wants["host"])
if lookupErr != nil {
return lookupErr
}
if userConfig == nil { if userConfig == nil {
log.Fatalf("No login found for host '%s'", wants["host"]) return fmt.Errorf("no login found for host '%s'", wants["host"])
} }
} }
if len(userConfig.GetAccessToken()) == 0 { if len(userConfig.GetAccessToken()) == 0 {
log.Fatal("User not set") return fmt.Errorf("user not set")
} }
host, err := url.Parse(userConfig.URL) host, err := url.Parse(userConfig.URL)

View File

@@ -104,7 +104,7 @@ func runReleaseCreate(_ stdctx.Context, cmd *cli.Command) error {
}) })
if err != nil { if err != nil {
if resp != nil && resp.StatusCode == http.StatusConflict { if resp != nil && resp.StatusCode == http.StatusConflict {
return fmt.Errorf("There already is a release for this tag") return fmt.Errorf("there is already a release for this tag")
} }
return err return err
} }

View File

@@ -55,7 +55,7 @@ func runReleaseDelete(_ stdctx.Context, cmd *cli.Command) error {
} }
for _, tag := range ctx.Args().Slice() { for _, tag := range ctx.Args().Slice() {
release, err := getReleaseByTag(ctx.Owner, ctx.Repo, tag, client) release, err := GetReleaseByTag(ctx.Owner, ctx.Repo, tag, client)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -81,7 +81,7 @@ func runReleaseEdit(_ stdctx.Context, cmd *cli.Command) error {
} }
for _, tag := range ctx.Args().Slice() { for _, tag := range ctx.Args().Slice() {
release, err := getReleaseByTag(ctx.Owner, ctx.Repo, tag, client) release, err := GetReleaseByTag(ctx.Owner, ctx.Repo, tag, client)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -5,7 +5,6 @@ package releases
import ( import (
stdctx "context" stdctx "context"
"fmt"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
@@ -48,21 +47,3 @@ func RunReleasesList(_ stdctx.Context, cmd *cli.Command) error {
return print.ReleasesList(releases, ctx.Output) return print.ReleasesList(releases, ctx.Output)
} }
func getReleaseByTag(owner, repo, tag string, client *gitea.Client) (*gitea.Release, error) {
rl, _, err := client.ListReleases(owner, repo, gitea.ListReleasesOptions{
ListOptions: gitea.ListOptions{Page: -1},
})
if err != nil {
return nil, err
}
if len(rl) == 0 {
return nil, fmt.Errorf("Repo does not have any release")
}
for _, r := range rl {
if r.TagName == tag {
return r, nil
}
}
return nil, fmt.Errorf("Release tag does not exist")
}

29
cmd/releases/utils.go Normal file
View File

@@ -0,0 +1,29 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package releases
import (
"fmt"
"code.gitea.io/sdk/gitea"
)
// GetReleaseByTag finds a release by its tag name.
func GetReleaseByTag(owner, repo, tag string, client *gitea.Client) (*gitea.Release, error) {
rl, _, err := client.ListReleases(owner, repo, gitea.ListReleasesOptions{
ListOptions: gitea.ListOptions{Page: -1},
})
if err != nil {
return nil, err
}
if len(rl) == 0 {
return nil, fmt.Errorf("repo does not have any release")
}
for _, r := range rl {
if r.TagName == tag {
return r, nil
}
}
return nil, fmt.Errorf("release tag does not exist")
}

View File

@@ -70,7 +70,7 @@ func runReposSearch(_ stdctx.Context, cmd *cli.Command) error {
org, resp, err := client.GetOrg(teaCmd.String("owner")) org, resp, err := client.GetOrg(teaCmd.String("owner"))
if err != nil { if err != nil {
if resp == nil || resp.StatusCode != http.StatusNotFound { if resp == nil || resp.StatusCode != http.StatusNotFound {
return fmt.Errorf("Could not find owner: %w", err) return fmt.Errorf("could not find owner: %w", err)
} }
// if owner is no org, its a user // if owner is no org, its a user

View File

@@ -41,7 +41,7 @@ func runTrackedTimesAdd(_ stdctx.Context, cmd *cli.Command) error {
} }
if ctx.Args().Len() < 2 { if ctx.Args().Len() < 2 {
return fmt.Errorf("No issue or duration specified.\nUsage:\t%s", ctx.Command.UsageText) return fmt.Errorf("no issue or duration specified.\nUsage:\t%s", ctx.Command.UsageText)
} }
issue, err := utils.ArgToIndex(ctx.Args().First()) issue, err := utils.ArgToIndex(ctx.Args().First())

View File

@@ -36,7 +36,7 @@ func runTrackedTimesDelete(_ stdctx.Context, cmd *cli.Command) error {
client := ctx.Login.Client() client := ctx.Login.Client()
if ctx.Args().Len() < 2 { if ctx.Args().Len() < 2 {
return fmt.Errorf("No issue or time ID specified.\nUsage:\t%s", ctx.Command.UsageText) return fmt.Errorf("no issue or time ID specified.\nUsage:\t%s", ctx.Command.UsageText)
} }
issue, err := utils.ArgToIndex(ctx.Args().First()) issue, err := utils.ArgToIndex(ctx.Args().First())

View File

@@ -35,7 +35,7 @@ func runTrackedTimesReset(_ stdctx.Context, cmd *cli.Command) error {
client := ctx.Login.Client() client := ctx.Login.Client()
if ctx.Args().Len() != 1 { if ctx.Args().Len() != 1 {
return fmt.Errorf("No issue specified.\nUsage:\t%s", ctx.Command.UsageText) return fmt.Errorf("no issue specified.\nUsage:\t%s", ctx.Command.UsageText)
} }
issue, err := utils.ArgToIndex(ctx.Args().First()) issue, err := utils.ArgToIndex(ctx.Args().First())

View File

@@ -226,7 +226,6 @@ func startLocalServerAndOpenBrowser(authURL, expectedState string, opts OAuthOpt
codeChan := make(chan string, 1) codeChan := make(chan string, 1)
stateChan := make(chan string, 1) stateChan := make(chan string, 1)
errChan := make(chan error, 1) errChan := make(chan error, 1)
portChan := make(chan int, 1)
// Parse the redirect URL to get the path // Parse the redirect URL to get the path
parsedURL, err := url.Parse(opts.RedirectURL) parsedURL, err := url.Parse(opts.RedirectURL)
@@ -311,7 +310,6 @@ func startLocalServerAndOpenBrowser(authURL, expectedState string, opts OAuthOpt
if port == 0 { if port == 0 {
addr := listener.Addr().(*net.TCPAddr) addr := listener.Addr().(*net.TCPAddr)
port = addr.Port port = addr.Port
portChan <- port
// Update redirect URL with actual port // Update redirect URL with actual port
parsedURL.Host = fmt.Sprintf("%s:%d", hostname, port) parsedURL.Host = fmt.Sprintf("%s:%d", hostname, port)

View File

@@ -5,7 +5,6 @@ package config
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
@@ -74,7 +73,8 @@ func GetConfigPath() string {
} }
if err != nil { if err != nil {
log.Fatal("unable to get or create config file") fmt.Fprintln(os.Stderr, "unable to get or create config file")
os.Exit(1)
} }
return configFilePath return configFilePath

View File

@@ -8,7 +8,6 @@ import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
"log"
"net/http" "net/http"
"net/http/cookiejar" "net/http/cookiejar"
"net/url" "net/url"
@@ -132,7 +131,7 @@ func GetDefaultLogin() (*Login, error) {
} }
if len(config.Logins) == 0 { if len(config.Logins) == 0 {
return nil, errors.New("No available login") return nil, errors.New("no available login")
} }
for _, l := range config.Logins { for _, l := range config.Logins {
if l.Default { if l.Default {
@@ -178,50 +177,51 @@ func GetLoginByName(name string) (*Login, error) {
} }
// GetLoginByToken get login by token // GetLoginByToken get login by token
func GetLoginByToken(token string) *Login { func GetLoginByToken(token string) (*Login, error) {
if token == "" { if token == "" {
return nil return nil, nil
} }
err := loadConfig() if err := loadConfig(); err != nil {
if err != nil { return nil, err
log.Fatal(err)
} }
for _, l := range config.Logins { for _, l := range config.Logins {
if l.Token == token { if l.Token == token {
return &l return &l, nil
} }
} }
return nil return nil, nil
} }
// GetLoginByHost finds a login by it's server URL // GetLoginByHost finds a login by its server URL
func GetLoginByHost(host string) *Login { func GetLoginByHost(host string) (*Login, error) {
logins := GetLoginsByHost(host) logins, err := GetLoginsByHost(host)
if len(logins) > 0 { if err != nil {
return logins[0] return nil, err
} }
return nil if len(logins) > 0 {
return logins[0], nil
}
return nil, nil
} }
// GetLoginsByHost returns all logins matching a host // GetLoginsByHost returns all logins matching a host
func GetLoginsByHost(host string) []*Login { func GetLoginsByHost(host string) ([]*Login, error) {
err := loadConfig() if err := loadConfig(); err != nil {
if err != nil { return nil, err
log.Fatal(err)
} }
var matches []*Login var matches []*Login
for i := range config.Logins { for i := range config.Logins {
loginURL, err := url.Parse(config.Logins[i].URL) loginURL, err := url.Parse(config.Logins[i].URL)
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
if loginURL.Host == host { if loginURL.Host == host {
matches = append(matches, &config.Logins[i]) matches = append(matches, &config.Logins[i])
} }
} }
return matches return matches, nil
} }
// DeleteLogin delete a login by name from config // DeleteLogin delete a login by name from config
@@ -417,12 +417,13 @@ func doOAuthRefresh(l *Login) (*oauth2.Token, error) {
func (l *Login) Client(options ...gitea.ClientOption) *gitea.Client { func (l *Login) Client(options ...gitea.ClientOption) *gitea.Client {
// Refresh OAuth token if expired or near expiry // Refresh OAuth token if expired or near expiry
if err := l.RefreshOAuthTokenIfNeeded(); err != nil { if err := l.RefreshOAuthTokenIfNeeded(); err != nil {
log.Fatalf("Failed to refresh token: %s\nPlease use 'tea login oauth-refresh %s' to manually refresh the token.\n", err, l.Name) fmt.Fprintf(os.Stderr, "Failed to refresh token: %s\nPlease use 'tea login oauth-refresh %s' to manually refresh the token.\n", err, l.Name)
os.Exit(1)
} }
httpClient := &http.Client{} httpClient := &http.Client{}
if l.Insecure { if l.Insecure {
cookieJar, _ := cookiejar.New(nil) cookieJar, _ := cookiejar.New(nil) // New with nil options never returns an error
httpClient = &http.Client{ httpClient = &http.Client{
Jar: cookieJar, Jar: cookieJar,
@@ -443,12 +444,18 @@ func (l *Login) Client(options ...gitea.ClientOption) *gitea.Client {
} }
if l.SSHCertPrincipal != "" { if l.SSHCertPrincipal != "" {
l.askForSSHPassphrase() if err := l.askForSSHPassphrase(); err != nil {
fmt.Fprintf(os.Stderr, "Failed to read SSH passphrase: %s\n", err)
os.Exit(1)
}
options = append(options, gitea.UseSSHCert(l.SSHCertPrincipal, l.SSHKey, l.SSHPassphrase)) options = append(options, gitea.UseSSHCert(l.SSHCertPrincipal, l.SSHKey, l.SSHPassphrase))
} }
if l.SSHKeyFingerprint != "" { if l.SSHKeyFingerprint != "" {
l.askForSSHPassphrase() if err := l.askForSSHPassphrase(); err != nil {
fmt.Fprintf(os.Stderr, "Failed to read SSH passphrase: %s\n", err)
os.Exit(1)
}
options = append(options, gitea.UseSSHPubkey(l.SSHKeyFingerprint, l.SSHKey, l.SSHPassphrase)) options = append(options, gitea.UseSSHPubkey(l.SSHKeyFingerprint, l.SSHKey, l.SSHPassphrase))
} }
@@ -456,25 +463,25 @@ func (l *Login) Client(options ...gitea.ClientOption) *gitea.Client {
if err != nil { if err != nil {
var versionError *gitea.ErrUnknownVersion var versionError *gitea.ErrUnknownVersion
if !errors.As(err, &versionError) { if !errors.As(err, &versionError) {
log.Fatal(err) fmt.Fprintf(os.Stderr, "Failed to create Gitea client: %s\n", err)
os.Exit(1)
} }
fmt.Fprintf(os.Stderr, "WARNING: could not detect gitea version: %s\nINFO: set gitea version: to last supported one\n", versionError) fmt.Fprintf(os.Stderr, "WARNING: could not detect gitea version: %s\nINFO: set gitea version: to last supported one\n", versionError)
} }
return client return client
} }
func (l *Login) askForSSHPassphrase() { func (l *Login) askForSSHPassphrase() error {
if ok, err := utils.IsKeyEncrypted(l.SSHKey); ok && err == nil && l.SSHPassphrase == "" { if ok, err := utils.IsKeyEncrypted(l.SSHKey); ok && err == nil && l.SSHPassphrase == "" {
if err := huh.NewInput(). return huh.NewInput().
Title("ssh-key is encrypted please enter the passphrase: "). Title("ssh-key is encrypted please enter the passphrase: ").
Validate(huh.ValidateNotEmpty()). Validate(huh.ValidateNotEmpty()).
EchoMode(huh.EchoModePassword). EchoMode(huh.EchoModePassword).
Value(&l.SSHPassphrase). Value(&l.SSHPassphrase).
WithTheme(theme.GetTheme()). WithTheme(theme.GetTheme()).
Run(); err != nil { Run()
log.Fatal(err)
}
} }
return nil
} }
// GetSSHHost returns SSH host name // GetSSHHost returns SSH host name

View File

@@ -20,7 +20,7 @@ import (
"golang.org/x/term" "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") 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. // ErrCommandCanceled is returned when the user explicitly cancels an interactive prompt.
var ErrCommandCanceled = errors.New("command canceled") var ErrCommandCanceled = errors.New("command canceled")

View File

@@ -80,7 +80,7 @@ func (r TeaRepo) TeaFindBranchBySha(sha, repoURL string) (b *git_config.Branch,
return nil, err return nil, err
} }
if remote == nil { if remote == nil {
return nil, fmt.Errorf("No remote found for '%s'", repoURL) return nil, fmt.Errorf("no remote found for '%s'", repoURL)
} }
remoteName := remote.Config().Name remoteName := remote.Config().Name
@@ -133,7 +133,7 @@ func (r TeaRepo) TeaFindBranchByName(branchName, repoURL string) (b *git_config.
return nil, err return nil, err
} }
if remote == nil { if remote == nil {
return nil, fmt.Errorf("No remote found for '%s'", repoURL) return nil, fmt.Errorf("no remote found for '%s'", repoURL)
} }
remoteName := remote.Config().Name remoteName := remote.Config().Name

View File

@@ -41,7 +41,7 @@ func CreateLogin() error {
} }
_, err := url.Parse(s) _, err := url.Parse(s)
if err != nil { if err != nil {
return fmt.Errorf("Invalid URL: %v", err) return fmt.Errorf("invalid URL: %v", err)
} }
return nil return nil
}). }).
@@ -69,7 +69,7 @@ func CreateLogin() error {
} }
for _, login := range logins { for _, login := range logins {
if login.Name == name { if login.Name == name {
return fmt.Errorf("Login with name '%s' already exists", name) return fmt.Errorf("login with name '%s' already exists", name)
} }
} }
return nil return nil
@@ -154,7 +154,7 @@ func CreateLogin() error {
Value(&tokenScopes). Value(&tokenScopes).
Validate(func(s []string) error { Validate(func(s []string) error {
if len(s) == 0 { if len(s) == 0 {
return errors.New("At least one scope is required") return errors.New("at least one scope is required")
} }
return nil return nil
}). }).

View File

@@ -58,7 +58,7 @@ func getPullIndex(ctx *context.TeaContext, branch string) (int64, error) {
return 0, err return 0, err
} }
if len(prs) == 0 { if len(prs) == 0 {
return 0, fmt.Errorf("No open PRs found") return 0, fmt.Errorf("no open PRs found")
} }
opts.ListOptions.Page++ opts.ListOptions.Page++
prOptions := make([]string, 0) prOptions := make([]string, 0)

View File

@@ -150,8 +150,7 @@ func outputYaml(f io.Writer, headers []string, values [][]string) error {
}) })
valueNode := &yaml.Node{Kind: yaml.ScalarNode, Value: val} valueNode := &yaml.Node{Kind: yaml.ScalarNode, Value: val}
intVal, _ := strconv.Atoi(val) if _, err := strconv.ParseInt(val, 10, 64); err == nil {
if strconv.Itoa(intVal) == val {
valueNode.Tag = "!!int" valueNode.Tag = "!!int"
} else { } else {
valueNode.Tag = "!!str" valueNode.Tag = "!!str"

View File

@@ -15,7 +15,7 @@ import (
func CreateIssue(login *config.Login, repoOwner, repoName string, opts gitea.CreateIssueOption) error { func CreateIssue(login *config.Login, repoOwner, repoName string, opts gitea.CreateIssueOption) error {
// title is required // title is required
if len(opts.Title) == 0 { if len(opts.Title) == 0 {
return fmt.Errorf("Title is required") return fmt.Errorf("title is required")
} }
issue, _, err := login.Client().CreateIssue(repoOwner, repoName, opts) issue, _, err := login.Client().CreateIssue(repoOwner, repoName, opts)

View File

@@ -20,12 +20,13 @@ import (
func SetupHelper(login config.Login) (ok bool, err error) { func SetupHelper(login config.Login) (ok bool, err error) {
// Check that the URL is not blank // Check that the URL is not blank
if login.URL == "" { if login.URL == "" {
return false, fmt.Errorf("Invalid gitea url") return false, fmt.Errorf("invalid Gitea URL")
} }
// get all helper to URL in git config // get all helper to URL in git config
helperKey := fmt.Sprintf("credential.%s.helper", login.URL)
var currentHelpers []byte var currentHelpers []byte
if currentHelpers, err = exec.Command("git", "config", "--global", "--get-all", fmt.Sprintf("credential.%s.helper", login.URL)).Output(); err != nil { if currentHelpers, err = exec.Command("git", "config", "--global", "--get-all", helperKey).Output(); err != nil {
currentHelpers = []byte{} currentHelpers = []byte{}
} }
@@ -37,10 +38,10 @@ func SetupHelper(login config.Login) (ok bool, err error) {
} }
// Add tea helper // Add tea helper
if _, err = exec.Command("git", "config", "--global", fmt.Sprintf("credential.%s.helper", login.URL), "").Output(); err != nil { if _, err = exec.Command("git", "config", "--global", helperKey, "").Output(); err != nil {
return false, fmt.Errorf("git config --global %s, error: %s", fmt.Sprintf("credential.%s.helper", login.URL), err) return false, fmt.Errorf("git config --global %s, error: %s", helperKey, err)
} else if _, err = exec.Command("git", "config", "--global", "--add", fmt.Sprintf("credential.%s.helper", login.URL), "!tea login helper").Output(); err != nil { } else if _, err = exec.Command("git", "config", "--global", "--add", helperKey, "!tea login helper").Output(); err != nil {
return false, fmt.Errorf("git config --global --add %s %s, error: %s", fmt.Sprintf("credential.%s.helper", login.URL), "!tea login helper", err) return false, fmt.Errorf("git config --global --add %s %s, error: %s", helperKey, "!tea login helper", err)
} }
return true, nil return true, nil
@@ -62,7 +63,11 @@ func CreateLogin(name, token, user, passwd, otp, scopes, sshKey, giteaURL, sshCe
} }
// ... if we already use this token // ... if we already use this token
if shouldCheckTokenUniqueness(token, sshAgent, sshKey, sshCertPrincipal, sshKeyFingerprint) { if shouldCheckTokenUniqueness(token, sshAgent, sshKey, sshCertPrincipal, sshKeyFingerprint) {
if login := config.GetLoginByToken(token); login != nil { login, err := config.GetLoginByToken(token)
if err != nil {
return err
}
if login != nil {
return fmt.Errorf("token already been used, delete login '%s' first", login.Name) return fmt.Errorf("token already been used, delete login '%s' first", login.Name)
} }
} }

View File

@@ -17,7 +17,7 @@ import (
func CreateMilestone(login *config.Login, repoOwner, repoName, title, description string, deadline *time.Time, state gitea.StateType) error { func CreateMilestone(login *config.Login, repoOwner, repoName, title, description string, deadline *time.Time, state gitea.StateType) error {
// title is required // title is required
if len(title) == 0 { if len(title) == 0 {
return fmt.Errorf("Title is required") return fmt.Errorf("title is required")
} }
mile, _, err := login.Client().CreateMilestone(repoOwner, repoName, gitea.CreateMilestoneOption{ mile, _, err := login.Client().CreateMilestone(repoOwner, repoName, gitea.CreateMilestoneOption{

View File

@@ -39,7 +39,7 @@ func PullClean(login *config.Login, repoOwner, repoName string, index int64, ign
// if remote head branch is already deleted, pr.Head.Ref points to "pulls/<idx>/head" // if remote head branch is already deleted, pr.Head.Ref points to "pulls/<idx>/head"
remoteBranch := pr.Head.Ref remoteBranch := pr.Head.Ref
remoteDeleted := remoteBranch == fmt.Sprintf("refs/pull/%d/head", pr.Index) remoteDeleted := isRemoteDeleted(pr)
if remoteDeleted { if remoteDeleted {
remoteBranch = pr.Head.Name // this still holds the original branch name remoteBranch = pr.Head.Name // this still holds the original branch name
fmt.Printf("Remote branch '%s' already deleted.\n", remoteBranch) fmt.Printf("Remote branch '%s' already deleted.\n", remoteBranch)
@@ -62,9 +62,9 @@ func PullClean(login *config.Login, repoOwner, repoName string, index int64, ign
} }
if branch == nil { if branch == nil {
if ignoreSHA { if ignoreSHA {
return fmt.Errorf("Remote branch %s not found in local repo", remoteBranch) return fmt.Errorf("remote branch %s not found in local repo", remoteBranch)
} }
return fmt.Errorf(`Remote branch %s not found in local repo. return fmt.Errorf(`remote branch %s not found in local repo.
Either you don't track this PR, or the local branch has diverged from the remote. Either you don't track this PR, or the local branch has diverged from the remote.
If you still want to continue & are sure you don't loose any important commits, If you still want to continue & are sure you don't loose any important commits,
call me again with the --ignore-sha flag`, remoteBranch) call me again with the --ignore-sha flag`, remoteBranch)

View File

@@ -18,7 +18,7 @@ func PullMerge(login *config.Login, repoOwner, repoName string, index int64, opt
return err return err
} }
if !success { if !success {
return fmt.Errorf("Failed to merge PR. Is it still open?") return fmt.Errorf("failed to merge PR, is it still open?")
} }
return nil return nil
} }

View File

@@ -9,6 +9,7 @@ import (
"os/user" "os/user"
"path/filepath" "path/filepath"
"strings" "strings"
"syscall"
) )
// PathExists returns whether the given file or directory exists or not // PathExists returns whether the given file or directory exists or not
@@ -38,18 +39,19 @@ func exists(path string, expectDir bool) (bool, error) {
if err != nil { if err != nil {
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
return false, nil return false, nil
} else if err.(*os.PathError).Err.Error() == "not a directory" { }
// some middle segment of path is a file, cannot traverse var pathErr *os.PathError
// FIXME: catches error on linux; go does not provide a way to catch this properly.. 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, nil
} }
return false, err return false, err
} }
isDir := f.IsDir() isDir := f.IsDir()
if isDir && !expectDir { if isDir && !expectDir {
return false, errors.New("A directory with the same name exists") return false, errors.New("a directory with the same name exists")
} else if !isDir && expectDir { } else if !isDir && expectDir {
return false, errors.New("A file with the same name exists") return false, errors.New("a file with the same name exists")
} }
return true, nil return true, nil
} }

View File

@@ -21,17 +21,17 @@ func ValidateAuthenticationMethod(
// Normalize URL // Normalize URL
serverURL, err := NormalizeURL(giteaURL) serverURL, err := NormalizeURL(giteaURL)
if err != nil { if err != nil {
return nil, fmt.Errorf("Unable to parse URL: %s", err) return nil, fmt.Errorf("unable to parse URL: %s", err)
} }
if !sshAgent && sshCertPrincipal == "" && sshKey == "" { if !sshAgent && sshCertPrincipal == "" && sshKey == "" {
// .. if we have enough information to authenticate // .. if we have enough information to authenticate
if len(token) == 0 && (len(user)+len(passwd)) == 0 { if len(token) == 0 && (len(user)+len(passwd)) == 0 {
return nil, fmt.Errorf("No token set") return nil, fmt.Errorf("no token set")
} else if len(user) != 0 && len(passwd) == 0 { } else if len(user) != 0 && len(passwd) == 0 {
return nil, fmt.Errorf("No password set") return nil, fmt.Errorf("no password set")
} else if len(user) == 0 && len(passwd) != 0 { } else if len(user) == 0 && len(passwd) != 0 {
return nil, fmt.Errorf("No user set") return nil, fmt.Errorf("no user set")
} }
} }
return serverURL, nil return serverURL, nil