Allow forcibly creating labels (#5451)

This commit is contained in:
Heath Stewart 2022-04-14 00:26:05 -07:00 committed by GitHub
parent fc8739cf97
commit 1b7d03fa8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 57 additions and 3 deletions

View file

@ -3,6 +3,7 @@ package create
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"math/rand"
"net/http"
@ -45,6 +46,7 @@ type CreateOptions struct {
Color string
Description string
Name string
Force bool
}
func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Command {
@ -81,10 +83,13 @@ 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.")
return cmd
}
var errLabelAlreadyExists = errors.New("label already exists")
func createRun(opts *CreateOptions) error {
httpClient, err := opts.HttpClient()
if err != nil {
@ -105,12 +110,15 @@ func createRun(opts *CreateOptions) error {
err = createLabel(httpClient, baseRepo, opts)
opts.IO.StopProgressIndicator()
if err != nil {
if errors.Is(err, errLabelAlreadyExists) {
return fmt.Errorf("label with name %q already exists; use `--force` to update its color and description", opts.Name)
}
return err
}
if opts.IO.IsStdoutTTY() {
cs := opts.IO.ColorScheme()
successMsg := fmt.Sprintf("\n%s Label %q created in %s\n", cs.SuccessIcon(), opts.Name, ghrepo.FullName(baseRepo))
successMsg := fmt.Sprintf("%s Label %q created in %s\n", cs.SuccessIcon(), opts.Name, ghrepo.FullName(baseRepo))
fmt.Fprint(opts.IO.Out, successMsg)
}
@ -130,5 +138,33 @@ func createLabel(client *http.Client, repo ghrepo.Interface, opts *CreateOptions
}
requestBody := bytes.NewReader(requestByte)
result := shared.Label{}
return apiClient.REST(repo.RepoHost(), "POST", path, requestBody, &result)
err = apiClient.REST(repo.RepoHost(), "POST", path, requestBody, &result)
if httpError, ok := err.(api.HTTPError); ok && isLabelAlreadyExistsError(httpError) {
err = errLabelAlreadyExists
}
if opts.Force && errors.Is(err, errLabelAlreadyExists) {
return updateLabel(apiClient, repo, opts)
}
return err
}
func updateLabel(apiClient *api.Client, repo ghrepo.Interface, opts *CreateOptions) 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,
})
if err != nil {
return err
}
requestBody := bytes.NewReader(requestByte)
result := shared.Label{}
return apiClient.REST(repo.RepoHost(), "PATCH", path, requestBody, &result)
}
func isLabelAlreadyExistsError(err api.HTTPError) bool {
return err.StatusCode == 422 && len(err.Errors) == 1 && err.Errors[0].Field == "name" && err.Errors[0].Code == "already_exists"
}

View file

@ -101,7 +101,7 @@ func TestCreateRun(t *testing.T) {
httpmock.StatusStringResponse(201, "{}"),
)
},
wantStdout: "\n✓ Label \"test\" created in OWNER/REPO\n",
wantStdout: "✓ Label \"test\" created in OWNER/REPO\n",
},
{
name: "creates label notty",
@ -115,6 +115,24 @@ func TestCreateRun(t *testing.T) {
},
wantStdout: "",
},
{
name: "creates existing label",
opts: &CreateOptions{Name: "test", Description: "some description", Force: true},
httpStubs: func(reg *httpmock.Registry) {
reg.Register(
httpmock.REST("POST", "repos/OWNER/REPO/labels"),
httpmock.WithHeader(
httpmock.StatusStringResponse(422, `{"message":"Validation Failed","errors":[{"resource":"Label","code":"already_exists","field":"name"}]}`),
"Content-Type",
"application/json",
),
)
reg.Register(
httpmock.REST("PATCH", "repos/OWNER/REPO/labels/test"),
httpmock.StatusStringResponse(201, "{}"),
)
},
},
}
for _, tt := range tests {