Add release delete
This commit is contained in:
parent
9829a4dc84
commit
d43d5e0bc9
7 changed files with 211 additions and 8 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
119
pkg/cmd/release/delete/delete.go
Normal file
119
pkg/cmd/release/delete/delete.go
Normal 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
|
||||
}
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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("✓")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue