Add label edit command (#5519)
This commit is contained in:
parent
db85b1d3cb
commit
6edb4ecdbb
4 changed files with 305 additions and 11 deletions
|
|
@ -60,12 +60,13 @@ func newCmdCreate(f *cmdutil.Factory, runF func(*createOptions) error) *cobra.Co
|
|||
Long: heredoc.Doc(`
|
||||
Create a new label on GitHub.
|
||||
|
||||
Must specify name for the label, the description and color are optional.
|
||||
Must specify name for the label. The description and color are optional.
|
||||
If a color isn't provided, a random one will be chosen.
|
||||
|
||||
Color needs to be 6 character hex value.
|
||||
The label color needs to be 6 character hex value.
|
||||
`),
|
||||
Example: heredoc.Doc(`
|
||||
# create new bug label
|
||||
$ gh label create bug --description "Something isn't working" --color E99695
|
||||
`),
|
||||
Args: cmdutil.ExactArgs(1, "cannot create label: name argument required"),
|
||||
|
|
@ -81,8 +82,8 @@ func newCmdCreate(f *cmdutil.Factory, runF func(*createOptions) error) *cobra.Co
|
|||
}
|
||||
|
||||
cmd.Flags().StringVarP(&opts.Description, "description", "d", "", "Description of the label")
|
||||
cmd.Flags().StringVarP(&opts.Color, "color", "c", "", "Color of the label, if not specified one will be selected at random")
|
||||
cmd.Flags().BoolVarP(&opts.Force, "force", "f", false, "Set the label color and description even if the name already exists.")
|
||||
cmd.Flags().StringVarP(&opts.Color, "color", "c", "", "Color of the label")
|
||||
cmd.Flags().BoolVarP(&opts.Force, "force", "f", false, "Update the label color and description if label already exists")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
@ -144,24 +145,42 @@ func createLabel(client *http.Client, repo ghrepo.Interface, opts *createOptions
|
|||
}
|
||||
|
||||
if opts.Force && errors.Is(err, errLabelAlreadyExists) {
|
||||
return updateLabel(apiClient, repo, opts)
|
||||
editOpts := editOptions{
|
||||
Description: opts.Description,
|
||||
Color: opts.Color,
|
||||
Name: opts.Name,
|
||||
}
|
||||
return updateLabel(apiClient, repo, &editOpts)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func updateLabel(apiClient *api.Client, repo ghrepo.Interface, opts *createOptions) error {
|
||||
func updateLabel(apiClient *api.Client, repo ghrepo.Interface, opts *editOptions) error {
|
||||
path := fmt.Sprintf("repos/%s/%s/labels/%s", repo.RepoOwner(), repo.RepoName(), opts.Name)
|
||||
requestByte, err := json.Marshal(map[string]string{
|
||||
"description": opts.Description,
|
||||
"color": opts.Color,
|
||||
})
|
||||
properties := map[string]string{}
|
||||
if opts.Description != "" {
|
||||
properties["description"] = opts.Description
|
||||
}
|
||||
if opts.Color != "" {
|
||||
properties["color"] = opts.Color
|
||||
}
|
||||
if opts.NewName != "" {
|
||||
properties["new_name"] = opts.NewName
|
||||
}
|
||||
requestByte, err := json.Marshal(properties)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
requestBody := bytes.NewReader(requestByte)
|
||||
result := label{}
|
||||
return apiClient.REST(repo.RepoHost(), "PATCH", path, requestBody, &result)
|
||||
err = apiClient.REST(repo.RepoHost(), "PATCH", path, requestBody, &result)
|
||||
|
||||
if httpError, ok := err.(api.HTTPError); ok && isLabelAlreadyExistsError(httpError) {
|
||||
err = errLabelAlreadyExists
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func isLabelAlreadyExistsError(err api.HTTPError) bool {
|
||||
|
|
|
|||
104
pkg/cmd/label/edit.go
Normal file
104
pkg/cmd/label/edit.go
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
package label
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"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/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type editOptions struct {
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
|
||||
Color string
|
||||
Description string
|
||||
Name string
|
||||
NewName string
|
||||
}
|
||||
|
||||
func newCmdEdit(f *cmdutil.Factory, runF func(*editOptions) error) *cobra.Command {
|
||||
opts := editOptions{
|
||||
HttpClient: f.HttpClient,
|
||||
IO: f.IOStreams,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "edit <name>",
|
||||
Short: "Edit a label",
|
||||
Long: heredoc.Docf(`
|
||||
Update a label on GitHub.
|
||||
|
||||
A label can be renamed using the %[1]s--name%[1]s flag.
|
||||
|
||||
The label color needs to be 6 character hex value.
|
||||
`, "`"),
|
||||
Example: heredoc.Doc(`
|
||||
# update the color of the bug label
|
||||
$ gh label edit bug --color FF0000
|
||||
|
||||
# rename and edit the description of the bug label
|
||||
$ gh label edit bug --name big-bug --description "Bigger than normal bug"
|
||||
`),
|
||||
Args: cmdutil.ExactArgs(1, "cannot update label: name argument required"),
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
opts.BaseRepo = f.BaseRepo
|
||||
opts.Name = args[0]
|
||||
opts.Color = strings.TrimPrefix(opts.Color, "#")
|
||||
if opts.Description == "" &&
|
||||
opts.Color == "" &&
|
||||
opts.NewName == "" {
|
||||
return cmdutil.FlagErrorf("specify at least one of `--color`, `--description`, or `--name`")
|
||||
}
|
||||
if runF != nil {
|
||||
return runF(&opts)
|
||||
}
|
||||
return editRun(&opts)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&opts.Description, "description", "d", "", "Description of the label")
|
||||
cmd.Flags().StringVarP(&opts.Color, "color", "c", "", "Color of the label")
|
||||
cmd.Flags().StringVarP(&opts.NewName, "name", "n", "", "Name of the label")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func editRun(opts *editOptions) error {
|
||||
httpClient, err := opts.HttpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apiClient := api.NewClientFromHTTP(httpClient)
|
||||
|
||||
baseRepo, err := opts.BaseRepo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts.IO.StartProgressIndicator()
|
||||
err = updateLabel(apiClient, baseRepo, opts)
|
||||
opts.IO.StopProgressIndicator()
|
||||
if err != nil {
|
||||
if errors.Is(err, errLabelAlreadyExists) {
|
||||
return fmt.Errorf("label with name %q already exists", opts.NewName)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
cs := opts.IO.ColorScheme()
|
||||
successMsg := fmt.Sprintf("%s Label %q updated in %s\n", cs.SuccessIcon(), opts.Name, ghrepo.FullName(baseRepo))
|
||||
fmt.Fprint(opts.IO.Out, successMsg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
170
pkg/cmd/label/edit_test.go
Normal file
170
pkg/cmd/label/edit_test.go
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
package label
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestNewCmdEdit(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
output editOptions
|
||||
wantErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "no argument",
|
||||
input: "",
|
||||
wantErr: true,
|
||||
errMsg: "cannot update label: name argument required",
|
||||
},
|
||||
{
|
||||
name: "name argument",
|
||||
input: "test",
|
||||
wantErr: true,
|
||||
errMsg: "specify at least one of `--color`, `--description`, or `--name`",
|
||||
},
|
||||
{
|
||||
name: "description flag",
|
||||
input: "test --description 'some description'",
|
||||
output: editOptions{Name: "test", Description: "some description"},
|
||||
},
|
||||
{
|
||||
name: "color flag",
|
||||
input: "test --color FFFFFF",
|
||||
output: editOptions{Name: "test", Color: "FFFFFF"},
|
||||
},
|
||||
{
|
||||
name: "color flag with pound sign",
|
||||
input: "test --color '#AAAAAA'",
|
||||
output: editOptions{Name: "test", Color: "AAAAAA"},
|
||||
},
|
||||
{
|
||||
name: "name flag",
|
||||
input: "test --name test1",
|
||||
output: editOptions{Name: "test", NewName: "test1"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
io, _, _, _ := iostreams.Test()
|
||||
f := &cmdutil.Factory{
|
||||
IOStreams: io,
|
||||
}
|
||||
argv, err := shlex.Split(tt.input)
|
||||
assert.NoError(t, err)
|
||||
var gotOpts *editOptions
|
||||
cmd := newCmdEdit(f, func(opts *editOptions) error {
|
||||
gotOpts = opts
|
||||
return nil
|
||||
})
|
||||
cmd.SetArgs(argv)
|
||||
cmd.SetIn(&bytes.Buffer{})
|
||||
cmd.SetOut(&bytes.Buffer{})
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
|
||||
_, err = cmd.ExecuteC()
|
||||
if tt.wantErr {
|
||||
assert.EqualError(t, err, tt.errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.output.Color, gotOpts.Color)
|
||||
assert.Equal(t, tt.output.Description, gotOpts.Description)
|
||||
assert.Equal(t, tt.output.Name, gotOpts.Name)
|
||||
assert.Equal(t, tt.output.NewName, gotOpts.NewName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEditRun(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tty bool
|
||||
opts *editOptions
|
||||
httpStubs func(*httpmock.Registry)
|
||||
wantStdout string
|
||||
wantErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "updates label",
|
||||
tty: true,
|
||||
opts: &editOptions{Name: "test", Description: "some description"},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("PATCH", "repos/OWNER/REPO/labels/test"),
|
||||
httpmock.StatusStringResponse(201, "{}"),
|
||||
)
|
||||
},
|
||||
wantStdout: "✓ Label \"test\" updated in OWNER/REPO\n",
|
||||
},
|
||||
{
|
||||
name: "updates label notty",
|
||||
tty: false,
|
||||
opts: &editOptions{Name: "test", Description: "some description"},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("PATCH", "repos/OWNER/REPO/labels/test"),
|
||||
httpmock.StatusStringResponse(201, "{}"),
|
||||
)
|
||||
},
|
||||
wantStdout: "",
|
||||
},
|
||||
{
|
||||
name: "updates missing label",
|
||||
opts: &editOptions{Name: "invalid", Description: "some description"},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("PATCH", "repos/OWNER/REPO/labels/invalid"),
|
||||
httpmock.WithHeader(
|
||||
httpmock.StatusStringResponse(404, `{"message":"Not Found"}`),
|
||||
"Content-Type",
|
||||
"application/json",
|
||||
),
|
||||
)
|
||||
},
|
||||
wantErrMsg: "HTTP 404: Not Found (https://api.github.com/repos/OWNER/REPO/labels/invalid)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
reg := &httpmock.Registry{}
|
||||
if tt.httpStubs != nil {
|
||||
tt.httpStubs(reg)
|
||||
}
|
||||
tt.opts.HttpClient = func() (*http.Client, error) {
|
||||
return &http.Client{Transport: reg}, nil
|
||||
}
|
||||
io, _, stdout, _ := iostreams.Test()
|
||||
io.SetStdoutTTY(tt.tty)
|
||||
io.SetStdinTTY(tt.tty)
|
||||
io.SetStderrTTY(tt.tty)
|
||||
tt.opts.IO = io
|
||||
tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
|
||||
return ghrepo.New("OWNER", "REPO"), nil
|
||||
}
|
||||
defer reg.Verify(t)
|
||||
err := editRun(tt.opts)
|
||||
|
||||
if tt.wantErrMsg != "" {
|
||||
assert.EqualError(t, err, tt.wantErrMsg)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.wantStdout, stdout.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ func NewCmdLabel(f *cmdutil.Factory) *cobra.Command {
|
|||
cmd.AddCommand(newCmdList(f, nil))
|
||||
cmd.AddCommand(newCmdCreate(f, nil))
|
||||
cmd.AddCommand(newCmdClone(f, nil))
|
||||
cmd.AddCommand(newCmdEdit(f, nil))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue