mirror of
				https://gitea.com/gitea/tea.git
				synced 2025-10-31 01:05:26 +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:
		| @@ -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}, | ||||||
| 	}) | 	}) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Norwin
					Norwin