Merge branch 'main' into fix-524

This commit is contained in:
harryzcy 2023-06-24 02:19:19 +00:00
commit 404439f8fd
25 changed files with 192 additions and 80 deletions

View File

@ -71,9 +71,8 @@ builds:
no_unique_dist_dir: true no_unique_dist_dir: true
hooks: hooks:
post: post:
- cmd: tar -cJf {{ .Path }}.xz {{ .Path }} - cmd: xz -k -9 {{ .Path }}
env: dir: ./dist/
- XZ_OPT=-9
- cmd: sh .goreleaser.checksum.sh {{ .Path }} - cmd: sh .goreleaser.checksum.sh {{ .Path }}
- cmd: sh .goreleaser.checksum.sh {{ .Path }}.xz - cmd: sh .goreleaser.checksum.sh {{ .Path }}.xz
@ -118,5 +117,10 @@ gitea_urls:
api: https://gitea.com/api/v1 api: https://gitea.com/api/v1
download: https://gitea.com download: https://gitea.com
release:
extra_files:
- glob: ./**.xz
- glob: ./**.xz.sha256
# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json # yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj # vim: set ts=2 sw=2 tw=0 fo=cnqoj

View File

@ -105,6 +105,11 @@ Make sure you have a current go version installed (1.13 or newer).
make make
``` ```
Note that GNU Make (gmake on OpenBSD) is required. Note that GNU Make (gmake on OpenBSD) is required.
If you want to install the compiled program you have to execute the following command:
```sh
make install
```
This installs the binary into the "bin" folder inside of your GOPATH folder (`go env GOPATH`). It is possible that this folder isn't in your PATH Environment Variable.
- For a quick installation without `git` & `make`, set $version and exec: - For a quick installation without `git` & `make`, set $version and exec:
```sh ```sh

View File

@ -71,6 +71,10 @@ var IssueListingFlags = append([]cli.Flag{
Name: "mentions", Name: "mentions",
Aliases: []string{"M"}, Aliases: []string{"M"},
}, },
&cli.StringFlag{
Name: "owner",
Aliases: []string{"org"},
},
&cli.StringFlag{ &cli.StringFlag{
Name: "from", Name: "from",
Aliases: []string{"F"}, Aliases: []string{"F"},

View File

@ -18,7 +18,7 @@ import (
) )
var issueFieldsFlag = flags.FieldsFlag(print.IssueFields, []string{ var issueFieldsFlag = flags.FieldsFlag(print.IssueFields, []string{
"index", "title", "state", "author", "milestone", "labels", "index", "title", "state", "author", "milestone", "labels", "owner", "repo",
}) })
// CmdIssuesList represents a sub command of issues to list issues // CmdIssuesList represents a sub command of issues to list issues
@ -35,7 +35,6 @@ var CmdIssuesList = cli.Command{
// RunIssuesList list issues // RunIssuesList list issues
func RunIssuesList(cmd *cli.Context) error { func RunIssuesList(cmd *cli.Context) error {
ctx := context.InitCommand(cmd) ctx := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
state := gitea.StateOpen state := gitea.StateOpen
switch ctx.String("state") { switch ctx.String("state") {
@ -75,27 +74,52 @@ func RunIssuesList(cmd *cli.Context) error {
return err return err
} }
} }
owner := ctx.Owner
if ctx.IsSet("owner") {
owner = ctx.String("owner")
}
// ignore error, as we don't do any input validation on these flags // ignore error, as we don't do any input validation on these flags
labels, _ := flags.LabelFilterFlag.GetValues(cmd) labels, _ := flags.LabelFilterFlag.GetValues(cmd)
milestones, _ := flags.MilestoneFilterFlag.GetValues(cmd) milestones, _ := flags.MilestoneFilterFlag.GetValues(cmd)
var issues []*gitea.Issue
if ctx.Repo != "" {
issues, _, err = ctx.Login.Client().ListRepoIssues(owner, ctx.Repo, gitea.ListIssueOption{
ListOptions: ctx.GetListOptions(),
State: state,
Type: kind,
KeyWord: ctx.String("keyword"),
CreatedBy: ctx.String("author"),
AssignedBy: ctx.String("assigned-to"),
MentionedBy: ctx.String("mentions"),
Labels: labels,
Milestones: milestones,
Since: from,
Before: until,
})
issues, _, err := ctx.Login.Client().ListRepoIssues(ctx.Owner, ctx.Repo, gitea.ListIssueOption{ if err != nil {
ListOptions: ctx.GetListOptions(), return err
State: state, }
Type: kind, } else {
KeyWord: ctx.String("keyword"), issues, _, err = ctx.Login.Client().ListIssues(gitea.ListIssueOption{
CreatedBy: ctx.String("author"), ListOptions: ctx.GetListOptions(),
AssignedBy: ctx.String("assigned-to"), State: state,
MentionedBy: ctx.String("mentions"), Type: kind,
Labels: labels, KeyWord: ctx.String("keyword"),
Milestones: milestones, CreatedBy: ctx.String("author"),
Since: from, AssignedBy: ctx.String("assigned-to"),
Before: until, MentionedBy: ctx.String("mentions"),
}) Labels: labels,
Milestones: milestones,
Since: from,
Before: until,
Owner: owner,
})
if err != nil { if err != nil {
return err return err
}
} }
fields, err := issueFieldsFlag.GetValues(cmd) fields, err := issueFieldsFlag.GetValues(cmd)

2
go.mod
View File

@ -18,6 +18,7 @@ require (
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/urfave/cli/v2 v2.16.3 github.com/urfave/cli/v2 v2.16.3
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
) )
@ -60,7 +61,6 @@ require (
github.com/yuin/goldmark-emoji v1.0.1 // indirect github.com/yuin/goldmark-emoji v1.0.1 // indirect
golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect
golang.org/x/sys v0.0.0-20220926163933-8cfa568d3c25 // indirect golang.org/x/sys v0.0.0-20220926163933-8cfa568d3c25 // indirect
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.12 // indirect golang.org/x/tools v0.1.12 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect

View File

@ -74,7 +74,7 @@ func GetConfigPath() string {
// GetPreferences returns preferences based on the config file // GetPreferences returns preferences based on the config file
func GetPreferences() Preferences { func GetPreferences() Preferences {
loadConfig() _ = loadConfig()
return config.Prefs return config.Prefs
} }
@ -105,5 +105,5 @@ func saveConfig() error {
if err != nil { if err != nil {
return err return err
} }
return ioutil.WriteFile(ymlPath, bs, 0660) return ioutil.WriteFile(ymlPath, bs, 0o660)
} }

View File

@ -228,5 +228,5 @@ func (l *Login) GetSSHHost() string {
return "" return ""
} }
return u.Hostname() return u.Host
} }

View File

@ -10,13 +10,10 @@ import (
"strings" "strings"
) )
var ( var protocolRe = regexp.MustCompile("^[a-zA-Z_+-]+://")
protocolRe = regexp.MustCompile("^[a-zA-Z_+-]+://")
)
// URLParser represents a git URL parser // URLParser represents a git URL parser
type URLParser struct { type URLParser struct{}
}
// Parse parses the git URL // Parse parses the git URL
func (p *URLParser) Parse(rawURL string) (u *url.URL, err error) { func (p *URLParser) Parse(rawURL string) (u *url.URL, err error) {
@ -50,9 +47,7 @@ func (p *URLParser) Parse(rawURL string) (u *url.URL, err error) {
} }
// .git suffix is optional and breaks normalization // .git suffix is optional and breaks normalization
if strings.HasSuffix(u.Path, ".git") { u.Path = strings.TrimSuffix(u.Path, ".git")
u.Path = strings.TrimSuffix(u.Path, ".git")
}
return return
} }

View File

@ -13,7 +13,7 @@ import (
"code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/print"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/term"
) )
// ShowCommentsMaybeInteractive fetches & prints comments, depending on the --comments flag. // ShowCommentsMaybeInteractive fetches & prints comments, depending on the --comments flag.
@ -72,5 +72,5 @@ func ShowCommentsPaginated(ctx *context.TeaContext, idx int64, totalComments int
// IsStdinPiped checks if stdin is piped // IsStdinPiped checks if stdin is piped
func IsStdinPiped() bool { func IsStdinPiped() bool {
return !terminal.IsTerminal(int(os.Stdin.Fd())) return !term.IsTerminal(int(os.Stdin.Fd()))
} }

View File

@ -49,7 +49,7 @@ func CreateLogin() error {
} }
switch loginMethod { switch loginMethod {
case "token": default: // token
var hasToken bool var hasToken bool
promptYN := &survey.Confirm{ promptYN := &survey.Confirm{
Message: "Do you have an access token?", Message: "Do you have an access token?",

View File

@ -18,12 +18,12 @@ func Comments(comments []*gitea.Comment) {
baseURL = getRepoURL(comments[0].HTMLURL) baseURL = getRepoURL(comments[0].HTMLURL)
} }
var out = make([]string, len(comments)) out := make([]string, len(comments))
for i, c := range comments { for i, c := range comments {
out[i] = formatComment(c) out[i] = formatComment(c)
} }
outputMarkdown(fmt.Sprintf( _ = outputMarkdown(fmt.Sprintf(
// this will become a heading by means of the first --- from a comment // this will become a heading by means of the first --- from a comment
"Comments\n%s", "Comments\n%s",
strings.Join(out, "\n"), strings.Join(out, "\n"),
@ -32,7 +32,7 @@ func Comments(comments []*gitea.Comment) {
// Comment renders a comment to stdout // Comment renders a comment to stdout
func Comment(c *gitea.Comment) { func Comment(c *gitea.Comment) {
outputMarkdown(formatComment(c), getRepoURL(c.HTMLURL)) _ = outputMarkdown(formatComment(c), getRepoURL(c.HTMLURL))
} }
func formatComment(c *gitea.Comment) string { func formatComment(c *gitea.Comment) string {

View File

@ -12,12 +12,12 @@ import (
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/muesli/termenv" "github.com/muesli/termenv"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/term"
) )
// IsInteractive checks if the output is piped, but NOT if the session is run interactively.. // IsInteractive checks if the output is piped, but NOT if the session is run interactively..
func IsInteractive() bool { func IsInteractive() bool {
return terminal.IsTerminal(int(os.Stdout.Fd())) return term.IsTerminal(int(os.Stdout.Fd()))
} }
// captures the repo URL part <host>/<owner>/<repo> of an url // captures the repo URL part <host>/<owner>/<repo> of an url

View File

@ -28,7 +28,7 @@ func IssueDetails(issue *gitea.Issue, reactions []*gitea.Reaction) {
out += fmt.Sprintf("\n---\n\n%s\n", formatReactions(reactions)) out += fmt.Sprintf("\n---\n\n%s\n", formatReactions(reactions))
} }
outputMarkdown(out, getRepoURL(issue.HTMLURL)) _ = outputMarkdown(out, getRepoURL(issue.HTMLURL))
} }
func formatReactions(reactions []*gitea.Reaction) string { func formatReactions(reactions []*gitea.Reaction) string {
@ -70,11 +70,13 @@ var IssueFields = []string{
"milestone", "milestone",
"labels", "labels",
"comments", "comments",
"owner",
"repo",
} }
func printIssues(issues []*gitea.Issue, output string, fields []string) { func printIssues(issues []*gitea.Issue, output string, fields []string) {
labelMap := map[int64]string{} labelMap := map[int64]string{}
var printables = make([]printable, len(issues)) printables := make([]printable, len(issues))
machineReadable := isMachineReadable(output) machineReadable := isMachineReadable(output)
for i, x := range issues { for i, x := range issues {
@ -133,19 +135,23 @@ func (x printableIssue) FormatField(field string, machineReadable bool) string {
} }
return "" return ""
case "labels": case "labels":
var labels = make([]string, len(x.Labels)) labels := make([]string, len(x.Labels))
for i, l := range x.Labels { for i, l := range x.Labels {
labels[i] = (*x.formattedLabels)[l.ID] labels[i] = (*x.formattedLabels)[l.ID]
} }
return strings.Join(labels, " ") return strings.Join(labels, " ")
case "assignees": case "assignees":
var assignees = make([]string, len(x.Assignees)) assignees := make([]string, len(x.Assignees))
for i, a := range x.Assignees { for i, a := range x.Assignees {
assignees[i] = formatUserName(a) assignees[i] = formatUserName(a)
} }
return strings.Join(assignees, " ") return strings.Join(assignees, " ")
case "comments": case "comments":
return fmt.Sprintf("%d", x.Comments) return fmt.Sprintf("%d", x.Comments)
case "owner":
return x.Repository.Owner
case "repo":
return x.Repository.Name
} }
return "" return ""
} }

View File

@ -28,7 +28,7 @@ func LoginDetails(login *config.Login) {
} }
in += fmt.Sprintf("\nCreated: %s", time.Unix(login.Created, 0).Format(time.RFC822)) in += fmt.Sprintf("\nCreated: %s", time.Unix(login.Created, 0).Format(time.RFC822))
outputMarkdown(in, "") _ = outputMarkdown(in, "")
} }
// LoginsList prints a listing of logins // LoginsList prints a listing of logins

View File

@ -9,7 +9,7 @@ import (
"os" "os"
"github.com/charmbracelet/glamour" "github.com/charmbracelet/glamour"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/term"
) )
// outputMarkdown prints markdown to stdout, formatted for terminals. // outputMarkdown prints markdown to stdout, formatted for terminals.
@ -47,8 +47,8 @@ func outputMarkdown(markdown string, baseURL string) error {
func getWordWrap() int { func getWordWrap() int {
fd := int(os.Stdout.Fd()) fd := int(os.Stdout.Fd())
width := 80 width := 80
if terminal.IsTerminal(fd) { if term.IsTerminal(fd) {
if w, _, err := terminal.GetSize(fd); err == nil { if w, _, err := term.GetSize(fd); err == nil {
width = w width = w
} }
} }

View File

@ -12,7 +12,7 @@ import (
// OrganizationDetails prints details of an org with formatting // OrganizationDetails prints details of an org with formatting
func OrganizationDetails(org *gitea.Organization) { func OrganizationDetails(org *gitea.Organization) {
outputMarkdown(fmt.Sprintf( _ = outputMarkdown(fmt.Sprintf(
"# %s\n%s\n\n- Visibility: %s\n- Location: %s\n- Website: %s\n", "# %s\n%s\n\n- Visibility: %s\n- Location: %s\n- Website: %s\n",
org.UserName, org.UserName,
org.Description, org.Description,

View File

@ -14,7 +14,7 @@ import (
// ReposList prints a listing of the repos // ReposList prints a listing of the repos
func ReposList(repos []*gitea.Repository, output string, fields []string) { func ReposList(repos []*gitea.Repository, output string, fields []string) {
var printables = make([]printable, len(repos)) printables := make([]printable, len(repos))
for i, r := range repos { for i, r := range repos {
printables[i] = &printableRepo{r} printables[i] = &printableRepo{r}
} }
@ -56,7 +56,7 @@ func RepoDetails(repo *gitea.Repository, topics []string) {
updated := fmt.Sprintf( updated := fmt.Sprintf(
"Updated: %s (%s ago)\n", "Updated: %s (%s ago)\n",
repo.Updated.Format("2006-01-02 15:04"), repo.Updated.Format("2006-01-02 15:04"),
time.Now().Sub(repo.Updated).Truncate(time.Minute), time.Since(repo.Updated).Truncate(time.Minute),
) )
urls := fmt.Sprintf( urls := fmt.Sprintf(

View File

@ -66,7 +66,6 @@ func (t *table) sort(column uint, desc bool) {
func (t table) Len() int { return len(t.values) } func (t table) Len() int { return len(t.values) }
func (t table) Swap(i, j int) { t.values[i], t.values[j] = t.values[j], t.values[i] } func (t table) Swap(i, j int) { t.values[i], t.values[j] = t.values[j], t.values[i] }
func (t table) Less(i, j int) bool { func (t table) Less(i, j int) bool {
const column = 0
if t.sortDesc { if t.sortDesc {
i, j = j, i i, j = j, i
} }

View File

@ -90,8 +90,8 @@ func CreateLogin(name, token, user, passwd, sshKey, giteaURL, sshCertPrincipal,
} }
// we do not have a method to get SSH config from api, // we do not have a method to get SSH config from api,
// so we just use the hostname // so we just use the host
login.SSHHost = serverURL.Hostname() login.SSHHost = serverURL.Host
if len(sshKey) == 0 { if len(sshKey) == 0 {
login.SSHKey, err = findSSHKey(client) login.SSHKey, err = findSSHKey(client)

View File

@ -91,15 +91,3 @@ func parseKeys(pkinput []byte, sshPath string) string {
return ssh.FingerprintSHA256(pkey) + " " + pkey.Type() + " " + comment + " (" + sshPath + ")" return ssh.FingerprintSHA256(pkey) + " " + pkey.Type() + " " + comment + " (" + sshPath + ")"
} }
func getCertPrincipals(pkey ssh.PublicKey) []string {
var principals []string
if cert, ok := pkey.(*ssh.Certificate); ok {
for _, principal := range cert.ValidPrincipals {
principals = append(principals, principal)
}
}
return principals
}

View File

@ -6,6 +6,7 @@ package task
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
@ -16,6 +17,12 @@ import (
"code.gitea.io/tea/modules/utils" "code.gitea.io/tea/modules/utils"
) )
var (
spaceRegex = regexp.MustCompile(`[\s_-]+`)
noSpace = regexp.MustCompile(`^[^a-zA-Z\s]*`)
consecutive = regexp.MustCompile(`[\s]{2,}`)
)
// CreatePull creates a PR in the given repo and prints the result // CreatePull creates a PR in the given repo and prints the result
func CreatePull(ctx *context.TeaContext, base, head string, allowMaintainerEdits bool, opts *gitea.CreateIssueOption) (err error) { func CreatePull(ctx *context.TeaContext, base, head string, allowMaintainerEdits bool, opts *gitea.CreateIssueOption) (err error) {
// default is default branch // default is default branch
@ -65,7 +72,6 @@ func CreatePull(ctx *context.TeaContext, base, head string, allowMaintainerEdits
Milestone: opts.Milestone, Milestone: opts.Milestone,
Deadline: opts.Deadline, Deadline: opts.Deadline,
}) })
if err != nil { if err != nil {
return fmt.Errorf("could not create PR from %s to %s:%s: %s", head, ctx.Owner, base, err) return fmt.Errorf("could not create PR from %s to %s:%s: %s", head, ctx.Owner, base, err)
} }
@ -133,13 +139,18 @@ func GetHeadSpec(owner, branch, baseOwner string) string {
} }
// GetDefaultPRTitle transforms a string like a branchname to a readable text // GetDefaultPRTitle transforms a string like a branchname to a readable text
func GetDefaultPRTitle(head string) string { func GetDefaultPRTitle(header string) string {
title := head // Extract the part after the last colon in the input string
if strings.Contains(title, ":") { colonIndex := strings.LastIndex(header, ":")
title = strings.SplitN(title, ":", 2)[1] if colonIndex != -1 {
header = header[colonIndex+1:]
} }
title = strings.Replace(title, "-", " ", -1)
title = strings.Replace(title, "_", " ", -1) title := noSpace.ReplaceAllString(header, "")
title = spaceRegex.ReplaceAllString(title, " ")
title = strings.TrimSpace(title)
title = strings.Title(strings.ToLower(title)) title = strings.Title(strings.ToLower(title))
title = consecutive.ReplaceAllString(title, " ")
return title return title
} }

View File

@ -0,0 +1,28 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package task
import "testing"
func TestGetDefaultPRTitle(t *testing.T) {
tests := []struct {
input string
want string
}{
{input: "Add new feature", want: "Add New Feature"},
{input: "update-docs: Fix typo", want: "Fix Typo"},
{input: "remove_long-string", want: "Remove Long String"},
{input: "Replace_Underscores_With_Spaces", want: "Replace Underscores With Spaces"},
{input: " leading-and-trailing-spaces ", want: "Leading And Trailing Spaces"},
{input: "-----No--Upper--Case-----", want: "No Upper Case"},
{input: "", want: ""},
}
for _, test := range tests {
got := GetDefaultPRTitle(test.input)
if got != test.want {
t.Errorf("GetDefaultPRTitle(%q) = %q, want %q", test.input, got, test.want)
}
}
}

View File

@ -26,7 +26,6 @@ func RepoClone(
callback func(string) (string, error), callback func(string) (string, error),
depth int, depth int,
) (*local_git.TeaRepo, error) { ) (*local_git.TeaRepo, error) {
repoMeta, _, err := login.Client().GetRepo(repoOwner, repoName) repoMeta, _, err := login.Client().GetRepo(repoOwner, repoName)
if err != nil { if err != nil {
return nil, err return nil, err
@ -64,10 +63,14 @@ func RepoClone(
return nil, err return nil, err
} }
upstreamBranch := repoMeta.Parent.DefaultBranch upstreamBranch := repoMeta.Parent.DefaultBranch
repo.CreateRemote(&git_config.RemoteConfig{ _, err = repo.CreateRemote(&git_config.RemoteConfig{
Name: "upstream", Name: "upstream",
URLs: []string{upstreamURL.String()}, URLs: []string{upstreamURL.String()},
}) })
if err != nil {
return nil, err
}
repoConf, err := repo.Config() repoConf, err := repo.Config()
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -24,10 +24,7 @@ func ArgsToIndices(args []string) ([]int64, error) {
// ArgToIndex take issue/pull index as string and return int64 // ArgToIndex take issue/pull index as string and return int64
func ArgToIndex(arg string) (int64, error) { func ArgToIndex(arg string) (int64, error) {
if strings.HasPrefix(arg, "#") { return strconv.ParseInt(strings.TrimPrefix(arg, "#"), 10, 64)
arg = arg[1:]
}
return strconv.ParseInt(arg, 10, 64)
} }
// NormalizeURL normalizes the input with a protocol // NormalizeURL normalizes the input with a protocol

View File

@ -0,0 +1,48 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
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)
}
})
}
}