mirror of
https://gitea.com/gitea/tea.git
synced 2024-11-21 18:11:36 +01:00
Add tea issue edit
(#506)
fixes #229 fixes #502 interactive mode will be in a follow up Co-authored-by: Norwin <git@nroo.de> Co-authored-by: 6543 <6543@obermui.de> Reviewed-on: https://gitea.com/gitea/tea/pulls/506 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
54b535a527
commit
2a8c1daa67
@ -7,6 +7,7 @@ package flags
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"code.gitea.io/tea/modules/context"
|
"code.gitea.io/tea/modules/context"
|
||||||
@ -84,8 +85,8 @@ var IssueListingFlags = append([]cli.Flag{
|
|||||||
&PaginationLimitFlag,
|
&PaginationLimitFlag,
|
||||||
}, AllDefaultFlags...)
|
}, AllDefaultFlags...)
|
||||||
|
|
||||||
// IssuePREditFlags defines flags for properties of issues and PRs
|
// issuePRFlags defines shared flags between flags IssuePRCreateFlags and IssuePREditFlags
|
||||||
var IssuePREditFlags = append([]cli.Flag{
|
var issuePRFlags = append([]cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "title",
|
Name: "title",
|
||||||
Aliases: []string{"t"},
|
Aliases: []string{"t"},
|
||||||
@ -94,6 +95,25 @@ var IssuePREditFlags = append([]cli.Flag{
|
|||||||
Name: "description",
|
Name: "description",
|
||||||
Aliases: []string{"d"},
|
Aliases: []string{"d"},
|
||||||
},
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "referenced-version",
|
||||||
|
Aliases: []string{"v"},
|
||||||
|
Usage: "commit-hash or tag name to assign",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "milestone",
|
||||||
|
Aliases: []string{"m"},
|
||||||
|
Usage: "Milestone to assign",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "deadline",
|
||||||
|
Aliases: []string{"D"},
|
||||||
|
Usage: "Deadline timestamp to assign",
|
||||||
|
},
|
||||||
|
}, LoginRepoFlags...)
|
||||||
|
|
||||||
|
// IssuePRCreateFlags defines flags for creation of issues and PRs
|
||||||
|
var IssuePRCreateFlags = append([]cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "assignees",
|
Name: "assignees",
|
||||||
Aliases: []string{"a"},
|
Aliases: []string{"a"},
|
||||||
@ -104,20 +124,10 @@ var IssuePREditFlags = append([]cli.Flag{
|
|||||||
Aliases: []string{"L"},
|
Aliases: []string{"L"},
|
||||||
Usage: "Comma-separated list of labels to assign",
|
Usage: "Comma-separated list of labels to assign",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
}, issuePRFlags...)
|
||||||
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
|
// GetIssuePRCreateFlags parses all IssuePREditFlags
|
||||||
func GetIssuePREditFlags(ctx *context.TeaContext) (*gitea.CreateIssueOption, error) {
|
func GetIssuePRCreateFlags(ctx *context.TeaContext) (*gitea.CreateIssueOption, error) {
|
||||||
opts := gitea.CreateIssueOption{
|
opts := gitea.CreateIssueOption{
|
||||||
Title: ctx.String("title"),
|
Title: ctx.String("title"),
|
||||||
Body: ctx.String("description"),
|
Body: ctx.String("description"),
|
||||||
@ -159,3 +169,67 @@ func GetIssuePREditFlags(ctx *context.TeaContext) (*gitea.CreateIssueOption, err
|
|||||||
|
|
||||||
return &opts, nil
|
return &opts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IssuePREditFlags defines flags for editing properties of issues and PRs
|
||||||
|
var IssuePREditFlags = append([]cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "add-assignees",
|
||||||
|
Aliases: []string{"a"},
|
||||||
|
Usage: "Comma-separated list of usernames to assign",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "add-labels",
|
||||||
|
Aliases: []string{"L"},
|
||||||
|
Usage: "Comma-separated list of labels to assign. Takes precedence over --remove-labels",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "remove-labels",
|
||||||
|
Usage: "Comma-separated list of labels to remove",
|
||||||
|
},
|
||||||
|
}, issuePRFlags...)
|
||||||
|
|
||||||
|
// GetIssuePREditFlags parses all IssuePREditFlags
|
||||||
|
func GetIssuePREditFlags(ctx *context.TeaContext) (*task.EditIssueOption, error) {
|
||||||
|
opts := task.EditIssueOption{}
|
||||||
|
if ctx.IsSet("title") {
|
||||||
|
val := ctx.String("title")
|
||||||
|
opts.Title = &val
|
||||||
|
}
|
||||||
|
if ctx.IsSet("description") {
|
||||||
|
val := ctx.String("description")
|
||||||
|
opts.Body = &val
|
||||||
|
}
|
||||||
|
if ctx.IsSet("referenced-version") {
|
||||||
|
val := ctx.String("referenced-version")
|
||||||
|
opts.Ref = &val
|
||||||
|
}
|
||||||
|
if ctx.IsSet("milestone") {
|
||||||
|
val := ctx.String("milestone")
|
||||||
|
opts.Milestone = &val
|
||||||
|
}
|
||||||
|
if ctx.IsSet("deadline") {
|
||||||
|
date := ctx.String("deadline")
|
||||||
|
if date == "" {
|
||||||
|
opts.Deadline = &time.Time{}
|
||||||
|
} else {
|
||||||
|
t, err := dateparse.ParseAny(date)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
opts.Deadline = &t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ctx.IsSet("add-assignees") {
|
||||||
|
val := ctx.String("add-assignees")
|
||||||
|
opts.AddAssignees = strings.Split(val, ",")
|
||||||
|
}
|
||||||
|
if ctx.IsSet("add-labels") {
|
||||||
|
val := ctx.String("add-labels")
|
||||||
|
opts.AddLabels = strings.Split(val, ",")
|
||||||
|
}
|
||||||
|
if ctx.IsSet("remove-labels") {
|
||||||
|
val := ctx.String("remove-labels")
|
||||||
|
opts.RemoveLabels = strings.Split(val, ",")
|
||||||
|
}
|
||||||
|
return &opts, nil
|
||||||
|
}
|
||||||
|
@ -28,6 +28,7 @@ var CmdIssues = cli.Command{
|
|||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
&issues.CmdIssuesList,
|
&issues.CmdIssuesList,
|
||||||
&issues.CmdIssuesCreate,
|
&issues.CmdIssuesCreate,
|
||||||
|
&issues.CmdIssuesEdit,
|
||||||
&issues.CmdIssuesReopen,
|
&issues.CmdIssuesReopen,
|
||||||
&issues.CmdIssuesClose,
|
&issues.CmdIssuesClose,
|
||||||
},
|
},
|
||||||
|
@ -21,7 +21,7 @@ var CmdIssuesCreate = cli.Command{
|
|||||||
Description: `Create an issue on repository`,
|
Description: `Create an issue on repository`,
|
||||||
ArgsUsage: " ", // command does not accept arguments
|
ArgsUsage: " ", // command does not accept arguments
|
||||||
Action: runIssuesCreate,
|
Action: runIssuesCreate,
|
||||||
Flags: flags.IssuePREditFlags,
|
Flags: flags.IssuePRCreateFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runIssuesCreate(cmd *cli.Context) error {
|
func runIssuesCreate(cmd *cli.Context) error {
|
||||||
@ -32,7 +32,7 @@ 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)
|
opts, err := flags.GetIssuePRCreateFlags(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
63
cmd/issues/edit.go
Normal file
63
cmd/issues/edit.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2022 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 issues
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/context"
|
||||||
|
"code.gitea.io/tea/modules/print"
|
||||||
|
"code.gitea.io/tea/modules/task"
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdIssuesEdit is the subcommand of issues to edit issues
|
||||||
|
var CmdIssuesEdit = cli.Command{
|
||||||
|
Name: "edit",
|
||||||
|
Aliases: []string{"e"},
|
||||||
|
Usage: "Edit one or more issues",
|
||||||
|
Description: `Edit one or more issues. To unset a property again,
|
||||||
|
use an empty string (eg. --milestone "").`,
|
||||||
|
ArgsUsage: "<idx> [<idx>...]",
|
||||||
|
Action: runIssuesEdit,
|
||||||
|
Flags: flags.IssuePREditFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runIssuesEdit(cmd *cli.Context) error {
|
||||||
|
ctx := context.InitCommand(cmd)
|
||||||
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
|
||||||
|
if !cmd.Args().Present() {
|
||||||
|
return fmt.Errorf("must specify at least one issue index")
|
||||||
|
}
|
||||||
|
|
||||||
|
opts, err := flags.GetIssuePREditFlags(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
indices, err := utils.ArgsToIndices(ctx.Args().Slice())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := ctx.Login.Client()
|
||||||
|
for _, opts.Index = range indices {
|
||||||
|
issue, err := task.EditIssue(ctx, client, *opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ctx.Args().Len() > 1 {
|
||||||
|
fmt.Println(issue.HTMLURL)
|
||||||
|
} else {
|
||||||
|
print.IssueDetails(issue, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -36,7 +36,7 @@ var CmdPullsCreate = cli.Command{
|
|||||||
Usage: "Enable maintainers to push to the base branch of created pull",
|
Usage: "Enable maintainers to push to the base branch of created pull",
|
||||||
Value: true,
|
Value: true,
|
||||||
},
|
},
|
||||||
}, flags.IssuePREditFlags...),
|
}, flags.IssuePRCreateFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPullsCreate(cmd *cli.Context) error {
|
func runPullsCreate(cmd *cli.Context) error {
|
||||||
@ -48,7 +48,7 @@ func runPullsCreate(cmd *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// else use args to create PR
|
// else use args to create PR
|
||||||
opts, err := flags.GetIssuePREditFlags(ctx)
|
opts, err := flags.GetIssuePRCreateFlags(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
135
modules/task/issue_edit.go
Normal file
135
modules/task/issue_edit.go
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
// Copyright 2022 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 (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"code.gitea.io/tea/modules/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EditIssueOption wraps around gitea.EditIssueOption which has bad & incosistent semantics.
|
||||||
|
type EditIssueOption struct {
|
||||||
|
Index int64
|
||||||
|
Title *string
|
||||||
|
Body *string
|
||||||
|
Ref *string
|
||||||
|
Milestone *string
|
||||||
|
Deadline *time.Time
|
||||||
|
AddLabels []string
|
||||||
|
RemoveLabels []string
|
||||||
|
AddAssignees []string
|
||||||
|
// RemoveAssignees []string // NOTE: with the current go-sdk, clearing assignees is not possible.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalizes the options into parameters that can be passed to the sdk.
|
||||||
|
// the returned value will be nil, when no change to this part of the issue is requested.
|
||||||
|
func (o EditIssueOption) toSdkOptions(ctx *context.TeaContext, client *gitea.Client) (*gitea.EditIssueOption, *gitea.IssueLabelsOption, *gitea.IssueLabelsOption, error) {
|
||||||
|
// labels have a separate API call, so they get their own options.
|
||||||
|
var addLabelOpts, rmLabelOpts *gitea.IssueLabelsOption
|
||||||
|
if o.AddLabels != nil && len(o.AddLabels) != 0 {
|
||||||
|
ids, err := ResolveLabelNames(client, ctx.Owner, ctx.Repo, o.AddLabels)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
addLabelOpts = &gitea.IssueLabelsOption{Labels: ids}
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.RemoveLabels != nil && len(o.RemoveLabels) != 0 {
|
||||||
|
ids, err := ResolveLabelNames(client, ctx.Owner, ctx.Repo, o.RemoveLabels)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
rmLabelOpts = &gitea.IssueLabelsOption{Labels: ids}
|
||||||
|
}
|
||||||
|
|
||||||
|
issueOpts := gitea.EditIssueOption{}
|
||||||
|
var issueOptsDirty bool
|
||||||
|
if o.Title != nil {
|
||||||
|
issueOpts.Title = *o.Title
|
||||||
|
issueOptsDirty = true
|
||||||
|
}
|
||||||
|
if o.Body != nil {
|
||||||
|
issueOpts.Body = o.Body
|
||||||
|
issueOptsDirty = true
|
||||||
|
}
|
||||||
|
if o.Ref != nil {
|
||||||
|
issueOpts.Ref = o.Ref
|
||||||
|
issueOptsDirty = true
|
||||||
|
}
|
||||||
|
if o.Milestone != nil {
|
||||||
|
if *o.Milestone == "" {
|
||||||
|
issueOpts.Milestone = gitea.OptionalInt64(0)
|
||||||
|
} else {
|
||||||
|
ms, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, *o.Milestone)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("Milestone '%s' not found", *o.Milestone)
|
||||||
|
}
|
||||||
|
issueOpts.Milestone = &ms.ID
|
||||||
|
}
|
||||||
|
issueOptsDirty = true
|
||||||
|
}
|
||||||
|
if o.Deadline != nil {
|
||||||
|
issueOpts.Deadline = o.Deadline
|
||||||
|
issueOptsDirty = true
|
||||||
|
if o.Deadline.IsZero() {
|
||||||
|
issueOpts.RemoveDeadline = gitea.OptionalBool(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if o.AddAssignees != nil && len(o.AddAssignees) != 0 {
|
||||||
|
issueOpts.Assignees = o.AddAssignees
|
||||||
|
issueOptsDirty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if issueOptsDirty {
|
||||||
|
return &issueOpts, addLabelOpts, rmLabelOpts, nil
|
||||||
|
}
|
||||||
|
return nil, addLabelOpts, rmLabelOpts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditIssue edits an issue and returns the updated issue.
|
||||||
|
func EditIssue(ctx *context.TeaContext, client *gitea.Client, opts EditIssueOption) (*gitea.Issue, error) {
|
||||||
|
if client == nil {
|
||||||
|
client = ctx.Login.Client()
|
||||||
|
}
|
||||||
|
|
||||||
|
issueOpts, addLabelOpts, rmLabelOpts, err := opts.toSdkOptions(ctx, client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rmLabelOpts != nil {
|
||||||
|
// NOTE: as of 1.17, there is no API to remove multiple labels at once.
|
||||||
|
for _, id := range rmLabelOpts.Labels {
|
||||||
|
_, err := client.DeleteIssueLabel(ctx.Owner, ctx.Repo, opts.Index, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not remove labels: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if addLabelOpts != nil {
|
||||||
|
_, _, err := client.AddIssueLabels(ctx.Owner, ctx.Repo, opts.Index, *addLabelOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not add labels: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var issue *gitea.Issue
|
||||||
|
if issueOpts != nil {
|
||||||
|
issue, _, err = client.EditIssue(ctx.Owner, ctx.Repo, opts.Index, *issueOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not edit issue: %s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
issue, _, err = client.GetIssue(ctx.Owner, ctx.Repo, opts.Index)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not get issue: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return issue, nil
|
||||||
|
}
|
@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
// ResolveLabelNames returns a list of label IDs for a given list of label names
|
// 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) {
|
func ResolveLabelNames(client *gitea.Client, owner, repo string, labelNames []string) ([]int64, error) {
|
||||||
labelIDs := make([]int64, len(labelNames))
|
labelIDs := make([]int64, 0, len(labelNames))
|
||||||
labels, _, err := client.ListRepoLabels(owner, repo, gitea.ListLabelsOptions{
|
labels, _, err := client.ListRepoLabels(owner, repo, gitea.ListLabelsOptions{
|
||||||
ListOptions: gitea.ListOptions{Page: -1},
|
ListOptions: gitea.ListOptions{Page: -1},
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user