11package create
22
33import (
4+ "bytes"
45 "fmt"
56 "io/ioutil"
67 "net/http"
8+ "os"
9+ "os/exec"
710 "strings"
811
912 "github.com/AlecAivazis/survey/v2"
1013 "github.com/cli/cli/internal/config"
1114 "github.com/cli/cli/internal/ghrepo"
15+ "github.com/cli/cli/internal/run"
1216 "github.com/cli/cli/pkg/cmd/release/shared"
1317 "github.com/cli/cli/pkg/cmdutil"
1418 "github.com/cli/cli/pkg/iostreams"
1519 "github.com/cli/cli/pkg/prompt"
1620 "github.com/cli/cli/pkg/surveyext"
21+ "github.com/cli/cli/pkg/text"
1722 "github.com/spf13/cobra"
1823)
1924
@@ -35,6 +40,11 @@ type CreateOptions struct {
3540
3641 // for interactive flow
3742 SubmitAction string
43+ // for interactive flow
44+ ReleaseNotesAction string
45+
46+ // the value from the --repo flag
47+ RepoOverride string
3848
3949 // maximum number of simultaneous uploads
4050 Concurrency int
@@ -56,6 +66,7 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co
5666 RunE : func (cmd * cobra.Command , args []string ) error {
5767 // support `-R, --repo` override
5868 opts .BaseRepo = f .BaseRepo
69+ opts .RepoOverride , _ = cmd .Flags ().GetString ("repo" )
5970
6071 opts .TagName = args [0 ]
6172
@@ -116,6 +127,35 @@ func createRun(opts *CreateOptions) error {
116127 return err
117128 }
118129
130+ var tagDescription string
131+ var generatedChangelog string
132+ if opts .RepoOverride == "" {
133+ headRef := opts .TagName
134+ tagDescription , _ = gitTagInfo (opts .TagName )
135+ if tagDescription == "" {
136+ if opts .Target != "" {
137+ // TODO: use the remote-tracking version of the branch ref
138+ headRef = opts .Target
139+ } else {
140+ headRef = "HEAD"
141+ }
142+ }
143+
144+ if prevTag , err := detectPreviousTag (headRef ); err == nil {
145+ commits , _ := changelogForRange (fmt .Sprintf ("%s..%s" , prevTag , headRef ))
146+ generatedChangelog = generateChangelog (commits )
147+ }
148+ }
149+
150+ editorOptions := []string {"Write my own" }
151+ if generatedChangelog != "" {
152+ editorOptions = append (editorOptions , "Write using commit log as template" )
153+ }
154+ if tagDescription != "" {
155+ editorOptions = append (editorOptions , "Write using git tag message as template" )
156+ }
157+ editorOptions = append (editorOptions , "Leave blank" )
158+
119159 qs := []* survey.Question {
120160 {
121161 Name : "name" ,
@@ -125,18 +165,46 @@ func createRun(opts *CreateOptions) error {
125165 },
126166 },
127167 {
128- Name : "body" ,
129- Prompt : & surveyext.GhEditor {
130- BlankAllowed : true ,
131- EditorCommand : editorCommand ,
132- Editor : & survey.Editor {
133- Message : "Release notes" ,
134- FileName : "*.md" ,
135- Default : opts .Body ,
136- HideDefault : true ,
137- },
168+ Name : "releaseNotesAction" ,
169+ Prompt : & survey.Select {
170+ Message : "Release notes" ,
171+ Options : editorOptions ,
138172 },
139173 },
174+ }
175+ err = prompt .SurveyAsk (qs , opts )
176+ if err != nil {
177+ return fmt .Errorf ("could not prompt: %w" , err )
178+ }
179+
180+ var openEditor bool
181+ var editorContents string
182+
183+ switch opts .ReleaseNotesAction {
184+ case "Write my own" :
185+ openEditor = true
186+ case "Write using commit log as template" :
187+ openEditor = true
188+ editorContents = generatedChangelog
189+ case "Write using git tag message as template" :
190+ openEditor = true
191+ editorContents = tagDescription
192+ case "Leave blank" :
193+ openEditor = false
194+ default :
195+ return fmt .Errorf ("invalid action: %v" , opts .ReleaseNotesAction )
196+ }
197+
198+ if openEditor {
199+ // TODO: consider using iostreams here
200+ text , err := surveyext .Edit (editorCommand , "*.md" , editorContents , os .Stdin , os .Stdout , os .Stderr , nil )
201+ if err != nil {
202+ return err
203+ }
204+ opts .Body = text
205+ }
206+
207+ qs = []* survey.Question {
140208 {
141209 Name : "prerelease" ,
142210 Prompt : & survey.Confirm {
@@ -169,6 +237,8 @@ func createRun(opts *CreateOptions) error {
169237 opts .Draft = true
170238 case "Cancel" :
171239 return cmdutil .SilentError
240+ default :
241+ return fmt .Errorf ("invalid action: %v" , opts .SubmitAction )
172242 }
173243 }
174244
@@ -220,3 +290,61 @@ func createRun(opts *CreateOptions) error {
220290
221291 return nil
222292}
293+
294+ func gitTagInfo (tagName string ) (string , error ) {
295+ cmd := exec .Command ("git" , "tag" , "--list" , tagName , "--format=%(contents:subject)%0a%0a%(contents:body)" )
296+ b , err := run .PrepareCmd (cmd ).Output ()
297+ return string (b ), err
298+ }
299+
300+ func detectPreviousTag (headRef string ) (string , error ) {
301+ cmd := exec .Command ("git" , "describe" , "--tags" , "--abbrev=0" , fmt .Sprintf ("%s^" , headRef ))
302+ b , err := run .PrepareCmd (cmd ).Output ()
303+ return strings .TrimSpace (string (b )), err
304+ }
305+
306+ type logEntry struct {
307+ Subject string
308+ Body string
309+ }
310+
311+ func changelogForRange (refRange string ) ([]logEntry , error ) {
312+ cmd := exec .Command ("git" , "-c" , "log.ShowSignature=false" , "log" , "--first-parent" , "--reverse" , "--pretty=format:%B%x00" , refRange )
313+ b , err := run .PrepareCmd (cmd ).Output ()
314+ if err != nil {
315+ return nil , err
316+ }
317+
318+ var entries []logEntry
319+ for _ , cb := range bytes .Split (b , []byte {'\000' }) {
320+ c := strings .ReplaceAll (string (cb ), "\r \n " , "\n " )
321+ c = strings .TrimPrefix (c , "\n " )
322+ if len (c ) == 0 {
323+ continue
324+ }
325+ parts := strings .SplitN (c , "\n \n " , 2 )
326+ var body string
327+ subject := strings .ReplaceAll (parts [0 ], "\n " , " " )
328+ if len (parts ) > 1 {
329+ body = parts [1 ]
330+ }
331+ entries = append (entries , logEntry {
332+ Subject : subject ,
333+ Body : body ,
334+ })
335+ }
336+
337+ return entries , nil
338+ }
339+
340+ func generateChangelog (commits []logEntry ) string {
341+ var parts []string
342+ for _ , c := range commits {
343+ // TODO: consider rendering "Merge pull request #123 from owner/branch" differently
344+ parts = append (parts , fmt .Sprintf ("* %s" , c .Subject ))
345+ if c .Body != "" {
346+ parts = append (parts , text .Indent (c .Body , " " ))
347+ }
348+ }
349+ return strings .Join (parts , "\n \n " )
350+ }
0 commit comments