mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-27 22:09:53 +02:00
@@ -17,7 +17,6 @@ package releaser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -25,22 +24,56 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
)
|
||||
|
||||
const commitPrefix = "releaser:"
|
||||
|
||||
// New initialises a ReleaseHandler.
|
||||
func New(skipPush, try bool, step int) (*ReleaseHandler, error) {
|
||||
if step < 1 || step > 2 {
|
||||
return nil, fmt.Errorf("step must be 1 or 2")
|
||||
}
|
||||
|
||||
prefix := "release-"
|
||||
branch, err := git("rev-parse", "--abbrev-ref", "HEAD")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !strings.HasPrefix(branch, prefix) {
|
||||
return nil, fmt.Errorf("branch %q is not a release branch", branch)
|
||||
}
|
||||
|
||||
logf("Branch: %s\n", branch)
|
||||
|
||||
version := strings.TrimPrefix(branch, prefix)
|
||||
version = strings.TrimPrefix(version, "v")
|
||||
rh := &ReleaseHandler{branchVersion: version, skipPush: skipPush, try: try, step: step}
|
||||
|
||||
if try {
|
||||
rh.git = func(args ...string) (string, error) {
|
||||
logln("git", strings.Join(args, " "))
|
||||
return "", nil
|
||||
}
|
||||
} else {
|
||||
rh.git = git
|
||||
}
|
||||
|
||||
return rh, nil
|
||||
}
|
||||
|
||||
// ReleaseHandler provides functionality to release a new version of Hugo.
|
||||
// Test this locally without doing an actual release:
|
||||
// go run -tags release main.go release --skip-publish --try -r 0.90.0
|
||||
// Or a variation of the above -- the skip-publish flag makes sure that any changes are performed to the local Git only.
|
||||
type ReleaseHandler struct {
|
||||
cliVersion string
|
||||
branchVersion string
|
||||
|
||||
skipPublish bool
|
||||
// 1 or 2.
|
||||
step int
|
||||
|
||||
// No remote pushes.
|
||||
skipPush bool
|
||||
|
||||
// Just simulate, no actual changes.
|
||||
try bool
|
||||
@@ -48,144 +81,48 @@ type ReleaseHandler struct {
|
||||
git func(args ...string) (string, error)
|
||||
}
|
||||
|
||||
func (r ReleaseHandler) calculateVersions() (hugo.Version, hugo.Version) {
|
||||
newVersion := hugo.MustParseVersion(r.cliVersion)
|
||||
finalVersion := newVersion.Next()
|
||||
finalVersion.PatchLevel = 0
|
||||
|
||||
if newVersion.Suffix != "-test" {
|
||||
newVersion.Suffix = ""
|
||||
}
|
||||
|
||||
finalVersion.Suffix = "-DEV"
|
||||
|
||||
return newVersion, finalVersion
|
||||
}
|
||||
|
||||
// New initialises a ReleaseHandler.
|
||||
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) {
|
||||
fmt.Println("git", strings.Join(args, " "))
|
||||
return "", nil
|
||||
}
|
||||
} else {
|
||||
rh.git = git
|
||||
}
|
||||
|
||||
return rh
|
||||
}
|
||||
|
||||
// Run creates a new release.
|
||||
func (r *ReleaseHandler) Run() error {
|
||||
if os.Getenv("GITHUB_TOKEN") == "" {
|
||||
return errors.New("GITHUB_TOKEN not set, create one here with the repo scope selected: https://github.com/settings/tokens/new")
|
||||
}
|
||||
|
||||
fmt.Printf("Start release from %q\n", wd())
|
||||
|
||||
newVersion, finalVersion := r.calculateVersions()
|
||||
|
||||
version := newVersion.String()
|
||||
tag := "v" + version
|
||||
isPatch := newVersion.PatchLevel > 0
|
||||
mainVersion := newVersion
|
||||
mainVersion.PatchLevel = 0
|
||||
|
||||
// Exit early if tag already exists
|
||||
exists, err := tagExists(tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.gitPush()
|
||||
|
||||
if exists {
|
||||
return fmt.Errorf("tag %q already exists", tag)
|
||||
}
|
||||
|
||||
var changeLogFromTag string
|
||||
|
||||
if newVersion.PatchLevel == 0 {
|
||||
// There may have been patch releases between, so set the tag explicitly.
|
||||
changeLogFromTag = "v" + newVersion.Prev().String()
|
||||
exists, _ := tagExists(changeLogFromTag)
|
||||
if !exists {
|
||||
// fall back to one that exists.
|
||||
changeLogFromTag = ""
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
gitCommits gitInfos
|
||||
gitCommitsDocs gitInfos
|
||||
)
|
||||
|
||||
defer r.gitPush() // TODO(bep)
|
||||
|
||||
gitCommits, err = getGitInfos(changeLogFromTag, "hugo", "", !r.try)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(bep) explicit tag?
|
||||
gitCommitsDocs, err = getGitInfos("", "hugoDocs", "../hugoDocs", !r.try)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
releaseNotesFile, err := r.writeReleaseNotesToTemp(version, isPatch, gitCommits, gitCommitsDocs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := r.git("add", releaseNotesFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
commitMsg := fmt.Sprintf("%s Add release notes for %s", commitPrefix, newVersion)
|
||||
commitMsg += "\n[ci skip]"
|
||||
|
||||
if _, err := r.git("commit", "-m", commitMsg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 _, err := r.git("tag", "-a", tag, "-m", fmt.Sprintf("%s %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !r.skipPublish {
|
||||
if _, err := r.git("push", "origin", tag); err != nil {
|
||||
if r.step == 1 {
|
||||
if err := r.bumpVersions(newVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.release(releaseNotesFile); 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
|
||||
}
|
||||
|
||||
// The above commit will be the target for this release, so print it to the console in a env friendly way.
|
||||
sha, err := git("rev-parse", "HEAD")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Hugoreleaser will do the actual release using these values.
|
||||
if err := r.replaceInFile("hugoreleaser.env",
|
||||
`HUGORELEASER_TAG=(\S*)`, "HUGORELEASER_TAG="+tag,
|
||||
`HUGORELEASER_COMMITISH=(\S*)`, "HUGORELEASER_COMMITISH="+sha,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
logf("HUGORELEASER_TAG=%s\n", tag)
|
||||
logf("HUGORELEASER_COMMITISH=%s\n", sha)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := r.bumpVersions(finalVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !r.try {
|
||||
// No longer needed.
|
||||
if err := os.Remove(releaseNotesFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := r.git("commit", "-a", "-m", fmt.Sprintf("%s Prepare repository for %s\n\n[ci skip]", commitPrefix, finalVersion)); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -193,36 +130,6 @@ 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...")
|
||||
return nil
|
||||
}
|
||||
|
||||
args := []string{"--parallelism", "2", "--timeout", "120m", "--rm-dist", "--release-notes", releaseNotesFile}
|
||||
if r.skipPublish {
|
||||
args = append(args, "--skip-publish")
|
||||
}
|
||||
|
||||
cmd, _ := hexec.SafeCommand("goreleaser", args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("goreleaser failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ReleaseHandler) bumpVersions(ver hugo.Version) error {
|
||||
toDev := ""
|
||||
|
||||
@@ -264,6 +171,29 @@ func (r *ReleaseHandler) bumpVersions(ver hugo.Version) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r ReleaseHandler) calculateVersions() (hugo.Version, hugo.Version) {
|
||||
newVersion := hugo.MustParseVersion(r.branchVersion)
|
||||
finalVersion := newVersion.Next()
|
||||
finalVersion.PatchLevel = 0
|
||||
|
||||
if newVersion.Suffix != "-test" {
|
||||
newVersion.Suffix = ""
|
||||
}
|
||||
|
||||
finalVersion.Suffix = "-DEV"
|
||||
|
||||
return newVersion, finalVersion
|
||||
}
|
||||
|
||||
func (r *ReleaseHandler) gitPush() {
|
||||
if r.skipPush {
|
||||
return
|
||||
}
|
||||
if _, err := r.git("push", "origin", "HEAD"); err != nil {
|
||||
log.Fatal("push failed:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ReleaseHandler) replaceInFile(filename string, oldNew ...string) error {
|
||||
filename = filepath.FromSlash(filename)
|
||||
fi, err := os.Stat(filename)
|
||||
@@ -272,11 +202,11 @@ func (r *ReleaseHandler) replaceInFile(filename string, oldNew ...string) error
|
||||
}
|
||||
|
||||
if r.try {
|
||||
fmt.Printf("Replace in %q: %q\n", filename, oldNew)
|
||||
logf("Replace in %q: %q\n", filename, oldNew)
|
||||
return nil
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
b, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -287,18 +217,22 @@ func (r *ReleaseHandler) replaceInFile(filename string, oldNew ...string) error
|
||||
newContent = re.ReplaceAllString(newContent, oldNew[i+1])
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(filename, []byte(newContent), fi.Mode())
|
||||
return os.WriteFile(filename, []byte(newContent), fi.Mode())
|
||||
}
|
||||
|
||||
func isCI() bool {
|
||||
return os.Getenv("CI") != ""
|
||||
}
|
||||
|
||||
func wd() string {
|
||||
p, err := os.Getwd()
|
||||
func git(args ...string) (string, error) {
|
||||
cmd, _ := hexec.SafeCommand("git", args...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return "", fmt.Errorf("git failed: %q: %q (%q)", err, out, args)
|
||||
}
|
||||
return p
|
||||
|
||||
return string(out), nil
|
||||
}
|
||||
|
||||
func logf(format string, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, format, args...)
|
||||
}
|
||||
|
||||
func logln(args ...interface{}) {
|
||||
fmt.Fprintln(os.Stderr, args...)
|
||||
}
|
||||
|
Reference in New Issue
Block a user