diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 0e99b6c..8feeb4c 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -71,9 +71,8 @@ builds: no_unique_dist_dir: true hooks: post: - - cmd: tar -cJf {{ .Path }}.xz {{ .Path }} - env: - - XZ_OPT=-9 + - cmd: xz -k -9 {{ .Path }} + dir: ./dist/ - cmd: sh .goreleaser.checksum.sh {{ .Path }} - cmd: sh .goreleaser.checksum.sh {{ .Path }}.xz @@ -118,5 +117,10 @@ gitea_urls: api: https://gitea.com/api/v1 download: https://gitea.com +release: + extra_files: + - glob: ./**.xz + - glob: ./**.xz.sha256 + # yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json -# vim: set ts=2 sw=2 tw=0 fo=cnqoj \ No newline at end of file +# vim: set ts=2 sw=2 tw=0 fo=cnqoj diff --git a/README.md b/README.md index 19811e9..7b71d02 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,11 @@ Make sure you have a current go version installed (1.13 or newer). make ``` 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: ```sh diff --git a/cmd/flags/issue_pr.go b/cmd/flags/issue_pr.go index c16ccd9..4d2b2f8 100644 --- a/cmd/flags/issue_pr.go +++ b/cmd/flags/issue_pr.go @@ -71,6 +71,10 @@ var IssueListingFlags = append([]cli.Flag{ Name: "mentions", Aliases: []string{"M"}, }, + &cli.StringFlag{ + Name: "owner", + Aliases: []string{"org"}, + }, &cli.StringFlag{ Name: "from", Aliases: []string{"F"}, diff --git a/cmd/issues/list.go b/cmd/issues/list.go index b67680d..bc43f18 100644 --- a/cmd/issues/list.go +++ b/cmd/issues/list.go @@ -18,7 +18,7 @@ import ( ) 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 @@ -35,7 +35,6 @@ var CmdIssuesList = cli.Command{ // RunIssuesList list issues func RunIssuesList(cmd *cli.Context) error { ctx := context.InitCommand(cmd) - ctx.Ensure(context.CtxRequirement{RemoteRepo: true}) state := gitea.StateOpen switch ctx.String("state") { @@ -75,27 +74,52 @@ func RunIssuesList(cmd *cli.Context) error { 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 labels, _ := flags.LabelFilterFlag.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{ - 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, - }) + if err != nil { + return err + } + } else { + issues, _, err = ctx.Login.Client().ListIssues(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, + Owner: owner, + }) - if err != nil { - return err + if err != nil { + return err + } } fields, err := issueFieldsFlag.GetValues(cmd) diff --git a/go.mod b/go.mod index d519255..2e853e6 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/stretchr/testify v1.7.0 github.com/urfave/cli/v2 v2.16.3 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 ) @@ -60,7 +61,6 @@ require ( github.com/yuin/goldmark-emoji v1.0.1 // indirect golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // 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/tools v0.1.12 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/modules/config/config.go b/modules/config/config.go index 203e3d1..062417b 100644 --- a/modules/config/config.go +++ b/modules/config/config.go @@ -74,7 +74,7 @@ func GetConfigPath() string { // GetPreferences returns preferences based on the config file func GetPreferences() Preferences { - loadConfig() + _ = loadConfig() return config.Prefs } @@ -105,5 +105,5 @@ func saveConfig() error { if err != nil { return err } - return ioutil.WriteFile(ymlPath, bs, 0660) + return ioutil.WriteFile(ymlPath, bs, 0o660) } diff --git a/modules/config/login.go b/modules/config/login.go index 0e7c58a..1d98fc4 100644 --- a/modules/config/login.go +++ b/modules/config/login.go @@ -228,5 +228,5 @@ func (l *Login) GetSSHHost() string { return "" } - return u.Hostname() + return u.Host } diff --git a/modules/git/url.go b/modules/git/url.go index f76a666..f076f94 100644 --- a/modules/git/url.go +++ b/modules/git/url.go @@ -10,13 +10,10 @@ import ( "strings" ) -var ( - protocolRe = regexp.MustCompile("^[a-zA-Z_+-]+://") -) +var protocolRe = regexp.MustCompile("^[a-zA-Z_+-]+://") // URLParser represents a git URL parser -type URLParser struct { -} +type URLParser struct{} // Parse parses the git URL 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 - if strings.HasSuffix(u.Path, ".git") { - u.Path = strings.TrimSuffix(u.Path, ".git") - } + u.Path = strings.TrimSuffix(u.Path, ".git") return } diff --git a/modules/interact/comments.go b/modules/interact/comments.go index 194335d..99b59e0 100644 --- a/modules/interact/comments.go +++ b/modules/interact/comments.go @@ -13,7 +13,7 @@ import ( "code.gitea.io/tea/modules/print" "github.com/AlecAivazis/survey/v2" - "golang.org/x/crypto/ssh/terminal" + "golang.org/x/term" ) // 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 func IsStdinPiped() bool { - return !terminal.IsTerminal(int(os.Stdin.Fd())) + return !term.IsTerminal(int(os.Stdin.Fd())) } diff --git a/modules/interact/login.go b/modules/interact/login.go index 4f159eb..584340e 100644 --- a/modules/interact/login.go +++ b/modules/interact/login.go @@ -49,7 +49,7 @@ func CreateLogin() error { } switch loginMethod { - case "token": + default: // token var hasToken bool promptYN := &survey.Confirm{ Message: "Do you have an access token?", diff --git a/modules/print/comment.go b/modules/print/comment.go index 64947d1..db8b342 100644 --- a/modules/print/comment.go +++ b/modules/print/comment.go @@ -18,12 +18,12 @@ func Comments(comments []*gitea.Comment) { baseURL = getRepoURL(comments[0].HTMLURL) } - var out = make([]string, len(comments)) + out := make([]string, len(comments)) for i, c := range comments { out[i] = formatComment(c) } - outputMarkdown(fmt.Sprintf( + _ = outputMarkdown(fmt.Sprintf( // this will become a heading by means of the first --- from a comment "Comments\n%s", strings.Join(out, "\n"), @@ -32,7 +32,7 @@ func Comments(comments []*gitea.Comment) { // Comment renders a comment to stdout func Comment(c *gitea.Comment) { - outputMarkdown(formatComment(c), getRepoURL(c.HTMLURL)) + _ = outputMarkdown(formatComment(c), getRepoURL(c.HTMLURL)) } func formatComment(c *gitea.Comment) string { diff --git a/modules/print/formatters.go b/modules/print/formatters.go index ec53fa0..4b3da6e 100644 --- a/modules/print/formatters.go +++ b/modules/print/formatters.go @@ -12,12 +12,12 @@ import ( "code.gitea.io/sdk/gitea" "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.. func IsInteractive() bool { - return terminal.IsTerminal(int(os.Stdout.Fd())) + return term.IsTerminal(int(os.Stdout.Fd())) } // captures the repo URL part // of an url diff --git a/modules/print/issue.go b/modules/print/issue.go index ec1c892..52d5bf1 100644 --- a/modules/print/issue.go +++ b/modules/print/issue.go @@ -28,7 +28,7 @@ func IssueDetails(issue *gitea.Issue, reactions []*gitea.Reaction) { 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 { @@ -70,11 +70,13 @@ var IssueFields = []string{ "milestone", "labels", "comments", + "owner", + "repo", } func printIssues(issues []*gitea.Issue, output string, fields []string) { labelMap := map[int64]string{} - var printables = make([]printable, len(issues)) + printables := make([]printable, len(issues)) machineReadable := isMachineReadable(output) for i, x := range issues { @@ -133,19 +135,23 @@ func (x printableIssue) FormatField(field string, machineReadable bool) string { } return "" case "labels": - var labels = make([]string, len(x.Labels)) + labels := make([]string, len(x.Labels)) for i, l := range x.Labels { labels[i] = (*x.formattedLabels)[l.ID] } return strings.Join(labels, " ") case "assignees": - var assignees = make([]string, len(x.Assignees)) + assignees := make([]string, len(x.Assignees)) for i, a := range x.Assignees { assignees[i] = formatUserName(a) } return strings.Join(assignees, " ") case "comments": return fmt.Sprintf("%d", x.Comments) + case "owner": + return x.Repository.Owner + case "repo": + return x.Repository.Name } return "" } diff --git a/modules/print/login.go b/modules/print/login.go index e8bd5d5..0a13cc8 100644 --- a/modules/print/login.go +++ b/modules/print/login.go @@ -28,7 +28,7 @@ func LoginDetails(login *config.Login) { } in += fmt.Sprintf("\nCreated: %s", time.Unix(login.Created, 0).Format(time.RFC822)) - outputMarkdown(in, "") + _ = outputMarkdown(in, "") } // LoginsList prints a listing of logins diff --git a/modules/print/markdown.go b/modules/print/markdown.go index 99318ab..5daac47 100644 --- a/modules/print/markdown.go +++ b/modules/print/markdown.go @@ -9,7 +9,7 @@ import ( "os" "github.com/charmbracelet/glamour" - "golang.org/x/crypto/ssh/terminal" + "golang.org/x/term" ) // outputMarkdown prints markdown to stdout, formatted for terminals. @@ -47,8 +47,8 @@ func outputMarkdown(markdown string, baseURL string) error { func getWordWrap() int { fd := int(os.Stdout.Fd()) width := 80 - if terminal.IsTerminal(fd) { - if w, _, err := terminal.GetSize(fd); err == nil { + if term.IsTerminal(fd) { + if w, _, err := term.GetSize(fd); err == nil { width = w } } diff --git a/modules/print/organization.go b/modules/print/organization.go index a9e10c6..ae6c9bd 100644 --- a/modules/print/organization.go +++ b/modules/print/organization.go @@ -12,7 +12,7 @@ import ( // OrganizationDetails prints details of an org with formatting 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", org.UserName, org.Description, diff --git a/modules/print/repo.go b/modules/print/repo.go index e7c3962..bb2984f 100644 --- a/modules/print/repo.go +++ b/modules/print/repo.go @@ -14,7 +14,7 @@ import ( // ReposList prints a listing of the repos 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 { printables[i] = &printableRepo{r} } @@ -56,7 +56,7 @@ func RepoDetails(repo *gitea.Repository, topics []string) { updated := fmt.Sprintf( "Updated: %s (%s ago)\n", 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( diff --git a/modules/print/table.go b/modules/print/table.go index 1ce6b34..b36b739 100644 --- a/modules/print/table.go +++ b/modules/print/table.go @@ -66,7 +66,6 @@ func (t *table) sort(column uint, desc bool) { 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) Less(i, j int) bool { - const column = 0 if t.sortDesc { i, j = j, i } diff --git a/modules/task/login_create.go b/modules/task/login_create.go index 7131e2f..f736877 100644 --- a/modules/task/login_create.go +++ b/modules/task/login_create.go @@ -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, - // so we just use the hostname - login.SSHHost = serverURL.Hostname() + // so we just use the host + login.SSHHost = serverURL.Host if len(sshKey) == 0 { login.SSHKey, err = findSSHKey(client) diff --git a/modules/task/login_httpsign.go b/modules/task/login_httpsign.go index 4b77c64..e2dc233 100644 --- a/modules/task/login_httpsign.go +++ b/modules/task/login_httpsign.go @@ -91,15 +91,3 @@ func parseKeys(pkinput []byte, sshPath string) string { 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 -} diff --git a/modules/task/pull_create.go b/modules/task/pull_create.go index f61108e..1a7bc6b 100644 --- a/modules/task/pull_create.go +++ b/modules/task/pull_create.go @@ -6,6 +6,7 @@ package task import ( "fmt" + "regexp" "strings" "code.gitea.io/sdk/gitea" @@ -16,6 +17,12 @@ import ( "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 func CreatePull(ctx *context.TeaContext, base, head string, allowMaintainerEdits bool, opts *gitea.CreateIssueOption) (err error) { // default is default branch @@ -65,7 +72,6 @@ func CreatePull(ctx *context.TeaContext, base, head string, allowMaintainerEdits Milestone: opts.Milestone, Deadline: opts.Deadline, }) - if err != nil { 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 -func GetDefaultPRTitle(head string) string { - title := head - if strings.Contains(title, ":") { - title = strings.SplitN(title, ":", 2)[1] +func GetDefaultPRTitle(header string) string { + // Extract the part after the last colon in the input string + colonIndex := strings.LastIndex(header, ":") + 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 = consecutive.ReplaceAllString(title, " ") + return title } diff --git a/modules/task/pull_create_test.go b/modules/task/pull_create_test.go new file mode 100644 index 0000000..819253c --- /dev/null +++ b/modules/task/pull_create_test.go @@ -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) + } + } +} diff --git a/modules/task/repo_clone.go b/modules/task/repo_clone.go index bc164fc..e9e3497 100644 --- a/modules/task/repo_clone.go +++ b/modules/task/repo_clone.go @@ -26,7 +26,6 @@ func RepoClone( callback func(string) (string, error), depth int, ) (*local_git.TeaRepo, error) { - repoMeta, _, err := login.Client().GetRepo(repoOwner, repoName) if err != nil { return nil, err @@ -64,10 +63,14 @@ func RepoClone( return nil, err } upstreamBranch := repoMeta.Parent.DefaultBranch - repo.CreateRemote(&git_config.RemoteConfig{ + _, err = repo.CreateRemote(&git_config.RemoteConfig{ Name: "upstream", URLs: []string{upstreamURL.String()}, }) + if err != nil { + return nil, err + } + repoConf, err := repo.Config() if err != nil { return nil, err diff --git a/modules/utils/parse.go b/modules/utils/parse.go index 94aa8e9..1c4c220 100644 --- a/modules/utils/parse.go +++ b/modules/utils/parse.go @@ -24,10 +24,7 @@ func ArgsToIndices(args []string) ([]int64, error) { // ArgToIndex take issue/pull index as string and return int64 func ArgToIndex(arg string) (int64, error) { - if strings.HasPrefix(arg, "#") { - arg = arg[1:] - } - return strconv.ParseInt(arg, 10, 64) + return strconv.ParseInt(strings.TrimPrefix(arg, "#"), 10, 64) } // NormalizeURL normalizes the input with a protocol diff --git a/modules/utils/parse_test.go b/modules/utils/parse_test.go new file mode 100644 index 0000000..e213b2c --- /dev/null +++ b/modules/utils/parse_test.go @@ -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) + } + }) + } +}