mirror of
https://gitea.com/jolheiser/sip
synced 2024-11-26 07:33:48 +01:00
Split up funcs, add PR create/status/checkout
Signed-off-by: jolheiser <john.olheiser@gmail.com>
This commit is contained in:
parent
20d5d36202
commit
8958c4a312
29
README.md
29
README.md
@ -1,2 +1,29 @@
|
|||||||
# Tea (alternative)
|
# Tea (alternative)
|
||||||
CLI for interacting with Gitea
|
CLI for interacting with Gitea
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
Understands the concepts of an origin vs remote repository.
|
||||||
|
By default uses remotes `origin` and `upstream`.
|
||||||
|
If no `upstream` repository is found, `upstream` becomes synonymous with `origin` for the sake of defaults.
|
||||||
|
|
||||||
|
* Configuration `tea config`
|
||||||
|
* Change the default `origin` remote name `tea config origin`
|
||||||
|
* Change the default `upstream` remote name `tea config upstrea`
|
||||||
|
* Login `tea login`
|
||||||
|
* Add a user token for API usage
|
||||||
|
* Generate a new token from CLI `tea login auto`
|
||||||
|
* Authenticate with username/password to get a new token without leaving the terminal
|
||||||
|
* List available logins `tea login list`
|
||||||
|
* Logout `tea logout`
|
||||||
|
* Remove user tokens
|
||||||
|
* Repository status `tea repo`
|
||||||
|
* Get basic information about the `upstream` repository
|
||||||
|
* Issue search `tea issues`
|
||||||
|
* Search issues based on keyword(s)
|
||||||
|
* Create a new issue `tea issues create`
|
||||||
|
* Pull request search `tea pulls`
|
||||||
|
* Search pull requests based on keyword(s)
|
||||||
|
* Create a new pull request `tea pulls create`
|
||||||
|
* Check pull request status (default based on current branch) `tea pulls status`
|
||||||
|
* Checkout a pull request to test locally `tea pulls checkout`
|
13
cmd/cmd.go
13
cmd/cmd.go
@ -14,6 +14,16 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
Flags = []cli.Flag{
|
Flags = []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "origin",
|
||||||
|
Usage: "The origin remote",
|
||||||
|
Value: config.Origin,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "upstream",
|
||||||
|
Usage: "The upstream remote",
|
||||||
|
Value: config.Upstream,
|
||||||
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "url",
|
Name: "url",
|
||||||
Aliases: []string{"u"},
|
Aliases: []string{"u"},
|
||||||
@ -82,6 +92,9 @@ func getClient(ctx *cli.Context) *gitea.Client {
|
|||||||
func getUpstreamRepo() []string {
|
func getUpstreamRepo() []string {
|
||||||
upstreamOnce.Do(func() {
|
upstreamOnce.Do(func() {
|
||||||
upstreamRepo = git.GetRepo(config.Upstream)
|
upstreamRepo = git.GetRepo(config.Upstream)
|
||||||
|
if upstreamRepo == nil {
|
||||||
|
upstreamRepo = git.GetRepo(config.Origin)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
return upstreamRepo
|
return upstreamRepo
|
||||||
}
|
}
|
||||||
|
@ -10,13 +10,13 @@ import (
|
|||||||
var Config = cli.Command{
|
var Config = cli.Command{
|
||||||
Name: "config",
|
Name: "config",
|
||||||
Aliases: []string{"cfg"},
|
Aliases: []string{"cfg"},
|
||||||
Usage: "Modify Tea config",
|
Usage: "Modify tea config",
|
||||||
Action: doConfig,
|
Action: doConfig,
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
{
|
{
|
||||||
Name: "remote",
|
Name: "origin",
|
||||||
Usage: "Specify default remote name",
|
Usage: "Specify default origin name",
|
||||||
Action: doConfigRemote,
|
Action: doConfigOrigin,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "upstream",
|
Name: "upstream",
|
||||||
@ -27,15 +27,15 @@ var Config = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func doConfig(ctx *cli.Context) error {
|
func doConfig(ctx *cli.Context) error {
|
||||||
if err := doConfigRemote(ctx); err != nil {
|
if err := doConfigOrigin(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return doConfigUpstream(ctx)
|
return doConfigUpstream(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func doConfigRemote(ctx *cli.Context) error {
|
func doConfigOrigin(ctx *cli.Context) error {
|
||||||
question := &survey.Input{
|
question := &survey.Input{
|
||||||
Message: "Default remote name",
|
Message: "Default origin name",
|
||||||
Default: "origin",
|
Default: "origin",
|
||||||
}
|
}
|
||||||
var answer string
|
var answer string
|
||||||
|
102
cmd/issues.go
102
cmd/issues.go
@ -3,7 +3,6 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gitea.com/jolheiser/beaver"
|
|
||||||
"gitea.com/jolheiser/beaver/color"
|
"gitea.com/jolheiser/beaver/color"
|
||||||
"gitea.com/jolheiser/tea/modules/markdown"
|
"gitea.com/jolheiser/tea/modules/markdown"
|
||||||
"gitea.com/jolheiser/tea/modules/sdk"
|
"gitea.com/jolheiser/tea/modules/sdk"
|
||||||
@ -20,27 +19,30 @@ var Issues = cli.Command{
|
|||||||
Usage: "Commands for interacting with issues",
|
Usage: "Commands for interacting with issues",
|
||||||
Action: doIssuesSearch,
|
Action: doIssuesSearch,
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
{
|
&IssuesCreate,
|
||||||
Name: "create",
|
|
||||||
Usage: "Create a new issue",
|
|
||||||
Action: doIssueCreate,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func issuesSearch(ctx *cli.Context, pulls bool) error {
|
func doIssuesSearch(ctx *cli.Context) error {
|
||||||
|
if _, err := issuesSearch(ctx, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func issuesSearch(ctx *cli.Context, pulls bool) (*gitea.Issue, error) {
|
||||||
typ := "issues"
|
typ := "issues"
|
||||||
if pulls {
|
if pulls {
|
||||||
typ = "pulls"
|
typ = "pulls"
|
||||||
}
|
}
|
||||||
issues, err := queryIssues(ctx, getClient(ctx), false)
|
issues, err := queryIssues(ctx, getClient(ctx), pulls)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(issues) == 0 {
|
if len(issues) == 0 {
|
||||||
stdout.Red("No " + typ + " found")
|
stdout.Red("No " + typ + " found")
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
issueMap := make(map[string]*gitea.Issue)
|
issueMap := make(map[string]*gitea.Issue)
|
||||||
@ -68,85 +70,19 @@ func issuesSearch(ctx *cli.Context, pulls bool) error {
|
|||||||
for key := range issueMap {
|
for key := range issueMap {
|
||||||
list = append(list, key)
|
list = append(list, key)
|
||||||
}
|
}
|
||||||
sel := &survey.Select{Options: list, Message: "Matching " + typ + ", select one to see more details"}
|
sel := &survey.Select{Options: list, Message: "Matching " + typ}
|
||||||
var selection string
|
var selection string
|
||||||
if err := survey.AskOne(sel, &selection); err != nil {
|
if err := survey.AskOne(sel, &selection); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := markdown.Render(issueMap[selection].Body)
|
body, err := markdown.Render(issueMap[selection].Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(body)
|
fmt.Println(body)
|
||||||
return nil
|
return issueMap[selection], nil
|
||||||
}
|
|
||||||
|
|
||||||
func doIssuesSearch(ctx *cli.Context) error {
|
|
||||||
return issuesSearch(ctx, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doIssueCreate(ctx *cli.Context) error {
|
|
||||||
beaver.Infof("Creating a new issue for %s/%s/%s", ctx.String("url"), ctx.String("owner"), ctx.String("repo"))
|
|
||||||
|
|
||||||
token, err := requireToken(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var confirmed bool
|
|
||||||
var title, body string
|
|
||||||
|
|
||||||
for !confirmed {
|
|
||||||
questions := []*survey.Question{
|
|
||||||
{
|
|
||||||
Name: "title",
|
|
||||||
Prompt: &survey.Input{Message: "Title", Default: title},
|
|
||||||
Validate: survey.Required,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "body",
|
|
||||||
Prompt: &survey.Multiline{Message: "Description", Default: body},
|
|
||||||
Validate: survey.Required,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
answers := struct {
|
|
||||||
Title string
|
|
||||||
Body string
|
|
||||||
}{}
|
|
||||||
|
|
||||||
if err := survey.Ask(questions, &answers); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
title = answers.Title
|
|
||||||
body = answers.Body
|
|
||||||
|
|
||||||
preview, err := markdown.Render(body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("%s\n\n%s\n", title, preview)
|
|
||||||
confirm := &survey.Confirm{Message: "Preview above, enter to create or 'n' to edit", Default: true}
|
|
||||||
|
|
||||||
if err := survey.AskOne(confirm, &confirmed); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client := gitea.NewClient(ctx.String("url"), token)
|
|
||||||
|
|
||||||
issue, err := client.CreateIssue(ctx.String("owner"), ctx.String("repo"), gitea.CreateIssueOption{Title: title, Body: body})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
info := color.Info
|
|
||||||
cyan := color.New(color.FgCyan)
|
|
||||||
fmt.Println(info.Format("Issue"), cyan.Format(fmt.Sprintf("#%d", issue.Index)), info.Format("created!"))
|
|
||||||
fmt.Println(cyan.Format(fmt.Sprintf("%s/%s/%s/issues/%d", ctx.String("url"), ctx.String("owner"), ctx.String("repo"), issue.Index)))
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func queryIssues(ctx *cli.Context, client *gitea.Client, pulls bool) ([]*gitea.Issue, error) {
|
func queryIssues(ctx *cli.Context, client *gitea.Client, pulls bool) ([]*gitea.Issue, error) {
|
||||||
@ -178,8 +114,10 @@ func queryIssues(ctx *cli.Context, client *gitea.Client, pulls bool) ([]*gitea.I
|
|||||||
|
|
||||||
filtered := make([]*gitea.Issue, 0)
|
filtered := make([]*gitea.Issue, 0)
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
if issue.PullRequest != nil && pulls {
|
if pulls {
|
||||||
filtered = append(filtered, issue)
|
if issue.PullRequest != nil {
|
||||||
|
filtered = append(filtered, issue)
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
filtered = append(filtered, issue)
|
filtered = append(filtered, issue)
|
||||||
|
79
cmd/issues_create.go
Normal file
79
cmd/issues_create.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"fmt"
|
||||||
|
"gitea.com/jolheiser/beaver/color"
|
||||||
|
"gitea.com/jolheiser/tea/modules/markdown"
|
||||||
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var IssuesCreate = cli.Command{
|
||||||
|
Name: "create",
|
||||||
|
Usage: "Create a new issue",
|
||||||
|
Action: doIssueCreate,
|
||||||
|
}
|
||||||
|
|
||||||
|
func doIssueCreate(ctx *cli.Context) error {
|
||||||
|
fmt.Println()
|
||||||
|
url := color.New(color.FgYellow).Format(fmt.Sprintf("%s/%s/%s", ctx.String("url"), ctx.String("owner"), ctx.String("repo")))
|
||||||
|
fmt.Println(color.New(color.FgCyan).Format("Creating a new issue for"), url)
|
||||||
|
|
||||||
|
token, err := requireToken(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var confirmed bool
|
||||||
|
var title, body string
|
||||||
|
|
||||||
|
for !confirmed {
|
||||||
|
questions := []*survey.Question{
|
||||||
|
{
|
||||||
|
Name: "title",
|
||||||
|
Prompt: &survey.Input{Message: "Title", Default: title},
|
||||||
|
Validate: survey.Required,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "body",
|
||||||
|
Prompt: &survey.Multiline{Message: "Description", Default: body},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
answers := struct {
|
||||||
|
Title string
|
||||||
|
Body string
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := survey.Ask(questions, &answers); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
title = answers.Title
|
||||||
|
body = answers.Body
|
||||||
|
|
||||||
|
preview, err := markdown.Render(body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s\n\n%s\n", title, preview)
|
||||||
|
confirm := &survey.Confirm{Message: "Preview above, enter to create or 'n' to edit", Default: true}
|
||||||
|
|
||||||
|
if err := survey.AskOne(confirm, &confirmed); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client := gitea.NewClient(ctx.String("url"), token)
|
||||||
|
|
||||||
|
issue, err := client.CreateIssue(ctx.String("owner"), ctx.String("repo"), gitea.CreateIssueOption{Title: title, Body: body})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
info := color.Info
|
||||||
|
cyan := color.New(color.FgCyan)
|
||||||
|
fmt.Println(info.Format("Issue"), cyan.Format(fmt.Sprintf("#%d", issue.Index)), info.Format("created!"))
|
||||||
|
fmt.Println(cyan.Format(fmt.Sprintf("%s/%s/%s/issues/%d", ctx.String("url"), ctx.String("owner"), ctx.String("repo"), issue.Index)))
|
||||||
|
return nil
|
||||||
|
}
|
120
cmd/pulls.go
120
cmd/pulls.go
@ -1,134 +1,24 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"fmt"
|
|
||||||
"gitea.com/jolheiser/beaver"
|
|
||||||
"gitea.com/jolheiser/beaver/color"
|
|
||||||
"gitea.com/jolheiser/tea/modules/git"
|
|
||||||
"gitea.com/jolheiser/tea/modules/markdown"
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Pulls = cli.Command{
|
var Pulls = cli.Command{
|
||||||
Name: "pulls",
|
Name: "pulls",
|
||||||
Aliases: []string{"pr"},
|
Aliases: []string{"pull", "pr"},
|
||||||
Usage: "Commands for interacting with pull requests",
|
Usage: "Commands for interacting with pull requests",
|
||||||
Action: doPullsSearch,
|
Action: doPullsSearch,
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
{
|
&PullsCreate,
|
||||||
Name: "create",
|
&PullsStatus,
|
||||||
Usage: "Create a new pull request",
|
&PullsCheckout,
|
||||||
Action: doPullCreate,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func doPullsSearch(ctx *cli.Context) error {
|
func doPullsSearch(ctx *cli.Context) error {
|
||||||
return issuesSearch(ctx, true)
|
if _, err := issuesSearch(ctx, true); err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
func doPullCreate(ctx *cli.Context) error {
|
|
||||||
beaver.Infof("Creating a new pull request for %s/%s/%s", ctx.String("url"), ctx.String("owner"), ctx.String("repo"))
|
|
||||||
|
|
||||||
token, err := requireToken(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
client := gitea.NewClient(ctx.String("url"), token)
|
|
||||||
|
|
||||||
upstreams, err := client.ListRepoBranches(getUpstreamRepo()[1], getUpstreamRepo()[2])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
bases := make([]string, len(upstreams))
|
|
||||||
defUpstream := upstreams[0].Name
|
|
||||||
for idx, upstream := range upstreams {
|
|
||||||
if upstream.Name == "master" {
|
|
||||||
defUpstream = upstream.Name
|
|
||||||
}
|
|
||||||
bases[idx] = upstream.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
origins, err := client.ListRepoBranches(getOriginRepo()[1], getOriginRepo()[2])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
heads := make([]string, len(origins))
|
|
||||||
defOrigin := origins[0].Name
|
|
||||||
for idx, origin := range origins {
|
|
||||||
if origin.Name == git.Branch() {
|
|
||||||
defOrigin = origin.Name
|
|
||||||
}
|
|
||||||
heads[idx] = origin.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
var confirmed bool
|
|
||||||
var title, body string
|
|
||||||
base := defUpstream
|
|
||||||
head := defOrigin
|
|
||||||
|
|
||||||
for !confirmed {
|
|
||||||
questions := []*survey.Question{
|
|
||||||
{
|
|
||||||
Name: "title",
|
|
||||||
Prompt: &survey.Input{Message: "Title", Default: title},
|
|
||||||
Validate: survey.Required,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "body",
|
|
||||||
Prompt: &survey.Multiline{Message: "Description", Default: body},
|
|
||||||
Validate: survey.Required,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "base",
|
|
||||||
Prompt: &survey.Select{Message: "Base target", Options: bases, Default: base},
|
|
||||||
Validate: survey.Required,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "head",
|
|
||||||
Prompt: &survey.Select{Message: "Head target", Options: heads, Default: head},
|
|
||||||
Validate: survey.Required,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
answers := struct {
|
|
||||||
Title string
|
|
||||||
Body string
|
|
||||||
Base string
|
|
||||||
Head string
|
|
||||||
}{}
|
|
||||||
|
|
||||||
if err := survey.Ask(questions, &answers); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
title = answers.Title
|
|
||||||
body = answers.Body
|
|
||||||
base = answers.Base
|
|
||||||
head = answers.Head
|
|
||||||
|
|
||||||
preview, err := markdown.Render(body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("%s\n\n%s\n", title, preview)
|
|
||||||
confirm := &survey.Confirm{Message: "Preview above, enter to create or 'n' to edit", Default: true}
|
|
||||||
|
|
||||||
if err := survey.AskOne(confirm, &confirmed); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pull, err := client.CreatePullRequest(ctx.String("owner"), ctx.String("repo"), gitea.CreatePullRequestOption{Title: title, Body: body, Base: base, Head: head})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
info := color.Info
|
|
||||||
cyan := color.New(color.FgCyan)
|
|
||||||
fmt.Println(info.Format("PR"), cyan.Format(fmt.Sprintf("#%d", pull.Index)), info.Format("created!"))
|
|
||||||
fmt.Println(cyan.Format(fmt.Sprintf("%s/%s/%s/pulls/%d", ctx.String("url"), ctx.String("owner"), ctx.String("repo"), pull.Index)))
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
83
cmd/pulls_checkout.go
Normal file
83
cmd/pulls_checkout.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"gitea.com/jolheiser/beaver"
|
||||||
|
"gitea.com/jolheiser/tea/modules/config"
|
||||||
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
"github.com/huandu/xstrings"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var PullsCheckout = cli.Command{
|
||||||
|
Name: "checkout",
|
||||||
|
Usage: "Checkout a pull request for testing",
|
||||||
|
Action: doPullCheckout,
|
||||||
|
}
|
||||||
|
|
||||||
|
func doPullCheckout(ctx *cli.Context) error {
|
||||||
|
var issue *gitea.Issue
|
||||||
|
questions := []*survey.Question{
|
||||||
|
{
|
||||||
|
Name: "index",
|
||||||
|
Prompt: &survey.Input{Message: "Pull request number", Help: "Don't worry if you aren't sure! Just say -1 and we'll search for it instead!"},
|
||||||
|
Validate: validatePRNum,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
prNum := struct {
|
||||||
|
Index int64
|
||||||
|
}{}
|
||||||
|
if err := survey.Ask(questions, &prNum); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if prNum.Index < 0 {
|
||||||
|
var confirmed bool
|
||||||
|
for !confirmed {
|
||||||
|
iss, err := issuesSearch(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
issue = iss
|
||||||
|
confirmation := &survey.Confirm{Message: "Is this the pull request you want to checkout?"}
|
||||||
|
if err := survey.AskOne(confirmation, &confirmed); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
iss, err := getClient(ctx).GetIssue(upstreamRepo[1], upstreamRepo[2], prNum.Index)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
issue = iss
|
||||||
|
}
|
||||||
|
|
||||||
|
if issue == nil {
|
||||||
|
return errors.New("no pull request selected")
|
||||||
|
}
|
||||||
|
|
||||||
|
branch := fmt.Sprintf("pr%d-%s", issue.Index, xstrings.ToKebabCase(issue.Title))
|
||||||
|
cmd := exec.Command("git", "fetch", config.Upstream, fmt.Sprintf("pull/%d/head:%s", issue.Index, branch))
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
beaver.Infof("Pull request successfully checked out. Switch to it using `git checkout %s`", branch)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validatePRNum(ans interface{}) error {
|
||||||
|
if err := survey.Required(ans); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := strconv.Atoi(ans.(string)); err != nil {
|
||||||
|
return errors.New("pull request number must be an number")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
142
cmd/pulls_create.go
Normal file
142
cmd/pulls_create.go
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"fmt"
|
||||||
|
"gitea.com/jolheiser/beaver/color"
|
||||||
|
"gitea.com/jolheiser/tea/modules/git"
|
||||||
|
"gitea.com/jolheiser/tea/modules/markdown"
|
||||||
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var PullsCreate = cli.Command{
|
||||||
|
Name: "create",
|
||||||
|
Usage: "Create a new pull request",
|
||||||
|
Action: doPullCreate,
|
||||||
|
}
|
||||||
|
|
||||||
|
func doPullCreate(ctx *cli.Context) error {
|
||||||
|
fmt.Println()
|
||||||
|
url := color.New(color.FgYellow).Format(fmt.Sprintf("%s/%s/%s", ctx.String("url"), ctx.String("owner"), ctx.String("repo")))
|
||||||
|
fmt.Println(color.New(color.FgCyan).Format("Creating a new pull request for"), url)
|
||||||
|
|
||||||
|
token, err := requireToken(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := gitea.NewClient(ctx.String("url"), token)
|
||||||
|
|
||||||
|
upstreams, err := client.ListRepoBranches(getUpstreamRepo()[1], getUpstreamRepo()[2])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bases := make([]string, len(upstreams))
|
||||||
|
defUpstream := upstreams[0].Name
|
||||||
|
for idx, upstream := range upstreams {
|
||||||
|
if upstream.Name == "master" {
|
||||||
|
defUpstream = upstream.Name
|
||||||
|
}
|
||||||
|
bases[idx] = upstream.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
origins, err := client.ListRepoBranches(getOriginRepo()[1], getOriginRepo()[2])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
heads := make([]string, len(origins))
|
||||||
|
defOrigin := origins[0].Name
|
||||||
|
for idx, origin := range origins {
|
||||||
|
if origin.Name == git.Branch() {
|
||||||
|
defOrigin = getOriginRepo()[1] + ":" + origin.Name
|
||||||
|
}
|
||||||
|
heads[idx] = getOriginRepo()[1] + ":" + origin.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
var confirmed bool
|
||||||
|
var title, body string
|
||||||
|
base := defUpstream
|
||||||
|
head := defOrigin
|
||||||
|
|
||||||
|
for !confirmed {
|
||||||
|
questions := []*survey.Question{
|
||||||
|
{
|
||||||
|
Name: "title",
|
||||||
|
Prompt: &survey.Input{Message: "Title", Default: title},
|
||||||
|
Validate: survey.Required,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "body",
|
||||||
|
Prompt: &survey.Multiline{Message: "Description", Default: body},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "base",
|
||||||
|
Prompt: &survey.Select{Message: "Base target", Options: bases, Default: base},
|
||||||
|
Validate: survey.Required,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "head",
|
||||||
|
Prompt: &survey.Select{Message: "Head target", Options: heads, Default: head},
|
||||||
|
Validate: survey.Required,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
answers := struct {
|
||||||
|
Title string
|
||||||
|
Body string
|
||||||
|
Base string
|
||||||
|
Head string
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := survey.Ask(questions, &answers); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
title = answers.Title
|
||||||
|
body = answers.Body
|
||||||
|
base = answers.Base
|
||||||
|
head = answers.Head
|
||||||
|
|
||||||
|
preview, err := markdown.Render(body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s\n\n%s\n", title, preview)
|
||||||
|
confirm := &survey.Confirm{Message: "Preview above, enter to create or 'n' to edit", Default: true}
|
||||||
|
|
||||||
|
if err := survey.AskOne(confirm, &confirmed); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pull, err := client.CreatePullRequest(ctx.String("owner"), ctx.String("repo"), gitea.CreatePullRequestOption{Title: title, Body: body, Base: base, Head: head})
|
||||||
|
if err != nil {
|
||||||
|
if fmt.Sprint(err) == "409 Conflict" { // Hard-coded in the SDK
|
||||||
|
return existingPR(client, getUpstreamRepo()[1], getUpstreamRepo()[2], head, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
info := color.Info
|
||||||
|
cyan := color.New(color.FgCyan)
|
||||||
|
fmt.Println(info.Format("PR"), cyan.Format(fmt.Sprintf("#%d", pull.Index)), info.Format("created!"))
|
||||||
|
fmt.Println(cyan.Format(fmt.Sprintf("%s/%s/%s/pulls/%d", ctx.String("url"), ctx.String("owner"), ctx.String("repo"), pull.Index)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func existingPR(client *gitea.Client, owner, repo, head string, pullErr error) error {
|
||||||
|
pulls, err := client.ListRepoPullRequests(owner, repo, gitea.ListPullRequestsOptions{State: "open"})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pull := range pulls {
|
||||||
|
compare := fmt.Sprintf("%s:%s", pull.Head.Repository.Owner.UserName, pull.Head.Name)
|
||||||
|
if compare == head {
|
||||||
|
fmt.Println(color.New(color.FgCyan).Format("PR already exists at"), color.New(color.FgYellow).Format(pull.HTMLURL))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pullErr
|
||||||
|
}
|
58
cmd/pulls_status.go
Normal file
58
cmd/pulls_status.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"fmt"
|
||||||
|
"gitea.com/jolheiser/beaver/color"
|
||||||
|
"gitea.com/jolheiser/tea/modules/git"
|
||||||
|
"gitea.com/jolheiser/tea/modules/sdk"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var PullsStatus = cli.Command{
|
||||||
|
Name: "status",
|
||||||
|
Usage: "View the status of a pull request",
|
||||||
|
Action: doPullStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
func doPullStatus(ctx *cli.Context) error {
|
||||||
|
client := getClient(ctx)
|
||||||
|
head := fmt.Sprintf("%s:%s", getOriginRepo()[1], git.Branch())
|
||||||
|
|
||||||
|
pulls, err := sdk.GetPulls(client, getUpstreamRepo()[1], getUpstreamRepo()[2], gitea.ListPullRequestsOptions{State: "all"})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var pr *gitea.PullRequest
|
||||||
|
for _, pull := range pulls {
|
||||||
|
compare := fmt.Sprintf("%s:%s", pull.Head.Repository.Owner.UserName, pull.Head.Name)
|
||||||
|
if compare == head {
|
||||||
|
pr = pull
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pr == nil {
|
||||||
|
return fmt.Errorf("no pull request found with head target %s", color.New(color.FgMagenta).Format(head))
|
||||||
|
}
|
||||||
|
|
||||||
|
index := color.New(color.FgCyan).Format("#" + strconv.Itoa(int(pr.Index)))
|
||||||
|
title := color.New(color.FgYellow).Format(pr.Title)
|
||||||
|
state := color.New(color.FgGreen).Format("[open]")
|
||||||
|
if pr.HasMerged {
|
||||||
|
state = color.New(color.FgMagenta).Format("[merged]")
|
||||||
|
} else if pr.State == gitea.StateClosed {
|
||||||
|
state = color.New(color.FgRed).Format("[closed]")
|
||||||
|
}
|
||||||
|
lbls := make([]string, len(pr.Labels))
|
||||||
|
for idx, label := range pr.Labels {
|
||||||
|
lbls[idx] = label.Name
|
||||||
|
}
|
||||||
|
fmt.Println(index, title, state)
|
||||||
|
fmt.Println(color.New(color.FgCyan).Format(fmt.Sprintf("%d comments", pr.Comments)))
|
||||||
|
fmt.Println(color.New(color.FgYellow).Format(pr.HTMLURL))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
1
go.mod
1
go.mod
@ -8,6 +8,7 @@ require (
|
|||||||
github.com/AlecAivazis/survey/v2 v2.0.5
|
github.com/AlecAivazis/survey/v2 v2.0.5
|
||||||
github.com/BurntSushi/toml v0.3.1
|
github.com/BurntSushi/toml v0.3.1
|
||||||
github.com/charmbracelet/glamour v0.1.0
|
github.com/charmbracelet/glamour v0.1.0
|
||||||
|
github.com/huandu/xstrings v1.3.0
|
||||||
github.com/kr/pretty v0.2.0 // indirect
|
github.com/kr/pretty v0.2.0 // indirect
|
||||||
github.com/kr/pty v1.1.8 // indirect
|
github.com/kr/pty v1.1.8 // indirect
|
||||||
github.com/kyokomi/emoji v2.1.0+incompatible
|
github.com/kyokomi/emoji v2.1.0+incompatible
|
||||||
|
2
go.sum
2
go.sum
@ -45,6 +45,8 @@ github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
|
|||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
|
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
|
||||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
||||||
|
github.com/huandu/xstrings v1.3.0 h1:gvV6jG9dTgFEncxo+AF7PH6MZXi/vZl25owA/8Dg8Wo=
|
||||||
|
github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||||
|
@ -10,7 +10,7 @@ func GetRepo(remoteName string) []string {
|
|||||||
cmd := exec.Command("git", "remote", "get-url", remoteName)
|
cmd := exec.Command("git", "remote", "get-url", remoteName)
|
||||||
out, err := cmd.Output()
|
out, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []string{"", ""}
|
return []string{"https://gitea.com", "jolheiser", "tea"}
|
||||||
}
|
}
|
||||||
|
|
||||||
remote := strings.TrimSpace(string(out))
|
remote := strings.TrimSpace(string(out))
|
||||||
|
Loading…
Reference in New Issue
Block a user