github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/issue/edit/edit.go (about) 1 package edit 2 3 import ( 4 "fmt" 5 "net/http" 6 7 "github.com/MakeNowJust/heredoc" 8 "github.com/ungtb10d/cli/v2/api" 9 "github.com/ungtb10d/cli/v2/internal/ghrepo" 10 shared "github.com/ungtb10d/cli/v2/pkg/cmd/issue/shared" 11 prShared "github.com/ungtb10d/cli/v2/pkg/cmd/pr/shared" 12 "github.com/ungtb10d/cli/v2/pkg/cmdutil" 13 "github.com/ungtb10d/cli/v2/pkg/iostreams" 14 "github.com/spf13/cobra" 15 ) 16 17 type EditOptions struct { 18 HttpClient func() (*http.Client, error) 19 IO *iostreams.IOStreams 20 BaseRepo func() (ghrepo.Interface, error) 21 22 DetermineEditor func() (string, error) 23 FieldsToEditSurvey func(*prShared.Editable) error 24 EditFieldsSurvey func(*prShared.Editable, string) error 25 FetchOptions func(*api.Client, ghrepo.Interface, *prShared.Editable) error 26 27 SelectorArg string 28 Interactive bool 29 30 prShared.Editable 31 } 32 33 func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Command { 34 opts := &EditOptions{ 35 IO: f.IOStreams, 36 HttpClient: f.HttpClient, 37 DetermineEditor: func() (string, error) { return cmdutil.DetermineEditor(f.Config) }, 38 FieldsToEditSurvey: prShared.FieldsToEditSurvey, 39 EditFieldsSurvey: prShared.EditFieldsSurvey, 40 FetchOptions: prShared.FetchOptions, 41 } 42 43 var bodyFile string 44 45 cmd := &cobra.Command{ 46 Use: "edit {<number> | <url>}", 47 Short: "Edit an issue", 48 Example: heredoc.Doc(` 49 $ gh issue edit 23 --title "I found a bug" --body "Nothing works" 50 $ gh issue edit 23 --add-label "bug,help wanted" --remove-label "core" 51 $ gh issue edit 23 --add-assignee "@me" --remove-assignee monalisa,hubot 52 $ gh issue edit 23 --add-project "Roadmap" --remove-project v1,v2 53 $ gh issue edit 23 --milestone "Version 1" 54 $ gh issue edit 23 --body-file body.txt 55 `), 56 Args: cobra.ExactArgs(1), 57 RunE: func(cmd *cobra.Command, args []string) error { 58 // support `-R, --repo` override 59 opts.BaseRepo = f.BaseRepo 60 61 opts.SelectorArg = args[0] 62 63 flags := cmd.Flags() 64 65 bodyProvided := flags.Changed("body") 66 bodyFileProvided := bodyFile != "" 67 68 if err := cmdutil.MutuallyExclusive( 69 "specify only one of `--body` or `--body-file`", 70 bodyProvided, 71 bodyFileProvided, 72 ); err != nil { 73 return err 74 } 75 if bodyProvided || bodyFileProvided { 76 opts.Editable.Body.Edited = true 77 if bodyFileProvided { 78 b, err := cmdutil.ReadFile(bodyFile, opts.IO.In) 79 if err != nil { 80 return err 81 } 82 opts.Editable.Body.Value = string(b) 83 } 84 } 85 86 if flags.Changed("title") { 87 opts.Editable.Title.Edited = true 88 } 89 if flags.Changed("add-assignee") || flags.Changed("remove-assignee") { 90 opts.Editable.Assignees.Edited = true 91 } 92 if flags.Changed("add-label") || flags.Changed("remove-label") { 93 opts.Editable.Labels.Edited = true 94 } 95 if flags.Changed("add-project") || flags.Changed("remove-project") { 96 opts.Editable.Projects.Edited = true 97 } 98 if flags.Changed("milestone") { 99 opts.Editable.Milestone.Edited = true 100 } 101 102 if !opts.Editable.Dirty() { 103 opts.Interactive = true 104 } 105 106 if opts.Interactive && !opts.IO.CanPrompt() { 107 return cmdutil.FlagErrorf("field to edit flag required when not running interactively") 108 } 109 110 if runF != nil { 111 return runF(opts) 112 } 113 114 return editRun(opts) 115 }, 116 } 117 118 cmd.Flags().StringVarP(&opts.Editable.Title.Value, "title", "t", "", "Set the new title.") 119 cmd.Flags().StringVarP(&opts.Editable.Body.Value, "body", "b", "", "Set the new body.") 120 cmd.Flags().StringVarP(&bodyFile, "body-file", "F", "", "Read body text from `file` (use \"-\" to read from standard input)") 121 cmd.Flags().StringSliceVar(&opts.Editable.Assignees.Add, "add-assignee", nil, "Add assigned users by their `login`. Use \"@me\" to assign yourself.") 122 cmd.Flags().StringSliceVar(&opts.Editable.Assignees.Remove, "remove-assignee", nil, "Remove assigned users by their `login`. Use \"@me\" to unassign yourself.") 123 cmd.Flags().StringSliceVar(&opts.Editable.Labels.Add, "add-label", nil, "Add labels by `name`") 124 cmd.Flags().StringSliceVar(&opts.Editable.Labels.Remove, "remove-label", nil, "Remove labels by `name`") 125 cmd.Flags().StringSliceVar(&opts.Editable.Projects.Add, "add-project", nil, "Add the issue to projects by `name`") 126 cmd.Flags().StringSliceVar(&opts.Editable.Projects.Remove, "remove-project", nil, "Remove the issue from projects by `name`") 127 cmd.Flags().StringVarP(&opts.Editable.Milestone.Value, "milestone", "m", "", "Edit the milestone the issue belongs to by `name`") 128 129 return cmd 130 } 131 132 func editRun(opts *EditOptions) error { 133 httpClient, err := opts.HttpClient() 134 if err != nil { 135 return err 136 } 137 138 editable := opts.Editable 139 lookupFields := []string{"id", "number", "title", "body", "url"} 140 if opts.Interactive || editable.Assignees.Edited { 141 lookupFields = append(lookupFields, "assignees") 142 } 143 if opts.Interactive || editable.Labels.Edited { 144 lookupFields = append(lookupFields, "labels") 145 } 146 if opts.Interactive || editable.Projects.Edited { 147 lookupFields = append(lookupFields, "projectCards") 148 } 149 if opts.Interactive || editable.Milestone.Edited { 150 lookupFields = append(lookupFields, "milestone") 151 } 152 153 issue, repo, err := shared.IssueFromArgWithFields(httpClient, opts.BaseRepo, opts.SelectorArg, lookupFields) 154 if err != nil { 155 return err 156 } 157 158 editable.Title.Default = issue.Title 159 editable.Body.Default = issue.Body 160 editable.Assignees.Default = issue.Assignees.Logins() 161 editable.Labels.Default = issue.Labels.Names() 162 editable.Projects.Default = issue.ProjectCards.ProjectNames() 163 if issue.Milestone != nil { 164 editable.Milestone.Default = issue.Milestone.Title 165 } 166 167 if opts.Interactive { 168 err = opts.FieldsToEditSurvey(&editable) 169 if err != nil { 170 return err 171 } 172 } 173 174 apiClient := api.NewClientFromHTTP(httpClient) 175 opts.IO.StartProgressIndicator() 176 err = opts.FetchOptions(apiClient, repo, &editable) 177 opts.IO.StopProgressIndicator() 178 if err != nil { 179 return err 180 } 181 182 if opts.Interactive { 183 editorCommand, err := opts.DetermineEditor() 184 if err != nil { 185 return err 186 } 187 err = opts.EditFieldsSurvey(&editable, editorCommand) 188 if err != nil { 189 return err 190 } 191 } 192 193 opts.IO.StartProgressIndicator() 194 err = prShared.UpdateIssue(httpClient, repo, issue.ID, issue.IsPullRequest(), editable) 195 opts.IO.StopProgressIndicator() 196 if err != nil { 197 return err 198 } 199 200 fmt.Fprintln(opts.IO.Out, issue.URL) 201 202 return nil 203 }