Set up Hugo release flow on CircleCI

This rewrites the release logic to use CircleCI 2.0 and its approve workflow in combination with the state of the release notes to determine what to do next.

Fixes #3779
This commit is contained in:
Bjørn Erik Pedersen
2017-09-10 17:14:02 +02:00
committed by GitHub
parent f4bf214137
commit d2249c5099
11 changed files with 197 additions and 86 deletions

View File

@@ -31,15 +31,18 @@ import (
const commitPrefix = "releaser:"
type releaseNotesState int
const (
releaseNotesNone = iota
releaseNotesCreated
releaseNotesReady
)
// ReleaseHandler provides functionality to release a new version of Hugo.
type ReleaseHandler struct {
cliVersion string
// If set, we do the releases in 3 steps:
// 1: Create and write a draft release note
// 2: Prepare files for new version
// 3: Release
step int
skipPublish bool
// Just simulate, no actual changes.
@@ -48,29 +51,14 @@ type ReleaseHandler struct {
git func(args ...string) (string, error)
}
func (r ReleaseHandler) shouldRelease() bool {
return r.step < 1 || r.shouldContinue()
}
func (r ReleaseHandler) shouldContinue() bool {
return r.step >= 3
}
func (r ReleaseHandler) shouldPrepareReleasenotes() bool {
return r.step < 1 || r.step == 1
}
func (r ReleaseHandler) shouldPrepareVersions() bool {
return r.step < 1 || r.step == 2 || r.step > 3
}
func (r ReleaseHandler) calculateVersions() (helpers.HugoVersion, helpers.HugoVersion) {
newVersion := helpers.MustParseHugoVersion(r.cliVersion)
finalVersion := newVersion
finalVersion.PatchLevel = 0
newVersion.Suffix = ""
if newVersion.Suffix != "-test" {
newVersion.Suffix = ""
}
if newVersion.PatchLevel == 0 {
finalVersion = finalVersion.Next()
@@ -82,8 +70,11 @@ func (r ReleaseHandler) calculateVersions() (helpers.HugoVersion, helpers.HugoVe
}
// New initialises a ReleaseHandler.
func New(version string, step int, skipPublish, try bool) *ReleaseHandler {
rh := &ReleaseHandler{cliVersion: version, step: step, skipPublish: skipPublish, try: try}
func New(version string, skipPublish, try bool) *ReleaseHandler {
// When triggered from CI release branch
version = strings.TrimPrefix(version, "release-")
version = strings.TrimPrefix(version, "v")
rh := &ReleaseHandler{cliVersion: version, skipPublish: skipPublish, try: try}
if try {
rh.git = func(args ...string) (string, error) {
@@ -133,20 +124,38 @@ func (r *ReleaseHandler) Run() error {
var (
gitCommits gitInfos
gitCommitsDocs gitInfos
relNotesState releaseNotesState
)
if r.shouldPrepareReleasenotes() || r.shouldRelease() {
gitCommits, err = getGitInfos(changeLogFromTag, "", !r.try)
relNotesState, err = r.releaseNotesState(version)
if err != nil {
return err
}
prepareRelaseNotes := relNotesState == releaseNotesNone
shouldRelease := relNotesState == releaseNotesReady
defer r.gitPush() // TODO(bep)
if prepareRelaseNotes || shouldRelease {
gitCommits, err = getGitInfos(changeLogFromTag, "hugo", "", !r.try)
if err != nil {
return err
}
gitCommitsDocs, err = getGitInfos(changeLogFromTag, "../hugoDocs", !r.try)
// TODO(bep) explicit tag?
gitCommitsDocs, err = getGitInfos("", "hugoDocs", "../hugoDocs", !r.try)
if err != nil {
return err
}
}
if r.shouldPrepareReleasenotes() {
if relNotesState == releaseNotesCreated {
fmt.Println("Release notes created, but not ready. Reneame to *-ready.md to continue ...")
return nil
}
if prepareRelaseNotes {
releaseNotesFile, err := r.writeReleaseNotesToTemp(version, gitCommits, gitCommitsDocs)
if err != nil {
return err
@@ -155,33 +164,30 @@ func (r *ReleaseHandler) Run() error {
if _, err := r.git("add", releaseNotesFile); err != nil {
return err
}
if _, err := r.git("commit", "-m", fmt.Sprintf("%s Add release notes draft for %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
if _, err := r.git("commit", "-m", fmt.Sprintf("%s Add release notes draft for %s\n\nRename to *-ready.md to continue. [ci skip]", commitPrefix, newVersion)); err != nil {
return err
}
}
if r.shouldPrepareVersions() {
// For docs, for now we assume that:
// The /docs subtree is up to date and ready to go.
// The hugoDocs/dev and hugoDocs/master must be merged manually after release.
// TODO(bep) improve this when we see how it works.
if err := r.bumpVersions(newVersion); err != nil {
return err
}
if _, err := r.git("commit", "-a", "-m", fmt.Sprintf("%s Bump versions for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
return err
}
}
if !r.shouldRelease() {
fmt.Printf("Skip release ... Use --state=%d for next or --state=4 to finish\n", r.step+1)
if !shouldRelease {
fmt.Printf("Skip release ... ")
return nil
}
releaseNotesFile := getReleaseNotesDocsTempFilename(version)
// For docs, for now we assume that:
// The /docs subtree is up to date and ready to go.
// The hugoDocs/dev and hugoDocs/master must be merged manually after release.
// TODO(bep) improve this when we see how it works.
if err := r.bumpVersions(newVersion); err != nil {
return err
}
if _, err := r.git("commit", "-a", "-m", fmt.Sprintf("%s Bump versions for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
return err
}
releaseNotesFile := getReleaseNotesDocsTempFilename(version, true)
// Write the release notes to the docs site as well.
docFile, err := r.writeReleaseNotesToDocs(version, releaseNotesFile)
@@ -196,12 +202,14 @@ func (r *ReleaseHandler) Run() error {
return err
}
if _, err := r.git("tag", "-a", tag, "-m", fmt.Sprintf("%s %s [ci deploy]", commitPrefix, newVersion)); err != nil {
if _, err := r.git("tag", "-a", tag, "-m", fmt.Sprintf("%s %s [ci skip]", commitPrefix, newVersion)); err != nil {
return err
}
if _, err := r.git("push", "origin", tag); err != nil {
return err
if !r.skipPublish {
if _, err := r.git("push", "origin", tag); err != nil {
return err
}
}
if err := r.release(releaseNotesFile); err != nil {
@@ -226,6 +234,15 @@ func (r *ReleaseHandler) Run() error {
return nil
}
func (r *ReleaseHandler) gitPush() {
if r.skipPublish {
return
}
if _, err := r.git("push", "origin", "HEAD"); err != nil {
log.Fatal("push failed:", err)
}
}
func (r *ReleaseHandler) release(releaseNotesFile string) error {
if r.try {
fmt.Println("Skip goreleaser...")
@@ -243,19 +260,16 @@ func (r *ReleaseHandler) release(releaseNotesFile string) error {
}
func (r *ReleaseHandler) bumpVersions(ver helpers.HugoVersion) error {
fromDev := ""
toDev := ""
if ver.Suffix != "" {
toDev = "-DEV"
} else {
fromDev = "-DEV"
toDev = ver.Suffix
}
if err := r.replaceInFile("helpers/hugo.go",
`Number:(\s{4,})(.*),`, fmt.Sprintf(`Number:${1}%.2f,`, ver.Number),
`PatchLevel:(\s*)(.*),`, fmt.Sprintf(`PatchLevel:${1}%d,`, ver.PatchLevel),
fmt.Sprintf(`Suffix:(\s{4,})"%s",`, fromDev), fmt.Sprintf(`Suffix:${1}"%s",`, toDev)); err != nil {
`Suffix:(\s{4,})".*",`, fmt.Sprintf(`Suffix:${1}"%s",`, toDev)); err != nil {
return err
}
@@ -325,3 +339,7 @@ func hugoFilepath(filename string) string {
}
return filepath.Join(pwd, filename)
}
func isCI() bool {
return os.Getenv("CI") != ""
}