diff --git a/pkg/cmd/release/create/create.go b/pkg/cmd/release/create/create.go index a5a02fa31..07e40b708 100644 --- a/pkg/cmd/release/create/create.go +++ b/pkg/cmd/release/create/create.go @@ -59,6 +59,7 @@ type CreateOptions struct { GenerateNotes bool NotesStartTag string VerifyTag bool + NotesFromTag bool } func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Command { @@ -92,6 +93,7 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co To create a release from an annotated git tag, first create one locally with git, push the tag to GitHub, then run this command. + You may also use %[1]s--notes-from-tag%[1]s to create it noninteractively. When using automatically generated release notes, a release title will also be automatically generated unless a title was explicitly passed. Additional release notes can be prepended to @@ -146,9 +148,13 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co return cmdutil.FlagErrorf("tag required when not running interactively") } + if opts.NotesFromTag && (opts.GenerateNotes || opts.NotesStartTag != "") { + return cmdutil.FlagErrorf("using `--notes-from-tag` with `--generate-notes` or `notes-start-tag` is not supported") + } + opts.Concurrency = 5 - opts.BodyProvided = cmd.Flags().Changed("notes") || opts.GenerateNotes + opts.BodyProvided = cmd.Flags().Changed("notes") || opts.GenerateNotes || opts.NotesFromTag if notesFile != "" { b, err := cmdutil.ReadFile(notesFile, opts.IO.In) if err != nil { @@ -176,6 +182,7 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co 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)") cmd.Flags().BoolVarP(&opts.VerifyTag, "verify-tag", "", false, "Abort in case the git tag doesn't already exist in the remote repository") + cmd.Flags().BoolVarP(&opts.NotesFromTag, "notes-from-tag", "", false, "Automatically populate the release notes with the message in the tag specified by `gh release create []`") _ = cmdutil.RegisterBranchCompletionFlags(f.GitClient, cmd, "target") @@ -241,6 +248,7 @@ func createRun(opts *CreateOptions) error { var tagDescription string if opts.RepoOverride == "" { tagDescription, _ = gitTagInfo(opts.GitClient, opts.TagName) + remoteExists, err := remoteTagExists(httpClient, baseRepo, opts.TagName) // If there is a local tag with the same name as specified // the user may not want to create a new tag on the remote // as the local one might be annotated or signed. @@ -250,7 +258,6 @@ func createRun(opts *CreateOptions) error { // If a remote tag with the same name as specified exists already // then a new tag will not be created so ignore local tag status. if tagDescription != "" && !existingTag && opts.Target == "" && !opts.VerifyTag { - remoteExists, err := remoteTagExists(httpClient, baseRepo, opts.TagName) if err != nil { return err } @@ -259,6 +266,13 @@ func createRun(opts *CreateOptions) error { opts.TagName, ghrepo.FullName(baseRepo)) } } + + if opts.NotesFromTag { + if !remoteExists { + return fmt.Errorf("tag %s doesn't exist in the repo %s, cannot populate release notes with annotated git tag message using the `--notes-from-tag` flag", + opts.TagName, ghrepo.FullName(baseRepo)) + } + } } if !opts.BodyProvided && opts.IO.CanPrompt() { @@ -427,6 +441,13 @@ func createRun(opts *CreateOptions) error { params["generate_release_notes"] = true } } + if opts.NotesFromTag { + if opts.Body == "" { + params["body"] = tagDescription + } else { + params["body"] = fmt.Sprintf("%s\n%s", opts.Body, tagDescription) + } + } hasAssets := len(opts.Assets) > 0 draftWhileUploading := false diff --git a/pkg/cmd/release/create/create_test.go b/pkg/cmd/release/create/create_test.go index 69188f593..1787c0931 100644 --- a/pkg/cmd/release/create/create_test.go +++ b/pkg/cmd/release/create/create_test.go @@ -321,6 +321,43 @@ func Test_NewCmdCreate(t *testing.T) { VerifyTag: true, }, }, + { + name: "tag and --notes-from-tag", + args: "v1.2.3 --notes-from-tag", + isTTY: false, + want: CreateOptions{ + TagName: "v1.2.3", + Target: "", + Name: "", + Body: "", + BodyProvided: true, + Draft: false, + Prerelease: false, + RepoOverride: "", + Concurrency: 5, + Assets: []*shared.AssetForUpload(nil), + GenerateNotes: false, + NotesFromTag: true, + }, + }, + { + name: "--notes-from-tag only ", + args: "--notes-from-tag", + isTTY: false, + wantErr: "tag required when not running interactively", + }, + { + name: "tag and --notes-from-tag and --generate-notes", + args: "v1.2.3 --notes-from-tag --generate-notes", + isTTY: false, + wantErr: "using `--notes-from-tag` with `--generate-notes` or `notes-start-tag` is not supported", + }, + { + name: "tag and --notes-from-tag and --notes-start-tag", + args: "v1.2.3 --notes-from-tag --notes-start-tag v1.2.3", + isTTY: false, + wantErr: "using `--notes-from-tag` with `--generate-notes` or `notes-start-tag` is not supported", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {