Add more issue / pr creation params (#331)
adds assignees, labels, deadline, milestone params - [x] add flags to `tea issue create` (this is BREAKING, `-b` moved to `-d` for consistency with pr create) - [x] add interactive mode to `tea issue create` - [x] add flags to `tea pr create` - [x] add interactive mode to `tea pr create` fixes #171, fixes #303 Co-authored-by: Norwin Roosen <git@nroo.de> Co-authored-by: 6543 <6543@obermui.de> Reviewed-on: https://gitea.com/gitea/tea/pulls/331 Reviewed-by: 6543 <6543@obermui.de> Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: Norwin <noerw@noreply.gitea.io> Co-committed-by: Norwin <noerw@noreply.gitea.io>
This commit is contained in:
parent
d22b314701
commit
6f738df4a5
|
@ -8,8 +8,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"code.gitea.io/tea/modules/context"
|
||||||
|
"code.gitea.io/tea/modules/task"
|
||||||
"code.gitea.io/tea/modules/utils"
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
|
"github.com/araddon/dateparse"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -97,6 +101,82 @@ var IssuePRFlags = append([]cli.Flag{
|
||||||
&PaginationLimitFlag,
|
&PaginationLimitFlag,
|
||||||
}, AllDefaultFlags...)
|
}, AllDefaultFlags...)
|
||||||
|
|
||||||
|
// IssuePREditFlags defines flags for properties of issues and PRs
|
||||||
|
var IssuePREditFlags = append([]cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "title",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "description",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "assignees",
|
||||||
|
Aliases: []string{"a"},
|
||||||
|
Usage: "Comma-separated list of usernames to assign",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "labels",
|
||||||
|
Aliases: []string{"L"},
|
||||||
|
Usage: "Comma-separated list of labels to assign",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "deadline",
|
||||||
|
Aliases: []string{"D"},
|
||||||
|
Usage: "Deadline timestamp to assign",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "milestone",
|
||||||
|
Aliases: []string{"m"},
|
||||||
|
Usage: "Milestone to assign",
|
||||||
|
},
|
||||||
|
}, LoginRepoFlags...)
|
||||||
|
|
||||||
|
// GetIssuePREditFlags parses all IssuePREditFlags
|
||||||
|
func GetIssuePREditFlags(ctx *context.TeaContext) (*gitea.CreateIssueOption, error) {
|
||||||
|
opts := gitea.CreateIssueOption{
|
||||||
|
Title: ctx.String("title"),
|
||||||
|
Body: ctx.String("body"),
|
||||||
|
Assignees: strings.Split(ctx.String("assignees"), ","),
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
|
||||||
|
date := ctx.String("deadline")
|
||||||
|
if date != "" {
|
||||||
|
t, err := dateparse.ParseAny(date)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
opts.Deadline = &t
|
||||||
|
}
|
||||||
|
|
||||||
|
client := ctx.Login.Client()
|
||||||
|
|
||||||
|
labelNames := strings.Split(ctx.String("labels"), ",")
|
||||||
|
if len(labelNames) != 0 {
|
||||||
|
if client == nil {
|
||||||
|
client = ctx.Login.Client()
|
||||||
|
}
|
||||||
|
if opts.Labels, err = task.ResolveLabelNames(client, ctx.Owner, ctx.Repo, labelNames); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if milestoneName := ctx.String("milestone"); len(milestoneName) != 0 {
|
||||||
|
if client == nil {
|
||||||
|
client = ctx.Login.Client()
|
||||||
|
}
|
||||||
|
ms, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, milestoneName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Milestone '%s' not found", milestoneName)
|
||||||
|
}
|
||||||
|
opts.Milestone = ms.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
return &opts, nil
|
||||||
|
}
|
||||||
|
|
||||||
// FieldsFlag generates a flag selecting printable fields.
|
// FieldsFlag generates a flag selecting printable fields.
|
||||||
// To retrieve the value, use GetFields()
|
// To retrieve the value, use GetFields()
|
||||||
func FieldsFlag(availableFields, defaultFields []string) *cli.StringFlag {
|
func FieldsFlag(availableFields, defaultFields []string) *cli.StringFlag {
|
||||||
|
|
|
@ -20,18 +20,7 @@ var CmdIssuesCreate = cli.Command{
|
||||||
Usage: "Create an issue on repository",
|
Usage: "Create an issue on repository",
|
||||||
Description: `Create an issue on repository`,
|
Description: `Create an issue on repository`,
|
||||||
Action: runIssuesCreate,
|
Action: runIssuesCreate,
|
||||||
Flags: append([]cli.Flag{
|
Flags: flags.IssuePREditFlags,
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "title",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Usage: "issue title to create",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "body",
|
|
||||||
Aliases: []string{"b"},
|
|
||||||
Usage: "issue body to create",
|
|
||||||
},
|
|
||||||
}, flags.LoginRepoFlags...),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runIssuesCreate(cmd *cli.Context) error {
|
func runIssuesCreate(cmd *cli.Context) error {
|
||||||
|
@ -42,11 +31,15 @@ func runIssuesCreate(cmd *cli.Context) error {
|
||||||
return interact.CreateIssue(ctx.Login, ctx.Owner, ctx.Repo)
|
return interact.CreateIssue(ctx.Login, ctx.Owner, ctx.Repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
opts, err := flags.GetIssuePREditFlags(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return task.CreateIssue(
|
return task.CreateIssue(
|
||||||
ctx.Login,
|
ctx.Login,
|
||||||
ctx.Owner,
|
ctx.Owner,
|
||||||
ctx.Repo,
|
ctx.Repo,
|
||||||
ctx.String("title"),
|
*opts,
|
||||||
ctx.String("body"),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,17 +30,7 @@ var CmdPullsCreate = cli.Command{
|
||||||
Aliases: []string{"b"},
|
Aliases: []string{"b"},
|
||||||
Usage: "Set base branch (default is default branch)",
|
Usage: "Set base branch (default is default branch)",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
}, flags.IssuePREditFlags...),
|
||||||
Name: "title",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Usage: "Set title of pull (default is head branch name)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "description",
|
|
||||||
Aliases: []string{"d"},
|
|
||||||
Usage: "Set body of new pull",
|
|
||||||
},
|
|
||||||
}, flags.AllDefaultFlags...),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPullsCreate(cmd *cli.Context) error {
|
func runPullsCreate(cmd *cli.Context) error {
|
||||||
|
@ -53,13 +43,17 @@ func runPullsCreate(cmd *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// else use args to create PR
|
// else use args to create PR
|
||||||
|
opts, err := flags.GetIssuePREditFlags(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return task.CreatePull(
|
return task.CreatePull(
|
||||||
ctx.Login,
|
ctx.Login,
|
||||||
ctx.Owner,
|
ctx.Owner,
|
||||||
ctx.Repo,
|
ctx.Repo,
|
||||||
ctx.String("base"),
|
ctx.String("base"),
|
||||||
ctx.String("head"),
|
ctx.String("head"),
|
||||||
ctx.String("title"),
|
opts,
|
||||||
ctx.String("description"),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package interact
|
package interact
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/config"
|
||||||
"code.gitea.io/tea/modules/task"
|
"code.gitea.io/tea/modules/task"
|
||||||
|
|
||||||
|
@ -13,31 +14,149 @@ import (
|
||||||
|
|
||||||
// CreateIssue interactively creates an issue
|
// CreateIssue interactively creates an issue
|
||||||
func CreateIssue(login *config.Login, owner, repo string) error {
|
func CreateIssue(login *config.Login, owner, repo string) error {
|
||||||
var title, description string
|
|
||||||
|
|
||||||
// owner, repo
|
|
||||||
owner, repo, err := promptRepoSlug(owner, repo)
|
owner, repo, err := promptRepoSlug(owner, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var opts gitea.CreateIssueOption
|
||||||
|
if err := promptIssueProperties(login, owner, repo, &opts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return task.CreateIssue(login, owner, repo, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func promptIssueProperties(login *config.Login, owner, repo string, o *gitea.CreateIssueOption) error {
|
||||||
|
var milestoneName string
|
||||||
|
var labels []string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
selectableChan := make(chan (issueSelectables), 1)
|
||||||
|
go fetchIssueSelectables(login, owner, repo, selectableChan)
|
||||||
|
|
||||||
// title
|
// title
|
||||||
promptOpts := survey.WithValidator(survey.Required)
|
promptOpts := survey.WithValidator(survey.Required)
|
||||||
promptI := &survey.Input{Message: "Issue title:"}
|
promptI := &survey.Input{Message: "Issue title:", Default: o.Title}
|
||||||
if err := survey.AskOne(promptI, &title, promptOpts); err != nil {
|
if err = survey.AskOne(promptI, &o.Title, promptOpts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// description
|
// description
|
||||||
promptM := &survey.Multiline{Message: "Issue description:"}
|
promptD := &survey.Multiline{Message: "Issue description:", Default: o.Body}
|
||||||
if err := survey.AskOne(promptM, &description); err != nil {
|
if err = survey.AskOne(promptD, &o.Body); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return task.CreateIssue(
|
// wait until selectables are fetched
|
||||||
login,
|
selectables := <-selectableChan
|
||||||
owner,
|
if selectables.Err != nil {
|
||||||
repo,
|
return selectables.Err
|
||||||
title,
|
}
|
||||||
description)
|
|
||||||
|
// skip remaining props if we don't have permission to set them
|
||||||
|
if !selectables.Repo.Permissions.Push {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assignees
|
||||||
|
if o.Assignees, err = promptMultiSelect("Assignees:", selectables.Collaborators, "[other]"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// milestone
|
||||||
|
if len(selectables.MilestoneList) != 0 {
|
||||||
|
if milestoneName, err = promptSelect("Milestone:", selectables.MilestoneList, "", "[none]"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.Milestone = selectables.MilestoneMap[milestoneName]
|
||||||
|
}
|
||||||
|
|
||||||
|
// labels
|
||||||
|
if len(selectables.LabelList) != 0 {
|
||||||
|
promptL := &survey.MultiSelect{Message: "Labels:", Options: selectables.LabelList, VimMode: true, Default: o.Labels}
|
||||||
|
if err := survey.AskOne(promptL, &labels); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.Labels = make([]int64, len(labels))
|
||||||
|
for i, l := range labels {
|
||||||
|
o.Labels[i] = selectables.LabelMap[l]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deadline
|
||||||
|
if o.Deadline, err = promptDatetime("Due date:"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type issueSelectables struct {
|
||||||
|
Repo *gitea.Repository
|
||||||
|
Collaborators []string
|
||||||
|
MilestoneList []string
|
||||||
|
MilestoneMap map[string]int64
|
||||||
|
LabelList []string
|
||||||
|
LabelMap map[string]int64
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchIssueSelectables(login *config.Login, owner, repo string, done chan issueSelectables) {
|
||||||
|
// TODO PERF make these calls concurrent
|
||||||
|
r := issueSelectables{}
|
||||||
|
c := login.Client()
|
||||||
|
|
||||||
|
r.Repo, _, r.Err = c.GetRepo(owner, repo)
|
||||||
|
if r.Err != nil {
|
||||||
|
done <- r
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// we can set the following properties only if we have write access to the repo
|
||||||
|
// so we fastpath this if not.
|
||||||
|
if !r.Repo.Permissions.Push {
|
||||||
|
done <- r
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: this should ideally be ListAssignees(), https://github.com/go-gitea/gitea/issues/14856
|
||||||
|
colabs, _, err := c.ListCollaborators(owner, repo, gitea.ListCollaboratorsOptions{})
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
done <- r
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.Collaborators = make([]string, len(colabs)+1)
|
||||||
|
r.Collaborators[0] = login.User
|
||||||
|
for i, u := range colabs {
|
||||||
|
r.Collaborators[i+1] = u.UserName
|
||||||
|
}
|
||||||
|
|
||||||
|
milestones, _, err := c.ListRepoMilestones(owner, repo, gitea.ListMilestoneOption{})
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
done <- r
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.MilestoneMap = make(map[string]int64)
|
||||||
|
r.MilestoneList = make([]string, len(milestones))
|
||||||
|
for i, m := range milestones {
|
||||||
|
r.MilestoneMap[m.Title] = m.ID
|
||||||
|
r.MilestoneList[i] = m.Title
|
||||||
|
}
|
||||||
|
|
||||||
|
labels, _, err := c.ListRepoLabels(owner, repo, gitea.ListLabelsOptions{})
|
||||||
|
if err != nil {
|
||||||
|
r.Err = err
|
||||||
|
done <- r
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.LabelMap = make(map[string]int64)
|
||||||
|
r.LabelList = make([]string, len(labels))
|
||||||
|
for i, l := range labels {
|
||||||
|
r.LabelMap[l.Name] = l.ID
|
||||||
|
r.LabelList[i] = l.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
done <- r
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
package interact
|
package interact
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/config"
|
||||||
|
@ -13,12 +12,11 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/araddon/dateparse"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateMilestone interactively creates a milestone
|
// CreateMilestone interactively creates a milestone
|
||||||
func CreateMilestone(login *config.Login, owner, repo string) error {
|
func CreateMilestone(login *config.Login, owner, repo string) error {
|
||||||
var title, description, dueDate string
|
var title, description string
|
||||||
var deadline *time.Time
|
var deadline *time.Time
|
||||||
|
|
||||||
// owner, repo
|
// owner, repo
|
||||||
|
@ -41,28 +39,7 @@ func CreateMilestone(login *config.Login, owner, repo string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// deadline
|
// deadline
|
||||||
promptI = &survey.Input{Message: "Milestone deadline [no due date]:"}
|
if deadline, err = promptDatetime("Milestone deadline:"); err != nil {
|
||||||
err = survey.AskOne(
|
|
||||||
promptI,
|
|
||||||
&dueDate,
|
|
||||||
survey.WithValidator(func(input interface{}) error {
|
|
||||||
if str, ok := input.(string); ok {
|
|
||||||
if len(str) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
t, err := dateparse.ParseAny(str)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
deadline = &t
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("invalid result type")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,11 @@ package interact
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
"github.com/araddon/dateparse"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PromptMultiline runs a textfield-style prompt and blocks until input was made.
|
// PromptMultiline runs a textfield-style prompt and blocks until input was made.
|
||||||
|
@ -27,9 +30,10 @@ func PromptPassword(name string) (pass string, err error) {
|
||||||
// promptRepoSlug interactively prompts for a Gitea repository or returns the current one
|
// promptRepoSlug interactively prompts for a Gitea repository or returns the current one
|
||||||
func promptRepoSlug(defaultOwner, defaultRepo string) (owner, repo string, err error) {
|
func promptRepoSlug(defaultOwner, defaultRepo string) (owner, repo string, err error) {
|
||||||
prompt := "Target repo:"
|
prompt := "Target repo:"
|
||||||
|
defaultVal := ""
|
||||||
required := true
|
required := true
|
||||||
if len(defaultOwner) != 0 && len(defaultRepo) != 0 {
|
if len(defaultOwner) != 0 && len(defaultRepo) != 0 {
|
||||||
prompt = fmt.Sprintf("Target repo [%s/%s]:", defaultOwner, defaultRepo)
|
defaultVal = fmt.Sprintf("%s/%s", defaultOwner, defaultRepo)
|
||||||
required = false
|
required = false
|
||||||
}
|
}
|
||||||
var repoSlug string
|
var repoSlug string
|
||||||
|
@ -38,7 +42,10 @@ func promptRepoSlug(defaultOwner, defaultRepo string) (owner, repo string, err e
|
||||||
repo = defaultRepo
|
repo = defaultRepo
|
||||||
|
|
||||||
err = survey.AskOne(
|
err = survey.AskOne(
|
||||||
&survey.Input{Message: prompt},
|
&survey.Input{
|
||||||
|
Message: prompt,
|
||||||
|
Default: defaultVal,
|
||||||
|
},
|
||||||
&repoSlug,
|
&repoSlug,
|
||||||
survey.WithValidator(func(input interface{}) error {
|
survey.WithValidator(func(input interface{}) error {
|
||||||
if str, ok := input.(string); ok {
|
if str, ok := input.(string); ok {
|
||||||
|
@ -63,3 +70,96 @@ func promptRepoSlug(defaultOwner, defaultRepo string) (owner, repo string, err e
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// promptDatetime prompts for a date or datetime string.
|
||||||
|
// Supports all formats understood by araddon/dateparse.
|
||||||
|
func promptDatetime(prompt string) (val *time.Time, err error) {
|
||||||
|
var input string
|
||||||
|
err = survey.AskOne(
|
||||||
|
&survey.Input{Message: prompt},
|
||||||
|
&input,
|
||||||
|
survey.WithValidator(func(input interface{}) error {
|
||||||
|
if str, ok := input.(string); ok {
|
||||||
|
if len(str) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t, err := dateparse.ParseAny(str)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
val = &t
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("invalid result type")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// promptSelect creates a generic multiselect prompt, with processing of custom values.
|
||||||
|
func promptMultiSelect(prompt string, options []string, customVal string) ([]string, error) {
|
||||||
|
var selection []string
|
||||||
|
promptA := &survey.MultiSelect{
|
||||||
|
Message: prompt,
|
||||||
|
Options: makeSelectOpts(options, customVal, ""),
|
||||||
|
VimMode: true,
|
||||||
|
}
|
||||||
|
if err := survey.AskOne(promptA, &selection); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return promptCustomVal(prompt, customVal, selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
// promptSelect creates a generic select prompt, with processing of custom values or none-option.
|
||||||
|
func promptSelect(prompt string, options []string, customVal, noneVal string) (string, error) {
|
||||||
|
var selection string
|
||||||
|
promptA := &survey.Select{
|
||||||
|
Message: prompt,
|
||||||
|
Options: makeSelectOpts(options, customVal, noneVal),
|
||||||
|
VimMode: true,
|
||||||
|
Default: noneVal,
|
||||||
|
}
|
||||||
|
if err := survey.AskOne(promptA, &selection); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if noneVal != "" && selection == noneVal {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
if customVal != "" {
|
||||||
|
sel, err := promptCustomVal(prompt, customVal, []string{selection})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
selection = sel[0]
|
||||||
|
}
|
||||||
|
return selection, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeSelectOpts adds cusotmVal & noneVal to opts if set.
|
||||||
|
func makeSelectOpts(opts []string, customVal, noneVal string) []string {
|
||||||
|
if customVal != "" {
|
||||||
|
opts = append(opts, customVal)
|
||||||
|
}
|
||||||
|
if noneVal != "" {
|
||||||
|
opts = append(opts, noneVal)
|
||||||
|
}
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// promptCustomVal checks if customVal is present in selection, and prompts
|
||||||
|
// for custom input to add to the selection instead.
|
||||||
|
func promptCustomVal(prompt, customVal string, selection []string) ([]string, error) {
|
||||||
|
// check for custom value & prompt again with text input
|
||||||
|
// HACK until https://github.com/AlecAivazis/survey/issues/339 is implemented
|
||||||
|
if otherIndex := utils.IndexOf(selection, customVal); otherIndex != -1 {
|
||||||
|
var customAssignees string
|
||||||
|
promptA := &survey.Input{Message: prompt, Help: "comma separated list"}
|
||||||
|
if err := survey.AskOne(promptA, &customAssignees); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
selection = append(selection[:otherIndex], selection[otherIndex+1:]...)
|
||||||
|
selection = append(selection, strings.Split(customAssignees, ",")...)
|
||||||
|
}
|
||||||
|
return selection, nil
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package interact
|
package interact
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/config"
|
||||||
"code.gitea.io/tea/modules/git"
|
"code.gitea.io/tea/modules/git"
|
||||||
"code.gitea.io/tea/modules/task"
|
"code.gitea.io/tea/modules/task"
|
||||||
|
@ -14,7 +15,7 @@ import (
|
||||||
|
|
||||||
// CreatePull interactively creates a PR
|
// CreatePull interactively creates a PR
|
||||||
func CreatePull(login *config.Login, owner, repo string) error {
|
func CreatePull(login *config.Login, owner, repo string) error {
|
||||||
var base, head, title, description string
|
var base, head string
|
||||||
|
|
||||||
// owner, repo
|
// owner, repo
|
||||||
owner, repo, err := promptRepoSlug(owner, repo)
|
owner, repo, err := promptRepoSlug(owner, repo)
|
||||||
|
@ -23,17 +24,14 @@ func CreatePull(login *config.Login, owner, repo string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// base
|
// base
|
||||||
baseBranch, err := task.GetDefaultPRBase(login, owner, repo)
|
base, err = task.GetDefaultPRBase(login, owner, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
promptI := &survey.Input{Message: "Target branch [" + baseBranch + "]:"}
|
promptI := &survey.Input{Message: "Target branch:", Default: base}
|
||||||
if err := survey.AskOne(promptI, &base); err != nil {
|
if err := survey.AskOne(promptI, &base); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(base) == 0 {
|
|
||||||
base = baseBranch
|
|
||||||
}
|
|
||||||
|
|
||||||
// head
|
// head
|
||||||
localRepo, err := git.RepoForWorkdir()
|
localRepo, err := git.RepoForWorkdir()
|
||||||
|
@ -45,38 +43,19 @@ func CreatePull(login *config.Login, owner, repo string) error {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
promptOpts = nil
|
promptOpts = nil
|
||||||
}
|
}
|
||||||
var headOwnerInput, headBranchInput string
|
promptI = &survey.Input{Message: "Source repo owner:", Default: headOwner}
|
||||||
promptI = &survey.Input{Message: "Source repo owner [" + headOwner + "]:"}
|
if err := survey.AskOne(promptI, &headOwner); err != nil {
|
||||||
if err := survey.AskOne(promptI, &headOwnerInput); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(headOwnerInput) != 0 {
|
promptI = &survey.Input{Message: "Source branch:", Default: headBranch}
|
||||||
headOwner = headOwnerInput
|
if err := survey.AskOne(promptI, &headBranch, promptOpts); err != nil {
|
||||||
}
|
|
||||||
promptI = &survey.Input{Message: "Source branch [" + headBranch + "]:"}
|
|
||||||
if err := survey.AskOne(promptI, &headBranchInput, promptOpts); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(headBranchInput) != 0 {
|
|
||||||
headBranch = headBranchInput
|
|
||||||
}
|
|
||||||
|
|
||||||
head = task.GetHeadSpec(headOwner, headBranch, owner)
|
head = task.GetHeadSpec(headOwner, headBranch, owner)
|
||||||
|
|
||||||
// title
|
opts := gitea.CreateIssueOption{Title: task.GetDefaultPRTitle(head)}
|
||||||
title = task.GetDefaultPRTitle(head)
|
if err = promptIssueProperties(login, owner, repo, &opts); err != nil {
|
||||||
promptOpts = survey.WithValidator(survey.Required)
|
|
||||||
if len(title) != 0 {
|
|
||||||
promptOpts = nil
|
|
||||||
}
|
|
||||||
promptI = &survey.Input{Message: "PR title [" + title + "]:"}
|
|
||||||
if err := survey.AskOne(promptI, &title, promptOpts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// description
|
|
||||||
promptM := &survey.Multiline{Message: "PR description:"}
|
|
||||||
if err := survey.AskOne(promptM, &description); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,6 +65,5 @@ func CreatePull(login *config.Login, owner, repo string) error {
|
||||||
repo,
|
repo,
|
||||||
base,
|
base,
|
||||||
head,
|
head,
|
||||||
title,
|
&opts)
|
||||||
description)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,25 +13,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateIssue creates an issue in the given repo and prints the result
|
// CreateIssue creates an issue in the given repo and prints the result
|
||||||
func CreateIssue(login *config.Login, repoOwner, repoName, title, description string) error {
|
func CreateIssue(login *config.Login, repoOwner, repoName string, opts gitea.CreateIssueOption) error {
|
||||||
|
|
||||||
// title is required
|
// title is required
|
||||||
if len(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, gitea.CreateIssueOption{
|
issue, _, err := login.Client().CreateIssue(repoOwner, repoName, opts)
|
||||||
Title: title,
|
|
||||||
Body: description,
|
|
||||||
// TODO:
|
|
||||||
//Assignee string `json:"assignee"`
|
|
||||||
//Assignees []string `json:"assignees"`
|
|
||||||
//Deadline *time.Time `json:"due_date"`
|
|
||||||
//Milestone int64 `json:"milestone"`
|
|
||||||
//Labels []int64 `json:"labels"`
|
|
||||||
//Closed bool `json:"closed"`
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not create issue: %s", err)
|
return fmt.Errorf("could not create issue: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright 2020 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 (
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResolveLabelNames returns a list of label IDs for a given list of label names
|
||||||
|
func ResolveLabelNames(client *gitea.Client, owner, repo string, labelNames []string) ([]int64, error) {
|
||||||
|
labelIDs := make([]int64, len(labelNames))
|
||||||
|
labels, _, err := client.ListRepoLabels(owner, repo, gitea.ListLabelsOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, l := range labels {
|
||||||
|
if utils.Contains(labelNames, l.Name) {
|
||||||
|
labelIDs = append(labelIDs, l.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return labelIDs, nil
|
||||||
|
}
|
|
@ -16,8 +16,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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(login *config.Login, repoOwner, repoName, base, head, title, description string) error {
|
func CreatePull(login *config.Login, repoOwner, repoName, base, head string, opts *gitea.CreateIssueOption) error {
|
||||||
|
|
||||||
// open local git repo
|
// open local git repo
|
||||||
localRepo, err := local_git.RepoForWorkdir()
|
localRepo, err := local_git.RepoForWorkdir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -48,19 +47,23 @@ func CreatePull(login *config.Login, repoOwner, repoName, base, head, title, des
|
||||||
}
|
}
|
||||||
|
|
||||||
// default is head branch name
|
// default is head branch name
|
||||||
if len(title) == 0 {
|
if len(opts.Title) == 0 {
|
||||||
title = GetDefaultPRTitle(head)
|
opts.Title = GetDefaultPRTitle(head)
|
||||||
}
|
}
|
||||||
// title is required
|
// title is required
|
||||||
if len(title) == 0 {
|
if len(opts.Title) == 0 {
|
||||||
return fmt.Errorf("Title is required")
|
return fmt.Errorf("Title is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
pr, _, err := login.Client().CreatePullRequest(repoOwner, repoName, gitea.CreatePullRequestOption{
|
pr, _, err := login.Client().CreatePullRequest(repoOwner, repoName, gitea.CreatePullRequestOption{
|
||||||
Head: head,
|
Head: head,
|
||||||
Base: base,
|
Base: base,
|
||||||
Title: title,
|
Title: opts.Title,
|
||||||
Body: description,
|
Body: opts.Body,
|
||||||
|
Assignees: opts.Assignees,
|
||||||
|
Labels: opts.Labels,
|
||||||
|
Milestone: opts.Milestone,
|
||||||
|
Deadline: opts.Deadline,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -6,11 +6,15 @@ package utils
|
||||||
|
|
||||||
// Contains checks containment
|
// Contains checks containment
|
||||||
func Contains(haystack []string, needle string) bool {
|
func Contains(haystack []string, needle string) bool {
|
||||||
for _, s := range haystack {
|
return IndexOf(haystack, needle) != -1
|
||||||
if s == needle {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
// IndexOf returns the index of first occurrence of needle in haystack
|
||||||
|
func IndexOf(haystack []string, needle string) int {
|
||||||
|
for i, s := range haystack {
|
||||||
|
if s == needle {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue