in cmd/krel/cmd/release_notes.go [1029:1208]
func fixReleaseNotes(workDir string, releaseNotes *notes.ReleaseNotes) error {
// Get data to record the session
userEmail, err := git.GetUserEmail()
if err != nil {
return errors.Wrap(err, "getting local user's email")
}
userName, err := git.GetUserName()
if err != nil {
return errors.Wrap(err, "getting local user's name")
}
// Check the workDir before going further
if !util.Exists(workDir) {
return errors.New("map directory does not exist")
}
// Create the new session struct
session := &sessionData{
UserEmail: userEmail,
UserName: userName,
Date: time.Now().UTC().Unix(),
Path: filepath.Join(workDir, mapsSessionDirectory),
}
// Read the list of all PRs we've processed so far
pullRequestChecklist, err := readFixSessions(filepath.Join(workDir, mapsSessionDirectory))
if err != nil {
return errors.Wrapf(err, "reading previous session data")
}
// Greet the user with basic instructions
greetingMessage := "\nWelcome to the Kubernetes Release Notes editing tool!\n\n"
greetingMessage += "This tool will allow you to review and edit all the release\n"
greetingMessage += "notes submitted by the Kubernetes contributors before publishing\n"
greetingMessage += "the updated draft.\n\n"
greetingMessage += "The flow will show each of the release notes that need to be\n"
greetingMessage += "reviewed once and you can choose to edit it or not.\n\n"
greetingMessage += "After you choose, it will be marked as reviewed and will not\n"
greetingMessage += "be shown during the next sessions unless you choose to do a\n"
greetingMessage += "full review of all notes.\n\n"
greetingMessage += "You can hit Ctrl+C at any time to exit the review process\n"
greetingMessage += "and submit the draft PR with the revisions made so far.\n\n"
fmt.Print(greetingMessage)
// Ask the user if they want to continue the last session o fix all notes
continueFromLastSession := true
if len(pullRequestChecklist) > 0 {
_, continueFromLastSession, err = util.Ask("Would you like to continue from the last session? (Y/n)", "y:Y:yes|n:N:no|y", 10)
} else {
_, _, err = util.Ask("Press enter to start editing", "y:Y:yes|n:N:no|y", 10)
}
if err != nil {
return errors.Wrap(err, "asking to retrieve last session")
}
// Bring up the provider
provider, err := notes.NewProviderFromInitString(workDir)
if err != nil {
return errors.Wrap(err, "while getting map provider for current notes")
}
const (
spacer = " │ "
)
// Cycle all gathered release notes
for pr, note := range releaseNotes.ByPR() {
contentHash, err := note.ContentHash()
noteReviewed := false
if err != nil {
return errors.Wrapf(err, "getting the content hash for PR#%d", pr)
}
// We'll skip editing if the Releas Note has been reviewed
if _, ok := pullRequestChecklist[pr]; ok &&
// and if we chose not to edit all
continueFromLastSession &&
// and if the not has not been modified in GutHub
contentHash == pullRequestChecklist[pr] {
logrus.Debugf("Pull Request %d already reviewed", pr)
continue
}
title := fmt.Sprintf("Release Note for PR %d:", pr)
fmt.Println(nl + title)
fmt.Println(strings.Repeat("=", len(title)))
fmt.Printf("Pull Request URL: %skubernetes/kubernetes/pull/%d%s", github.GitHubURL, pr, nl)
noteMaps, err := provider.GetMapsForPR(pr)
if err != nil {
return errors.Wrapf(err, "while getting map for PR #%d", pr)
}
// Capture the original note values to compare
originalNote := ¬es.ReleaseNote{
Text: note.Text,
Author: note.Author,
Areas: note.Areas,
Kinds: note.Kinds,
SIGs: note.SIGs,
Feature: note.Feature,
ActionRequired: note.ActionRequired,
Documentation: note.Documentation,
DoNotPublish: note.DoNotPublish,
}
if noteMaps != nil {
fmt.Println("✨ Note contents was previously modified with a map")
for _, noteMap := range noteMaps {
if err := note.ApplyMap(noteMap, true); err != nil {
return errors.Wrapf(err, "applying notemap for PR #%d", pr)
}
}
}
fmt.Println(pointIfChanged("Author", note.Author, originalNote.Author), "@"+note.Author)
fmt.Println(pointIfChanged("SIGs", note.SIGs, originalNote.SIGs), note.SIGs)
fmt.Println(pointIfChanged("Kinds", note.Kinds, originalNote.Kinds), note.Kinds)
fmt.Println(pointIfChanged("Areas", note.Areas, originalNote.Areas), note.Areas)
fmt.Println(pointIfChanged("Feature", note.Feature, originalNote.Feature), note.Feature)
fmt.Println(pointIfChanged("ActionRequired", note.ActionRequired, originalNote.ActionRequired), note.ActionRequired)
fmt.Println(pointIfChanged("DoNotPublish", note.DoNotPublish, originalNote.DoNotPublish), note.DoNotPublish)
// TODO: Implement note.Documentation
// Wrap the note for better readability on the terminal
fmt.Println(pointIfChanged("Text", note.Text, originalNote.Text))
text := util.WrapText(note.Text, 80)
fmt.Println(spacer + strings.ReplaceAll(text, nl, nl+spacer))
_, choice, err := util.Ask(fmt.Sprintf("\n- Fix note for PR #%d? (y/N)", note.PrNumber), "y:Y:yes|n:N:no|n", 10)
if err != nil {
// If the user cancelled with ctr+c exit and continue the PR flow
if err.(util.UserInputError).IsCtrlC() {
logrus.Info("Input cancelled, exiting edit flow")
return nil
}
return errors.Wrap(err, "while asking to edit release note")
}
noteReviewed = true
if choice {
for {
retry, err := editReleaseNote(pr, workDir, originalNote, note)
if err == nil {
break
}
// If it's a user error (like yaml error) we can try again
if retry {
logrus.Error(err)
_, retryEditingChoice, err := util.Ask(
fmt.Sprintf("\n- An error occurred while editing PR #%d. Try again?", note.PrNumber),
"y:yes|n:no", 10,
)
if err != nil {
return errors.Wrap(err, "while asking to re-edit release note")
}
// If user chooses not to fix the faulty yaml, do not mark as fixed
if !retryEditingChoice {
noteReviewed = false
break
}
} else {
return errors.Wrap(err, "while editing release note")
}
}
}
// If the note was reviewed, add the PR to the session file:
if noteReviewed {
pullRequestChecklist[note.PrNumber] = contentHash
session.PullRequests = append(session.PullRequests, struct {
Number int `json:"nr"`
Hash string `json:"hash"`
}{
Number: note.PrNumber,
Hash: contentHash,
})
if err := session.Save(); err != nil {
return errors.Wrap(err, "while saving editing session data")
}
}
}
return nil
}