diff --git a/pkg/cmd/release/create/create.go b/pkg/cmd/release/create/create.go index 9a846700e..f4f04ac48 100644 --- a/pkg/cmd/release/create/create.go +++ b/pkg/cmd/release/create/create.go @@ -38,6 +38,7 @@ type CreateOptions struct { BodyProvided bool Draft bool Prerelease bool + IsLatest *bool Assets []*shared.AssetForUpload @@ -164,6 +165,7 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co cmd.Flags().StringVarP(&opts.DiscussionCategory, "discussion-category", "", "", "Start a discussion in the specified category") cmd.Flags().BoolVarP(&opts.GenerateNotes, "generate-notes", "", false, "Automatically generate title and notes for the release") cmd.Flags().StringVar(&opts.NotesStartTag, "notes-start-tag", "", "Tag to use as the starting point for generating release notes") + cmdutil.NilBoolFlag(cmd, &opts.IsLatest, "latest", "", "Mark this release as \"Latest\" (default: automatic based on date and version)") return cmd } @@ -409,6 +411,10 @@ func createRun(opts *CreateOptions) error { if opts.Target != "" { params["target_commitish"] = opts.Target } + if opts.IsLatest != nil { + // valid values: true/false/legacy + params["make_latest"] = fmt.Sprintf("%v", *opts.IsLatest) + } if opts.DiscussionCategory != "" { params["discussion_category_name"] = opts.DiscussionCategory } diff --git a/pkg/cmd/release/create/create_test.go b/pkg/cmd/release/create/create_test.go index e5b8e1744..1a7a43c60 100644 --- a/pkg/cmd/release/create/create_test.go +++ b/pkg/cmd/release/create/create_test.go @@ -260,6 +260,46 @@ func Test_NewCmdCreate(t *testing.T) { NotesStartTag: "v1.1.0", }, }, + { + name: "latest", + args: "--latest v1.1.0", + isTTY: false, + want: CreateOptions{ + TagName: "v1.1.0", + Target: "", + Name: "", + Body: "", + BodyProvided: false, + Draft: false, + Prerelease: false, + IsLatest: boolPtr(true), + RepoOverride: "", + Concurrency: 5, + Assets: []*shared.AssetForUpload(nil), + GenerateNotes: false, + NotesStartTag: "", + }, + }, + { + name: "not latest", + args: "--latest=false v1.1.0", + isTTY: false, + want: CreateOptions{ + TagName: "v1.1.0", + Target: "", + Name: "", + Body: "", + BodyProvided: false, + Draft: false, + Prerelease: false, + IsLatest: boolPtr(false), + RepoOverride: "", + Concurrency: 5, + Assets: []*shared.AssetForUpload(nil), + GenerateNotes: false, + NotesStartTag: "", + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -312,6 +352,7 @@ func Test_NewCmdCreate(t *testing.T) { assert.Equal(t, tt.want.DiscussionCategory, opts.DiscussionCategory) assert.Equal(t, tt.want.GenerateNotes, opts.GenerateNotes) assert.Equal(t, tt.want.NotesStartTag, opts.NotesStartTag) + assert.Equal(t, tt.want.IsLatest, opts.IsLatest) require.Equal(t, len(tt.want.Assets), len(opts.Assets)) for i := range tt.want.Assets { @@ -444,6 +485,35 @@ func Test_createRun(t *testing.T) { wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n", wantStderr: ``, }, + { + name: "with latest", + isTTY: false, + opts: CreateOptions{ + TagName: "v1.2.3", + Name: "", + Body: "", + Target: "", + IsLatest: boolPtr(true), + BodyProvided: true, + GenerateNotes: false, + }, + httpStubs: func(t *testing.T, reg *httpmock.Registry) { + reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.RESTPayload(201, `{ + "url": "https://api.github.com/releases/123", + "upload_url": "https://api.github.com/assets/upload", + "html_url": "https://github.com/OWNER/REPO/releases/tag/v1.2.3" + }`, func(params map[string]interface{}) { + assert.Equal(t, map[string]interface{}{ + "tag_name": "v1.2.3", + "draft": false, + "prerelease": false, + "make_latest": "true", + }, params) + })) + }, + wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n", + wantErr: "", + }, { name: "with generate notes", isTTY: true, @@ -1130,3 +1200,7 @@ func Test_createRun_interactive(t *testing.T) { }) } } + +func boolPtr(b bool) *bool { + return &b +} diff --git a/pkg/cmd/release/edit/edit.go b/pkg/cmd/release/edit/edit.go index ca0840fa1..e3af2d714 100644 --- a/pkg/cmd/release/edit/edit.go +++ b/pkg/cmd/release/edit/edit.go @@ -24,6 +24,7 @@ type EditOptions struct { DiscussionCategory *string Draft *bool Prerelease *bool + IsLatest *bool } func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Command { @@ -72,6 +73,7 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman cmdutil.NilBoolFlag(cmd, &opts.Draft, "draft", "", "Save the release as a draft instead of publishing it") cmdutil.NilBoolFlag(cmd, &opts.Prerelease, "prerelease", "", "Mark the release as a prerelease") + cmdutil.NilBoolFlag(cmd, &opts.IsLatest, "latest", "", "Explicitly mark the release as \"Latest\"") cmdutil.NilStringFlag(cmd, &opts.Body, "notes", "n", "Release notes") cmdutil.NilStringFlag(cmd, &opts.Name, "title", "t", "Release title") cmdutil.NilStringFlag(cmd, &opts.DiscussionCategory, "discussion-category", "", "Start a discussion in the specified category when publishing a draft") @@ -146,5 +148,10 @@ func getParams(opts *EditOptions) map[string]interface{} { params["target_commitish"] = opts.Target } + if opts.IsLatest != nil { + // valid values: true/false/legacy + params["make_latest"] = fmt.Sprintf("%v", *opts.IsLatest) + } + return params } diff --git a/pkg/cmd/release/edit/edit_test.go b/pkg/cmd/release/edit/edit_test.go index f486bee72..c6bc31aef 100644 --- a/pkg/cmd/release/edit/edit_test.go +++ b/pkg/cmd/release/edit/edit_test.go @@ -102,6 +102,24 @@ func Test_NewCmdEdit(t *testing.T) { Draft: boolPtr(false), }, }, + { + name: "latest", + args: "v1.2.3 --latest", + isTTY: false, + want: EditOptions{ + TagName: "", + IsLatest: boolPtr(true), + }, + }, + { + name: "not latest", + args: "v1.2.3 --latest=false", + isTTY: false, + want: EditOptions{ + TagName: "", + IsLatest: boolPtr(false), + }, + }, { name: "provide notes from file", args: fmt.Sprintf(`v1.2.3 -F '%s'`, tf.Name()), @@ -169,6 +187,7 @@ func Test_NewCmdEdit(t *testing.T) { assert.Equal(t, tt.want.DiscussionCategory, opts.DiscussionCategory) assert.Equal(t, tt.want.Draft, opts.Draft) assert.Equal(t, tt.want.Prerelease, opts.Prerelease) + assert.Equal(t, tt.want.IsLatest, opts.IsLatest) }) } } @@ -250,6 +269,23 @@ func Test_editRun(t *testing.T) { wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n", wantStderr: "", }, + { + name: "edit the latest marker", + isTTY: false, + opts: EditOptions{ + IsLatest: boolPtr(true), + }, + httpStubs: func(t *testing.T, reg *httpmock.Registry) { + mockSuccessfulEditResponse(reg, func(params map[string]interface{}) { + assert.Equal(t, map[string]interface{}{ + "tag_name": "v1.2.3", + "make_latest": "true", + }, params) + }) + }, + wantStdout: "https://github.com/OWNER/REPO/releases/tag/v1.2.3\n", + wantStderr: "", + }, { name: "edit the release name (empty)", isTTY: true,