From 0b0ae1e6734e411482d8c548e681e243db2bf45a Mon Sep 17 00:00:00 2001 From: lktslionel Date: Thu, 6 Oct 2022 00:35:06 +0200 Subject: [PATCH] feat(cmd/release): allow to delete release with its attached tag --- pkg/cmd/release/delete/delete.go | 39 ++++++++++++++++++++++-- pkg/cmd/release/delete/delete_test.go | 43 +++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/release/delete/delete.go b/pkg/cmd/release/delete/delete.go index bb0ce0083..f4b124e97 100644 --- a/pkg/cmd/release/delete/delete.go +++ b/pkg/cmd/release/delete/delete.go @@ -6,6 +6,7 @@ import ( "github.com/AlecAivazis/survey/v2" "github.com/cli/cli/v2/api" + "github.com/cli/cli/v2/internal/ghinstance" "github.com/cli/cli/v2/internal/ghrepo" "github.com/cli/cli/v2/pkg/cmd/release/shared" "github.com/cli/cli/v2/pkg/cmdutil" @@ -21,6 +22,7 @@ type DeleteOptions struct { TagName string SkipConfirm bool + CleanupTag bool } func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Command { @@ -47,6 +49,7 @@ func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co } cmd.Flags().BoolVarP(&opts.SkipConfirm, "yes", "y", false, "Skip the confirmation prompt") + cmd.Flags().BoolVar(&opts.CleanupTag, "cleanup-tag", false, "Delete the tag attached to the release") return cmd } @@ -88,13 +91,23 @@ func deleteRun(opts *DeleteOptions) error { return err } + var cleanupMessage string + mustCleanupTag := opts.CleanupTag + if mustCleanupTag { + err = deleteTag(httpClient, baseRepo, release.TagName) + if err != nil { + return err + } + cleanupMessage = " and cleanup the tag" + } + if !opts.IO.IsStdoutTTY() || !opts.IO.IsStderrTTY() { return nil } iofmt := opts.IO.ColorScheme() - fmt.Fprintf(opts.IO.ErrOut, "%s Deleted release %s\n", iofmt.SuccessIconWithColor(iofmt.Red), release.TagName) - if !release.IsDraft { + fmt.Fprintf(opts.IO.ErrOut, "%s Deleted release %s%s\n", iofmt.SuccessIconWithColor(iofmt.Red), release.TagName, cleanupMessage) + if !release.IsDraft && !mustCleanupTag { fmt.Fprintf(opts.IO.ErrOut, "%s Note that the %s git tag still remains in the repository\n", iofmt.WarningIcon(), release.TagName) } @@ -118,3 +131,25 @@ func deleteRelease(httpClient *http.Client, releaseURL string) error { } return nil } + +func deleteTag(httpClient *http.Client, baseRepo ghrepo.Interface, tagName string) error { + path := fmt.Sprintf("repos/%s/%s/git/refs/tags/%s", baseRepo.RepoOwner(), baseRepo.RepoName(), tagName) + url := ghinstance.RESTPrefix(baseRepo.RepoHost()) + path + + req, err := http.NewRequest("DELETE", url, 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 +} + diff --git a/pkg/cmd/release/delete/delete_test.go b/pkg/cmd/release/delete/delete_test.go index 5655e2d26..f03d730cc 100644 --- a/pkg/cmd/release/delete/delete_test.go +++ b/pkg/cmd/release/delete/delete_test.go @@ -31,6 +31,7 @@ func Test_NewCmdDelete(t *testing.T) { want: DeleteOptions{ TagName: "v1.2.3", SkipConfirm: false, + CleanupTag: false, }, }, { @@ -40,6 +41,17 @@ func Test_NewCmdDelete(t *testing.T) { want: DeleteOptions{ TagName: "v1.2.3", SkipConfirm: true, + CleanupTag: false, + }, + }, + { + name: "cleanup tag", + args: "v1.2.3 --cleanup-tag", + isTTY: true, + want: DeleteOptions{ + TagName: "v1.2.3", + SkipConfirm: false, + CleanupTag: true, }, }, { @@ -83,8 +95,10 @@ func Test_NewCmdDelete(t *testing.T) { require.NoError(t, err) } + assert.Equal(t, tt.want.TagName, opts.TagName) assert.Equal(t, tt.want.SkipConfirm, opts.SkipConfirm) + assert.Equal(t, tt.want.CleanupTag, opts.CleanupTag) }) } } @@ -104,6 +118,7 @@ func Test_deleteRun(t *testing.T) { opts: DeleteOptions{ TagName: "v1.2.3", SkipConfirm: true, + CleanupTag: false, }, wantStdout: ``, wantStderr: heredoc.Doc(` @@ -117,6 +132,31 @@ func Test_deleteRun(t *testing.T) { opts: DeleteOptions{ TagName: "v1.2.3", SkipConfirm: false, + CleanupTag: false, + }, + wantStdout: ``, + wantStderr: ``, + }, + { + name: "cleanup-tag & skipping confirmation", + isTTY: true, + opts: DeleteOptions{ + TagName: "v1.2.3", + SkipConfirm: true, + CleanupTag: true, + }, + wantStdout: ``, + wantStderr: heredoc.Doc(` + ✓ Deleted release v1.2.3 and cleanup the tag + `), + }, + { + name: "cleanup-tag", + isTTY: false, + opts: DeleteOptions{ + TagName: "v1.2.3", + SkipConfirm: false, + CleanupTag: true, }, wantStdout: ``, wantStderr: ``, @@ -135,7 +175,10 @@ func Test_deleteRun(t *testing.T) { "draft": false, "url": "https://api.github.com/repos/OWNER/REPO/releases/23456" }`)) + fakeHTTP.Register(httpmock.REST("DELETE", "repos/OWNER/REPO/releases/23456"), httpmock.StatusStringResponse(204, "")) + fakeHTTP.Register(httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/tags/v1.2.3"), httpmock.StatusStringResponse(204, "")) + tt.opts.IO = ios tt.opts.HttpClient = func() (*http.Client, error) {