forked from cli/cli
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathedit.go
More file actions
230 lines (199 loc) · 7.4 KB
/
edit.go
File metadata and controls
230 lines (199 loc) · 7.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package edit
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/ghinstance"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/set"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
)
type EditOptions struct {
HTTPClient *http.Client
Repository ghrepo.Interface
Edits EditRepositoryInput
AddTopics []string
RemoveTopics []string
}
type EditRepositoryInput struct {
Description *string `json:"description,omitempty"`
Homepage *string `json:"homepage,omitempty"`
Visibility *string `json:"visibility,omitempty"`
EnableIssues *bool `json:"has_issues,omitempty"`
EnableProjects *bool `json:"has_projects,omitempty"`
EnableWiki *bool `json:"has_wiki,omitempty"`
IsTemplate *bool `json:"is_template,omitempty"`
DefaultBranch *string `json:"default_branch,omitempty"`
EnableSquashMerge *bool `json:"allow_squash_merge,omitempty"`
EnableMergeCommit *bool `json:"allow_merge_commit,omitempty"`
EnableRebaseMerge *bool `json:"allow_rebase_merge,omitempty"`
EnableAutoMerge *bool `json:"allow_auto_merge,omitempty"`
DeleteBranchOnMerge *bool `json:"delete_branch_on_merge,omitempty"`
AllowForking *bool `json:"allow_forking,omitempty"`
}
func NewCmdEdit(f *cmdutil.Factory, runF func(options *EditOptions) error) *cobra.Command {
opts := &EditOptions{}
cmd := &cobra.Command{
Use: "edit [<repository>]",
Short: "Edit repository settings",
Annotations: map[string]string{
"help:arguments": heredoc.Doc(`
A repository can be supplied as an argument in any of the following formats:
- "OWNER/REPO"
- by URL, e.g. "https://github.com/OWNER/REPO"
`),
},
Long: heredoc.Docf(`
Edit repository settings.
To toggle a setting off, use the %[1]s--flag=false%[1]s syntax.
`, "`"),
Args: cobra.MaximumNArgs(1),
Example: heredoc.Doc(`
# enable issues and wiki
gh repo edit --enable-issues --enable-wiki
# disable projects
gh repo edit --enable-projects=false
`),
RunE: func(cmd *cobra.Command, args []string) error {
if cmd.Flags().NFlag() == 0 {
return cmdutil.FlagErrorf("at least one flag is required")
}
if len(args) > 0 {
var err error
opts.Repository, err = ghrepo.FromFullName(args[0])
if err != nil {
return err
}
} else {
var err error
opts.Repository, err = f.BaseRepo()
if err != nil {
return err
}
}
if httpClient, err := f.HttpClient(); err == nil {
opts.HTTPClient = httpClient
} else {
return err
}
if runF != nil {
return runF(opts)
}
return editRun(cmd.Context(), opts)
},
}
cmdutil.NilStringFlag(cmd, &opts.Edits.Description, "description", "d", "Description of the repository")
cmdutil.NilStringFlag(cmd, &opts.Edits.Homepage, "homepage", "h", "Repository home page `URL`")
cmdutil.NilStringFlag(cmd, &opts.Edits.DefaultBranch, "default-branch", "", "Set the default branch `name` for the repository")
cmdutil.NilStringFlag(cmd, &opts.Edits.Visibility, "visibility", "", "Change the visibility of the repository to {public,private,internal}")
cmdutil.NilBoolFlag(cmd, &opts.Edits.IsTemplate, "template", "", "Make the repository available as a template repository")
cmdutil.NilBoolFlag(cmd, &opts.Edits.EnableIssues, "enable-issues", "", "Enable issues in the repository")
cmdutil.NilBoolFlag(cmd, &opts.Edits.EnableProjects, "enable-projects", "", "Enable projects in the repository")
cmdutil.NilBoolFlag(cmd, &opts.Edits.EnableWiki, "enable-wiki", "", "Enable wiki in the repository")
cmdutil.NilBoolFlag(cmd, &opts.Edits.EnableMergeCommit, "enable-merge-commit", "", "Enable merging pull requests via merge commit")
cmdutil.NilBoolFlag(cmd, &opts.Edits.EnableSquashMerge, "enable-squash-merge", "", "Enable merging pull requests via squashed commit")
cmdutil.NilBoolFlag(cmd, &opts.Edits.EnableRebaseMerge, "enable-rebase-merge", "", "Enable merging pull requests via rebase")
cmdutil.NilBoolFlag(cmd, &opts.Edits.EnableAutoMerge, "enable-auto-merge", "", "Enable auto-merge functionality")
cmdutil.NilBoolFlag(cmd, &opts.Edits.DeleteBranchOnMerge, "delete-branch-on-merge", "", "Delete head branch when pull requests are merged")
cmdutil.NilBoolFlag(cmd, &opts.Edits.AllowForking, "allow-forking", "", "Allow forking of an organization repository")
cmd.Flags().StringSliceVar(&opts.AddTopics, "add-topic", nil, "Add repository topic")
cmd.Flags().StringSliceVar(&opts.RemoveTopics, "remove-topic", nil, "Remove repository topic")
return cmd
}
func editRun(ctx context.Context, opts *EditOptions) error {
repo := opts.Repository
apiPath := fmt.Sprintf("repos/%s/%s", repo.RepoOwner(), repo.RepoName())
body := &bytes.Buffer{}
enc := json.NewEncoder(body)
if err := enc.Encode(opts.Edits); err != nil {
return err
}
g := errgroup.Group{}
if body.Len() > 3 {
g.Go(func() error {
apiClient := api.NewClientFromHTTP(opts.HTTPClient)
_, err := api.CreateRepoTransformToV4(apiClient, repo.RepoHost(), "PATCH", apiPath, body)
return err
})
}
if len(opts.AddTopics) > 0 || len(opts.RemoveTopics) > 0 {
g.Go(func() error {
existingTopics, err := getTopics(ctx, opts.HTTPClient, repo)
if err != nil {
return err
}
oldTopics := set.NewStringSet()
oldTopics.AddValues(existingTopics)
newTopics := set.NewStringSet()
newTopics.AddValues(existingTopics)
newTopics.AddValues(opts.AddTopics)
newTopics.RemoveValues(opts.RemoveTopics)
if oldTopics.Equal(newTopics) {
return nil
}
return setTopics(ctx, opts.HTTPClient, repo, newTopics.ToSlice())
})
}
return g.Wait()
}
func getTopics(ctx context.Context, httpClient *http.Client, repo ghrepo.Interface) ([]string, error) {
apiPath := fmt.Sprintf("repos/%s/%s/topics", repo.RepoOwner(), repo.RepoName())
req, err := http.NewRequestWithContext(ctx, "GET", ghinstance.RESTPrefix(repo.RepoHost())+apiPath, nil)
if err != nil {
return nil, err
}
// "mercy-preview" is still needed for some GitHub Enterprise versions
req.Header.Set("Accept", "application/vnd.github.mercy-preview+json")
res, err := httpClient.Do(req)
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusOK {
return nil, api.HandleHTTPError(res)
}
var responseData struct {
Names []string `json:"names"`
}
dec := json.NewDecoder(res.Body)
err = dec.Decode(&responseData)
return responseData.Names, err
}
func setTopics(ctx context.Context, httpClient *http.Client, repo ghrepo.Interface, topics []string) error {
payload := struct {
Names []string `json:"names"`
}{
Names: topics,
}
body := &bytes.Buffer{}
dec := json.NewEncoder(body)
if err := dec.Encode(&payload); err != nil {
return err
}
apiPath := fmt.Sprintf("repos/%s/%s/topics", repo.RepoOwner(), repo.RepoName())
req, err := http.NewRequestWithContext(ctx, "PUT", ghinstance.RESTPrefix(repo.RepoHost())+apiPath, body)
if err != nil {
return err
}
req.Header.Set("Content-type", "application/json")
// "mercy-preview" is still needed for some GitHub Enterprise versions
req.Header.Set("Accept", "application/vnd.github.mercy-preview+json")
res, err := httpClient.Do(req)
if err != nil {
return err
}
if res.StatusCode != http.StatusOK {
return api.HandleHTTPError(res)
}
if res.Body != nil {
_, _ = io.Copy(ioutil.Discard, res.Body)
}
return nil
}