gh repo edit
Co-authored-by: Mislav Marohnić <mislav@github.com>
This commit is contained in:
parent
0e3c3bb4a4
commit
98217fc38c
4 changed files with 337 additions and 0 deletions
118
pkg/cmd/repo/edit/edit.go
Normal file
118
pkg/cmd/repo/edit/edit.go
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
package edit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/MakeNowJust/heredoc"
|
||||||
|
"github.com/cli/cli/v2/api"
|
||||||
|
"github.com/cli/cli/v2/internal/ghrepo"
|
||||||
|
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EditOptions struct {
|
||||||
|
HTTPClient *http.Client
|
||||||
|
Repository ghrepo.Interface
|
||||||
|
Edits EditRepositoryInput
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
|
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(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")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func editRun(opts *EditOptions) error {
|
||||||
|
repo := opts.Repository
|
||||||
|
input := opts.Edits
|
||||||
|
|
||||||
|
apiPath := fmt.Sprintf("repos/%s/%s", repo.RepoOwner(), repo.RepoName())
|
||||||
|
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
enc := json.NewEncoder(body)
|
||||||
|
if err := enc.Encode(input); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
apiClient := api.NewClientFromHTTP(opts.HTTPClient)
|
||||||
|
_, err := api.CreateRepoTransformToV4(apiClient, repo.RepoHost(), "PATCH", apiPath, body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
140
pkg/cmd/repo/edit/edit_test.go
Normal file
140
pkg/cmd/repo/edit/edit_test.go
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
package edit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cli/cli/v2/internal/ghrepo"
|
||||||
|
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||||
|
"github.com/cli/cli/v2/pkg/httpmock"
|
||||||
|
"github.com/cli/cli/v2/pkg/iostreams"
|
||||||
|
"github.com/google/shlex"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewCmdEdit(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args string
|
||||||
|
wantOpts EditOptions
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no argument",
|
||||||
|
args: "",
|
||||||
|
wantErr: "at least one flag is required",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change repo description",
|
||||||
|
args: "--description hello",
|
||||||
|
wantOpts: EditOptions{
|
||||||
|
Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"),
|
||||||
|
Edits: EditRepositoryInput{
|
||||||
|
Description: sp("hello"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
io, _, _, _ := iostreams.Test()
|
||||||
|
io.SetStdoutTTY(true)
|
||||||
|
io.SetStdinTTY(true)
|
||||||
|
io.SetStderrTTY(true)
|
||||||
|
|
||||||
|
f := &cmdutil.Factory{
|
||||||
|
IOStreams: io,
|
||||||
|
BaseRepo: func() (ghrepo.Interface, error) {
|
||||||
|
return ghrepo.New("OWNER", "REPO"), nil
|
||||||
|
},
|
||||||
|
HttpClient: func() (*http.Client, error) {
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
argv, err := shlex.Split(tt.args)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var gotOpts *EditOptions
|
||||||
|
cmd := NewCmdEdit(f, func(opts *EditOptions) error {
|
||||||
|
gotOpts = opts
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
cmd.Flags().BoolP("help", "x", false, "")
|
||||||
|
|
||||||
|
cmd.SetArgs(argv)
|
||||||
|
cmd.SetIn(&bytes.Buffer{})
|
||||||
|
cmd.SetOut(ioutil.Discard)
|
||||||
|
cmd.SetErr(ioutil.Discard)
|
||||||
|
|
||||||
|
_, err = cmd.ExecuteC()
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
assert.EqualError(t, err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, ghrepo.FullName(tt.wantOpts.Repository), ghrepo.FullName(gotOpts.Repository))
|
||||||
|
assert.Equal(t, tt.wantOpts.Edits, gotOpts.Edits)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_editRun(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
opts EditOptions
|
||||||
|
httpStubs func(*testing.T, *httpmock.Registry)
|
||||||
|
wantsStderr string
|
||||||
|
wantsErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "change name and description",
|
||||||
|
opts: EditOptions{
|
||||||
|
Repository: ghrepo.NewWithHost("OWNER", "REPO", "github.com"),
|
||||||
|
Edits: EditRepositoryInput{
|
||||||
|
Homepage: sp("newURL"),
|
||||||
|
Description: sp("hello world!"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
httpStubs: func(t *testing.T, r *httpmock.Registry) {
|
||||||
|
r.Register(
|
||||||
|
httpmock.REST("PATCH", "repos/OWNER/REPO"),
|
||||||
|
httpmock.RESTPayload(200, `{}`, func(payload map[string]interface{}) {
|
||||||
|
assert.Equal(t, 2, len(payload))
|
||||||
|
assert.Equal(t, "newURL", payload["homepage"])
|
||||||
|
assert.Equal(t, "hello world!", payload["description"])
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
httpReg := &httpmock.Registry{}
|
||||||
|
defer httpReg.Verify(t)
|
||||||
|
if tt.httpStubs != nil {
|
||||||
|
tt.httpStubs(t, httpReg)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := &tt.opts
|
||||||
|
opts.HTTPClient = &http.Client{Transport: httpReg}
|
||||||
|
|
||||||
|
err := editRun(opts)
|
||||||
|
if tt.wantsErr == "" {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
assert.EqualError(t, err, tt.wantsErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sp(v string) *string {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
repoCreateCmd "github.com/cli/cli/v2/pkg/cmd/repo/create"
|
repoCreateCmd "github.com/cli/cli/v2/pkg/cmd/repo/create"
|
||||||
creditsCmd "github.com/cli/cli/v2/pkg/cmd/repo/credits"
|
creditsCmd "github.com/cli/cli/v2/pkg/cmd/repo/credits"
|
||||||
repoDeleteCmd "github.com/cli/cli/v2/pkg/cmd/repo/delete"
|
repoDeleteCmd "github.com/cli/cli/v2/pkg/cmd/repo/delete"
|
||||||
|
repoEditCmd "github.com/cli/cli/v2/pkg/cmd/repo/edit"
|
||||||
repoForkCmd "github.com/cli/cli/v2/pkg/cmd/repo/fork"
|
repoForkCmd "github.com/cli/cli/v2/pkg/cmd/repo/fork"
|
||||||
gardenCmd "github.com/cli/cli/v2/pkg/cmd/repo/garden"
|
gardenCmd "github.com/cli/cli/v2/pkg/cmd/repo/garden"
|
||||||
repoListCmd "github.com/cli/cli/v2/pkg/cmd/repo/list"
|
repoListCmd "github.com/cli/cli/v2/pkg/cmd/repo/list"
|
||||||
|
|
@ -41,6 +42,7 @@ func NewCmdRepo(f *cmdutil.Factory) *cobra.Command {
|
||||||
cmd.AddCommand(repoForkCmd.NewCmdFork(f, nil))
|
cmd.AddCommand(repoForkCmd.NewCmdFork(f, nil))
|
||||||
cmd.AddCommand(repoCloneCmd.NewCmdClone(f, nil))
|
cmd.AddCommand(repoCloneCmd.NewCmdClone(f, nil))
|
||||||
cmd.AddCommand(repoCreateCmd.NewCmdCreate(f, nil))
|
cmd.AddCommand(repoCreateCmd.NewCmdCreate(f, nil))
|
||||||
|
cmd.AddCommand(repoEditCmd.NewCmdEdit(f, nil))
|
||||||
cmd.AddCommand(repoListCmd.NewCmdList(f, nil))
|
cmd.AddCommand(repoListCmd.NewCmdList(f, nil))
|
||||||
cmd.AddCommand(repoSyncCmd.NewCmdSync(f, nil))
|
cmd.AddCommand(repoSyncCmd.NewCmdSync(f, nil))
|
||||||
cmd.AddCommand(creditsCmd.NewCmdRepoCredits(f, nil))
|
cmd.AddCommand(creditsCmd.NewCmdRepoCredits(f, nil))
|
||||||
|
|
|
||||||
77
pkg/cmdutil/flags.go
Normal file
77
pkg/cmdutil/flags.go
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
package cmdutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NilStringFlag defines a new flag with a string pointer receiver. This is useful for differentiating
|
||||||
|
// between the flag being set to a blank value and the flag not being passed at all.
|
||||||
|
func NilStringFlag(cmd *cobra.Command, p **string, name string, shorthand string, usage string) *pflag.Flag {
|
||||||
|
return cmd.Flags().VarPF(newStringValue(p), name, shorthand, usage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NilBoolFlag defines a new flag with a bool pointer receiver. This is useful for differentiating
|
||||||
|
// between the flag being explicitly set to a false value and the flag not being passed at all.
|
||||||
|
func NilBoolFlag(cmd *cobra.Command, p **bool, name string, shorthand string, usage string) *pflag.Flag {
|
||||||
|
f := cmd.Flags().VarPF(newBoolValue(p), name, shorthand, usage)
|
||||||
|
f.NoOptDefVal = "true"
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringValue struct {
|
||||||
|
string **string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStringValue(p **string) *stringValue {
|
||||||
|
return &stringValue{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringValue) Set(value string) error {
|
||||||
|
*s.string = &value
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringValue) String() string {
|
||||||
|
if s.string == nil || *s.string == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return **s.string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringValue) Type() string {
|
||||||
|
return "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
type boolValue struct {
|
||||||
|
bool **bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBoolValue(p **bool) *boolValue {
|
||||||
|
return &boolValue{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *boolValue) Set(value string) error {
|
||||||
|
v, err := strconv.ParseBool(value)
|
||||||
|
*b.bool = &v
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *boolValue) String() string {
|
||||||
|
if b.bool == nil || *b.bool == nil {
|
||||||
|
return "false"
|
||||||
|
} else if **b.bool {
|
||||||
|
return "true"
|
||||||
|
}
|
||||||
|
return "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *boolValue) Type() string {
|
||||||
|
return "bool"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *boolValue) IsBoolFlag() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue