X Tutup
Skip to content

Commit d43d5e0

Browse files
committed
Add release delete
1 parent 9829a4d commit d43d5e0

File tree

7 files changed

+211
-8
lines changed

7 files changed

+211
-8
lines changed

api/queries_repo.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"errors"
88
"fmt"
9+
"net/http"
910
"sort"
1011
"strings"
1112
"time"
@@ -127,6 +128,19 @@ func RepoDefaultBranch(client *Client, repo ghrepo.Interface) (string, error) {
127128
return r.DefaultBranchRef.Name, nil
128129
}
129130

131+
func CanPushToRepo(httpClient *http.Client, repo ghrepo.Interface) (bool, error) {
132+
if r, ok := repo.(*Repository); ok && r.ViewerPermission != "" {
133+
return r.ViewerCanPush(), nil
134+
}
135+
136+
apiClient := NewClientFromHTTP(httpClient)
137+
r, err := GitHubRepo(apiClient, repo)
138+
if err != nil {
139+
return false, err
140+
}
141+
return r.ViewerCanPush(), nil
142+
}
143+
130144
// RepoParent finds out the parent repository of a fork
131145
func RepoParent(client *Client, repo ghrepo.Interface) (ghrepo.Interface, error) {
132146
var query struct {

pkg/cmd/release/create/create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ func createRun(opts *CreateOptions) error {
279279
}
280280

281281
if !opts.Draft {
282-
err := publishRelease(httpClient, newRelease.URL)
282+
err := publishRelease(httpClient, newRelease.APIURL)
283283
if err != nil {
284284
return err
285285
}

pkg/cmd/release/delete/delete.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package delete
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
7+
"github.com/AlecAivazis/survey/v2"
8+
"github.com/cli/cli/api"
9+
"github.com/cli/cli/internal/ghrepo"
10+
"github.com/cli/cli/pkg/cmd/release/shared"
11+
"github.com/cli/cli/pkg/cmdutil"
12+
"github.com/cli/cli/pkg/iostreams"
13+
"github.com/cli/cli/pkg/prompt"
14+
"github.com/spf13/cobra"
15+
)
16+
17+
type DeleteOptions struct {
18+
HttpClient func() (*http.Client, error)
19+
IO *iostreams.IOStreams
20+
BaseRepo func() (ghrepo.Interface, error)
21+
22+
TagName string
23+
SkipConfirm bool
24+
}
25+
26+
func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Command {
27+
opts := &DeleteOptions{
28+
IO: f.IOStreams,
29+
HttpClient: f.HttpClient,
30+
}
31+
32+
cmd := &cobra.Command{
33+
Use: "delete <tag>",
34+
Short: "Delete a release",
35+
Args: cobra.ExactArgs(1),
36+
RunE: func(cmd *cobra.Command, args []string) error {
37+
// support `-R, --repo` override
38+
opts.BaseRepo = f.BaseRepo
39+
40+
opts.TagName = args[0]
41+
42+
if runF != nil {
43+
return runF(opts)
44+
}
45+
return deleteRun(opts)
46+
},
47+
}
48+
49+
cmd.Flags().BoolVarP(&opts.SkipConfirm, "yes", "y", false, "Skip the confirmation prompt")
50+
51+
return cmd
52+
}
53+
54+
func deleteRun(opts *DeleteOptions) error {
55+
httpClient, err := opts.HttpClient()
56+
if err != nil {
57+
return err
58+
}
59+
60+
baseRepo, err := opts.BaseRepo()
61+
if err != nil {
62+
return err
63+
}
64+
65+
release, err := shared.FetchRelease(httpClient, baseRepo, opts.TagName)
66+
if err != nil {
67+
return err
68+
}
69+
70+
if !opts.SkipConfirm && opts.IO.CanPrompt() {
71+
var confirmed bool
72+
err := prompt.SurveyAskOne(&survey.Confirm{
73+
Message: fmt.Sprintf("Delete release %s in %s?", release.TagName, ghrepo.FullName(baseRepo)),
74+
Default: true,
75+
}, &confirmed)
76+
if err != nil {
77+
return err
78+
}
79+
80+
if !confirmed {
81+
return cmdutil.SilentError
82+
}
83+
}
84+
85+
err = deleteRelease(httpClient, release.APIURL)
86+
if err != nil {
87+
return err
88+
}
89+
90+
if !opts.IO.IsStdoutTTY() || !opts.IO.IsStderrTTY() {
91+
return nil
92+
}
93+
94+
iofmt := opts.IO.ColorScheme()
95+
fmt.Fprintf(opts.IO.ErrOut, "%s Deleted release %s\n", iofmt.SuccessIcon(), release.TagName)
96+
if !release.IsDraft {
97+
fmt.Fprintf(opts.IO.ErrOut, "%s Note that the %s git tag still remains in the repository\n", iofmt.WarningIcon(), release.TagName)
98+
}
99+
100+
return nil
101+
}
102+
103+
func deleteRelease(httpClient *http.Client, releaseURL string) error {
104+
req, err := http.NewRequest("DELETE", releaseURL, nil)
105+
if err != nil {
106+
return err
107+
}
108+
109+
resp, err := httpClient.Do(req)
110+
if err != nil {
111+
return err
112+
}
113+
defer resp.Body.Close()
114+
115+
if resp.StatusCode > 299 {
116+
return api.HandleHTTPError(resp)
117+
}
118+
return nil
119+
}

pkg/cmd/release/release.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package release
22

33
import (
44
cmdCreate "github.com/cli/cli/pkg/cmd/release/create"
5+
cmdDelete "github.com/cli/cli/pkg/cmd/release/delete"
56
cmdDownload "github.com/cli/cli/pkg/cmd/release/download"
67
cmdList "github.com/cli/cli/pkg/cmd/release/list"
78
cmdUpload "github.com/cli/cli/pkg/cmd/release/upload"
@@ -22,6 +23,7 @@ func NewCmdRelease(f *cmdutil.Factory) *cobra.Command {
2223
cmdutil.EnableRepoOverride(cmd, f)
2324

2425
cmd.AddCommand(cmdCreate.NewCmdCreate(f, nil))
26+
cmd.AddCommand(cmdDelete.NewCmdDelete(f, nil))
2527
cmd.AddCommand(cmdDownload.NewCmdDownload(f, nil))
2628
cmd.AddCommand(cmdList.NewCmdList(f, nil))
2729
cmd.AddCommand(cmdView.NewCmdView(f, nil))

pkg/cmd/release/shared/fetch.go

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package shared
22

33
import (
44
"encoding/json"
5+
"errors"
56
"fmt"
67
"io/ioutil"
78
"net/http"
@@ -18,9 +19,10 @@ type Release struct {
1819
Body string `json:"body"`
1920
IsDraft bool `json:"draft"`
2021
IsPrerelease bool `json:"prerelease"`
22+
CreatedAt time.Time `json:"created_at"`
2123
PublishedAt time.Time `json:"published_at"`
2224

23-
URL string `json:"url"`
25+
APIURL string `json:"url"`
2426
UploadURL string `json:"upload_url"`
2527
HTMLURL string `json:"html_url"`
2628
Assets []ReleaseAsset
@@ -37,25 +39,30 @@ type ReleaseAsset struct {
3739
URL string
3840
}
3941

42+
// FetchRelease finds a repository release by its tagName.
4043
func FetchRelease(httpClient *http.Client, baseRepo ghrepo.Interface, tagName string) (*Release, error) {
41-
// FIXME: this doesn't find draft releases
4244
path := fmt.Sprintf("repos/%s/%s/releases/tags/%s", baseRepo.RepoOwner(), baseRepo.RepoName(), tagName)
4345
url := ghinstance.RESTPrefix(baseRepo.RepoHost()) + path
4446
req, err := http.NewRequest("GET", url, nil)
4547
if err != nil {
4648
return nil, err
4749
}
4850

49-
req.Header.Set("Content-Type", "application/json; charset=utf-8")
50-
5151
resp, err := httpClient.Do(req)
5252
if err != nil {
5353
return nil, err
5454
}
5555
defer resp.Body.Close()
5656

57-
success := resp.StatusCode >= 200 && resp.StatusCode < 300
58-
if !success {
57+
if resp.StatusCode == 404 {
58+
if canPush, err := api.CanPushToRepo(httpClient, baseRepo); err == nil && canPush {
59+
return FindDraftRelease(httpClient, baseRepo, tagName)
60+
} else if err != nil {
61+
return nil, err
62+
}
63+
}
64+
65+
if resp.StatusCode > 299 {
5966
return nil, api.HandleHTTPError(resp)
6067
}
6168

@@ -72,3 +79,52 @@ func FetchRelease(httpClient *http.Client, baseRepo ghrepo.Interface, tagName st
7279

7380
return &release, nil
7481
}
82+
83+
// FindDraftRelease interates over all releases in a repository until it finds one that matches tagName.
84+
func FindDraftRelease(httpClient *http.Client, baseRepo ghrepo.Interface, tagName string) (*Release, error) {
85+
path := fmt.Sprintf("repos/%s/%s/releases", baseRepo.RepoOwner(), baseRepo.RepoName())
86+
url := ghinstance.RESTPrefix(baseRepo.RepoHost()) + path
87+
88+
perPage := 100
89+
page := 1
90+
for {
91+
req, err := http.NewRequest("GET", fmt.Sprintf("%s?per_page=%d&page=%d", url, perPage, page), nil)
92+
if err != nil {
93+
return nil, err
94+
}
95+
96+
resp, err := httpClient.Do(req)
97+
if err != nil {
98+
return nil, err
99+
}
100+
defer resp.Body.Close()
101+
102+
if resp.StatusCode > 299 {
103+
return nil, api.HandleHTTPError(resp)
104+
}
105+
106+
b, err := ioutil.ReadAll(resp.Body)
107+
if err != nil {
108+
return nil, err
109+
}
110+
111+
var releases []Release
112+
err = json.Unmarshal(b, &releases)
113+
if err != nil {
114+
return nil, err
115+
}
116+
117+
for _, r := range releases {
118+
if r.TagName == tagName {
119+
return &r, nil
120+
}
121+
}
122+
123+
if len(releases) < perPage {
124+
break
125+
}
126+
page++
127+
}
128+
129+
return nil, errors.New("release not found")
130+
}

pkg/cmd/release/view/view.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,11 @@ func viewRun(opts *ViewOptions) error {
7070
} else if release.IsPrerelease {
7171
fmt.Fprintf(opts.IO.Out, "%s • ", iofmt.Yellow("Pre-release"))
7272
}
73-
fmt.Fprintf(opts.IO.Out, "%s\n", iofmt.Gray(fmt.Sprintf("%s released this %s", release.Author.Login, utils.FuzzyAgo(time.Since(release.PublishedAt)))))
73+
if release.IsDraft {
74+
fmt.Fprintf(opts.IO.Out, "%s\n", iofmt.Gray(fmt.Sprintf("%s created this %s", release.Author.Login, utils.FuzzyAgo(time.Since(release.CreatedAt)))))
75+
} else {
76+
fmt.Fprintf(opts.IO.Out, "%s\n", iofmt.Gray(fmt.Sprintf("%s released this %s", release.Author.Login, utils.FuzzyAgo(time.Since(release.PublishedAt)))))
77+
}
7478

7579
renderedDescription, err := utils.RenderMarkdown(release.Body)
7680
if err != nil {

pkg/iostreams/color.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,11 @@ func (c *ColorScheme) Blue(t string) string {
7676
}
7777
return blue(t)
7878
}
79+
80+
func (c *ColorScheme) SuccessIcon() string {
81+
return c.Green("✓")
82+
}
83+
84+
func (c *ColorScheme) WarningIcon() string {
85+
return c.Yellow("✓")
86+
}

0 commit comments

Comments
 (0)
X Tutup