Add release delete

This commit is contained in:
Mislav Marohnić 2020-08-28 17:41:00 +02:00
parent 9829a4dc84
commit d43d5e0bc9
7 changed files with 211 additions and 8 deletions

View file

@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net/http"
"sort"
"strings"
"time"
@ -127,6 +128,19 @@ func RepoDefaultBranch(client *Client, repo ghrepo.Interface) (string, error) {
return r.DefaultBranchRef.Name, nil
}
func CanPushToRepo(httpClient *http.Client, repo ghrepo.Interface) (bool, error) {
if r, ok := repo.(*Repository); ok && r.ViewerPermission != "" {
return r.ViewerCanPush(), nil
}
apiClient := NewClientFromHTTP(httpClient)
r, err := GitHubRepo(apiClient, repo)
if err != nil {
return false, err
}
return r.ViewerCanPush(), nil
}
// RepoParent finds out the parent repository of a fork
func RepoParent(client *Client, repo ghrepo.Interface) (ghrepo.Interface, error) {
var query struct {

View file

@ -279,7 +279,7 @@ func createRun(opts *CreateOptions) error {
}
if !opts.Draft {
err := publishRelease(httpClient, newRelease.URL)
err := publishRelease(httpClient, newRelease.APIURL)
if err != nil {
return err
}

View file

@ -0,0 +1,119 @@
package delete
import (
"fmt"
"net/http"
"github.com/AlecAivazis/survey/v2"
"github.com/cli/cli/api"
"github.com/cli/cli/internal/ghrepo"
"github.com/cli/cli/pkg/cmd/release/shared"
"github.com/cli/cli/pkg/cmdutil"
"github.com/cli/cli/pkg/iostreams"
"github.com/cli/cli/pkg/prompt"
"github.com/spf13/cobra"
)
type DeleteOptions struct {
HttpClient func() (*http.Client, error)
IO *iostreams.IOStreams
BaseRepo func() (ghrepo.Interface, error)
TagName string
SkipConfirm bool
}
func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Command {
opts := &DeleteOptions{
IO: f.IOStreams,
HttpClient: f.HttpClient,
}
cmd := &cobra.Command{
Use: "delete <tag>",
Short: "Delete a release",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
// support `-R, --repo` override
opts.BaseRepo = f.BaseRepo
opts.TagName = args[0]
if runF != nil {
return runF(opts)
}
return deleteRun(opts)
},
}
cmd.Flags().BoolVarP(&opts.SkipConfirm, "yes", "y", false, "Skip the confirmation prompt")
return cmd
}
func deleteRun(opts *DeleteOptions) error {
httpClient, err := opts.HttpClient()
if err != nil {
return err
}
baseRepo, err := opts.BaseRepo()
if err != nil {
return err
}
release, err := shared.FetchRelease(httpClient, baseRepo, opts.TagName)
if err != nil {
return err
}
if !opts.SkipConfirm && opts.IO.CanPrompt() {
var confirmed bool
err := prompt.SurveyAskOne(&survey.Confirm{
Message: fmt.Sprintf("Delete release %s in %s?", release.TagName, ghrepo.FullName(baseRepo)),
Default: true,
}, &confirmed)
if err != nil {
return err
}
if !confirmed {
return cmdutil.SilentError
}
}
err = deleteRelease(httpClient, release.APIURL)
if err != nil {
return err
}
if !opts.IO.IsStdoutTTY() || !opts.IO.IsStderrTTY() {
return nil
}
iofmt := opts.IO.ColorScheme()
fmt.Fprintf(opts.IO.ErrOut, "%s Deleted release %s\n", iofmt.SuccessIcon(), release.TagName)
if !release.IsDraft {
fmt.Fprintf(opts.IO.ErrOut, "%s Note that the %s git tag still remains in the repository\n", iofmt.WarningIcon(), release.TagName)
}
return nil
}
func deleteRelease(httpClient *http.Client, releaseURL string) error {
req, err := http.NewRequest("DELETE", releaseURL, nil)
if err != nil {
return err
}
resp, err := httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode > 299 {
return api.HandleHTTPError(resp)
}
return nil
}

View file

@ -2,6 +2,7 @@ package release
import (
cmdCreate "github.com/cli/cli/pkg/cmd/release/create"
cmdDelete "github.com/cli/cli/pkg/cmd/release/delete"
cmdDownload "github.com/cli/cli/pkg/cmd/release/download"
cmdList "github.com/cli/cli/pkg/cmd/release/list"
cmdUpload "github.com/cli/cli/pkg/cmd/release/upload"
@ -22,6 +23,7 @@ func NewCmdRelease(f *cmdutil.Factory) *cobra.Command {
cmdutil.EnableRepoOverride(cmd, f)
cmd.AddCommand(cmdCreate.NewCmdCreate(f, nil))
cmd.AddCommand(cmdDelete.NewCmdDelete(f, nil))
cmd.AddCommand(cmdDownload.NewCmdDownload(f, nil))
cmd.AddCommand(cmdList.NewCmdList(f, nil))
cmd.AddCommand(cmdView.NewCmdView(f, nil))

View file

@ -2,6 +2,7 @@ package shared
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
@ -18,9 +19,10 @@ type Release struct {
Body string `json:"body"`
IsDraft bool `json:"draft"`
IsPrerelease bool `json:"prerelease"`
CreatedAt time.Time `json:"created_at"`
PublishedAt time.Time `json:"published_at"`
URL string `json:"url"`
APIURL string `json:"url"`
UploadURL string `json:"upload_url"`
HTMLURL string `json:"html_url"`
Assets []ReleaseAsset
@ -37,8 +39,8 @@ type ReleaseAsset struct {
URL string
}
// FetchRelease finds a repository release by its tagName.
func FetchRelease(httpClient *http.Client, baseRepo ghrepo.Interface, tagName string) (*Release, error) {
// FIXME: this doesn't find draft releases
path := fmt.Sprintf("repos/%s/%s/releases/tags/%s", baseRepo.RepoOwner(), baseRepo.RepoName(), tagName)
url := ghinstance.RESTPrefix(baseRepo.RepoHost()) + path
req, err := http.NewRequest("GET", url, nil)
@ -46,16 +48,21 @@ func FetchRelease(httpClient *http.Client, baseRepo ghrepo.Interface, tagName st
return nil, err
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
success := resp.StatusCode >= 200 && resp.StatusCode < 300
if !success {
if resp.StatusCode == 404 {
if canPush, err := api.CanPushToRepo(httpClient, baseRepo); err == nil && canPush {
return FindDraftRelease(httpClient, baseRepo, tagName)
} else if err != nil {
return nil, err
}
}
if resp.StatusCode > 299 {
return nil, api.HandleHTTPError(resp)
}
@ -72,3 +79,52 @@ func FetchRelease(httpClient *http.Client, baseRepo ghrepo.Interface, tagName st
return &release, nil
}
// FindDraftRelease interates over all releases in a repository until it finds one that matches tagName.
func FindDraftRelease(httpClient *http.Client, baseRepo ghrepo.Interface, tagName string) (*Release, error) {
path := fmt.Sprintf("repos/%s/%s/releases", baseRepo.RepoOwner(), baseRepo.RepoName())
url := ghinstance.RESTPrefix(baseRepo.RepoHost()) + path
perPage := 100
page := 1
for {
req, err := http.NewRequest("GET", fmt.Sprintf("%s?per_page=%d&page=%d", url, perPage, page), nil)
if err != nil {
return nil, err
}
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode > 299 {
return nil, api.HandleHTTPError(resp)
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var releases []Release
err = json.Unmarshal(b, &releases)
if err != nil {
return nil, err
}
for _, r := range releases {
if r.TagName == tagName {
return &r, nil
}
}
if len(releases) < perPage {
break
}
page++
}
return nil, errors.New("release not found")
}

View file

@ -70,7 +70,11 @@ func viewRun(opts *ViewOptions) error {
} else if release.IsPrerelease {
fmt.Fprintf(opts.IO.Out, "%s • ", iofmt.Yellow("Pre-release"))
}
fmt.Fprintf(opts.IO.Out, "%s\n", iofmt.Gray(fmt.Sprintf("%s released this %s", release.Author.Login, utils.FuzzyAgo(time.Since(release.PublishedAt)))))
if release.IsDraft {
fmt.Fprintf(opts.IO.Out, "%s\n", iofmt.Gray(fmt.Sprintf("%s created this %s", release.Author.Login, utils.FuzzyAgo(time.Since(release.CreatedAt)))))
} else {
fmt.Fprintf(opts.IO.Out, "%s\n", iofmt.Gray(fmt.Sprintf("%s released this %s", release.Author.Login, utils.FuzzyAgo(time.Since(release.PublishedAt)))))
}
renderedDescription, err := utils.RenderMarkdown(release.Body)
if err != nil {

View file

@ -76,3 +76,11 @@ func (c *ColorScheme) Blue(t string) string {
}
return blue(t)
}
func (c *ColorScheme) SuccessIcon() string {
return c.Green("✓")
}
func (c *ColorScheme) WarningIcon() string {
return c.Yellow("✓")
}