aboutsummaryrefslogtreecommitdiffhomepage
path: root/releaser
diff options
context:
space:
mode:
Diffstat (limited to 'releaser')
-rw-r--r--releaser/git.go253
-rw-r--r--releaser/git_test.go86
-rw-r--r--releaser/github.go143
-rw-r--r--releaser/github_test.go46
-rw-r--r--releaser/releasenotes_writer.go191
-rw-r--r--releaser/releasenotes_writer_test.go46
-rw-r--r--releaser/releaser.go254
7 files changed, 94 insertions, 925 deletions
diff --git a/releaser/git.go b/releaser/git.go
deleted file mode 100644
index ced363a9d..000000000
--- a/releaser/git.go
+++ /dev/null
@@ -1,253 +0,0 @@
-// Copyright 2017-present The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package releaser
-
-import (
- "fmt"
- "regexp"
- "sort"
- "strconv"
- "strings"
-
- "github.com/gohugoio/hugo/common/hexec"
-)
-
-var issueRe = regexp.MustCompile(`(?i)(?:Updates?|Closes?|Fix.*|See) #(\d+)`)
-
-type changeLog struct {
- Version string
- Notes gitInfos
- All gitInfos
- Docs gitInfos
-
- // Overall stats
- Repo *gitHubRepo
- ContributorCount int
- ThemeCount int
-}
-
-func newChangeLog(infos, docInfos gitInfos) *changeLog {
- log := &changeLog{
- Docs: docInfos,
- }
-
- for _, info := range infos {
- // TODO(bep) improve
- if regexp.MustCompile("(?i)deprecate|note").MatchString(info.Subject) {
- log.Notes = append(log.Notes, info)
- }
-
- log.All = append(log.All, info)
- info.Subject = strings.TrimSpace(info.Subject)
-
- }
-
- return log
-}
-
-type gitInfo struct {
- Hash string
- Author string
- Subject string
- Body string
-
- GitHubCommit *gitHubCommit
-}
-
-func (g gitInfo) Issues() []int {
- return extractIssues(g.Body)
-}
-
-func (g gitInfo) AuthorID() string {
- if g.GitHubCommit != nil {
- return g.GitHubCommit.Author.Login
- }
- return g.Author
-}
-
-func extractIssues(body string) []int {
- var i []int
- m := issueRe.FindAllStringSubmatch(body, -1)
- for _, mm := range m {
- issueID, err := strconv.Atoi(mm[1])
- if err != nil {
- continue
- }
- i = append(i, issueID)
- }
- return i
-}
-
-type gitInfos []gitInfo
-
-func git(args ...string) (string, error) {
- cmd, _ := hexec.SafeCommand("git", args...)
- out, err := cmd.CombinedOutput()
- if err != nil {
- return "", fmt.Errorf("git failed: %q: %q (%q)", err, out, args)
- }
- return string(out), nil
-}
-
-func getGitInfos(tag, repo, repoPath string, remote bool) (gitInfos, error) {
- return getGitInfosBefore("HEAD", tag, repo, repoPath, remote)
-}
-
-type countribCount struct {
- Author string
- GitHubAuthor gitHubAuthor
- Count int
-}
-
-func (c countribCount) AuthorLink() string {
- if c.GitHubAuthor.HTMLURL != "" {
- return fmt.Sprintf("[@%s](%s)", c.GitHubAuthor.Login, c.GitHubAuthor.HTMLURL)
- }
-
- if !strings.Contains(c.Author, "@") {
- return c.Author
- }
-
- return c.Author[:strings.Index(c.Author, "@")]
-}
-
-type contribCounts []countribCount
-
-func (c contribCounts) Less(i, j int) bool { return c[i].Count > c[j].Count }
-func (c contribCounts) Len() int { return len(c) }
-func (c contribCounts) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
-
-func (g gitInfos) ContribCountPerAuthor() contribCounts {
- var c contribCounts
-
- counters := make(map[string]countribCount)
-
- for _, gi := range g {
- authorID := gi.AuthorID()
- if count, ok := counters[authorID]; ok {
- count.Count = count.Count + 1
- counters[authorID] = count
- } else {
- var ghA gitHubAuthor
- if gi.GitHubCommit != nil {
- ghA = gi.GitHubCommit.Author
- }
- authorCount := countribCount{Count: 1, Author: gi.Author, GitHubAuthor: ghA}
- counters[authorID] = authorCount
- }
- }
-
- for _, v := range counters {
- c = append(c, v)
- }
-
- sort.Sort(c)
- return c
-}
-
-func getGitInfosBefore(ref, tag, repo, repoPath string, remote bool) (gitInfos, error) {
- client := newGitHubAPI(repo)
- var g gitInfos
-
- log, err := gitLogBefore(ref, tag, repoPath)
- if err != nil {
- return g, err
- }
-
- log = strings.Trim(log, "\n\x1e'")
- entries := strings.Split(log, "\x1e")
-
- for _, entry := range entries {
- items := strings.Split(entry, "\x1f")
- gi := gitInfo{}
-
- if len(items) > 0 {
- gi.Hash = items[0]
- }
- if len(items) > 1 {
- gi.Author = items[1]
- }
- if len(items) > 2 {
- gi.Subject = items[2]
- }
- if len(items) > 3 {
- gi.Body = items[3]
- }
-
- if remote && gi.Hash != "" {
- gc, err := client.fetchCommit(gi.Hash)
- if err == nil {
- gi.GitHubCommit = &gc
- }
- }
- g = append(g, gi)
- }
-
- return g, nil
-}
-
-// Ignore autogenerated commits etc. in change log. This is a regexp.
-const ignoredCommits = "snapcraft:|Merge commit|Squashed"
-
-func gitLogBefore(ref, tag, repoPath string) (string, error) {
- var prevTag string
- var err error
- if tag != "" {
- prevTag = tag
- } else {
- prevTag, err = gitVersionTagBefore(ref)
- if err != nil {
- return "", err
- }
- }
-
- defaultArgs := []string{"log", "-E", fmt.Sprintf("--grep=%s", ignoredCommits), "--invert-grep", "--pretty=format:%x1e%h%x1f%aE%x1f%s%x1f%b", "--abbrev-commit", prevTag + ".." + ref}
-
- var args []string
-
- if repoPath != "" {
- args = append([]string{"-C", repoPath}, defaultArgs...)
- } else {
- args = defaultArgs
- }
-
- log, err := git(args...)
- if err != nil {
- return ",", err
- }
-
- return log, err
-}
-
-func gitVersionTagBefore(ref string) (string, error) {
- return gitShort("describe", "--tags", "--abbrev=0", "--always", "--match", "v[0-9]*", ref+"^")
-}
-
-func gitShort(args ...string) (output string, err error) {
- output, err = git(args...)
- return strings.Replace(strings.Split(output, "\n")[0], "'", "", -1), err
-}
-
-func tagExists(tag string) (bool, error) {
- out, err := git("tag", "-l", tag)
- if err != nil {
- return false, err
- }
-
- if strings.Contains(out, tag) {
- return true, nil
- }
-
- return false, nil
-}
diff --git a/releaser/git_test.go b/releaser/git_test.go
deleted file mode 100644
index ff77eb8c6..000000000
--- a/releaser/git_test.go
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright 2017-present The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package releaser
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestGitInfos(t *testing.T) {
- c := qt.New(t)
- skipIfCI(t)
- infos, err := getGitInfos("v0.20", "hugo", "", false)
-
- c.Assert(err, qt.IsNil)
- c.Assert(len(infos) > 0, qt.Equals, true)
-}
-
-func TestIssuesRe(t *testing.T) {
- c := qt.New(t)
-
- body := `
-This is a commit message.
-
-Updates #123
-Fix #345
-closes #543
-See #456
- `
-
- issues := extractIssues(body)
-
- c.Assert(len(issues), qt.Equals, 4)
- c.Assert(issues[0], qt.Equals, 123)
- c.Assert(issues[2], qt.Equals, 543)
-
- bodyNoIssues := `
-This is a commit message without issue refs.
-
-But it has e #10 to make old regexp confused.
-Streets #20.
- `
-
- emptyIssuesList := extractIssues(bodyNoIssues)
- c.Assert(len(emptyIssuesList), qt.Equals, 0)
-}
-
-func TestGitVersionTagBefore(t *testing.T) {
- skipIfCI(t)
- c := qt.New(t)
- v1, err := gitVersionTagBefore("v0.18")
- c.Assert(err, qt.IsNil)
- c.Assert(v1, qt.Equals, "v0.17")
-}
-
-func TestTagExists(t *testing.T) {
- skipIfCI(t)
- c := qt.New(t)
- b1, err := tagExists("v0.18")
- c.Assert(err, qt.IsNil)
- c.Assert(b1, qt.Equals, true)
-
- b2, err := tagExists("adfagdsfg")
- c.Assert(err, qt.IsNil)
- c.Assert(b2, qt.Equals, false)
-}
-
-func skipIfCI(t *testing.T) {
- if isCI() {
- // Travis has an ancient git with no --invert-grep: https://github.com/travis-ci/travis-ci/issues/6328
- // Also Travis clones very shallowly, making some of the tests above shaky.
- t.Skip("Skip git test on Linux to make Travis happy.")
- }
-}
diff --git a/releaser/github.go b/releaser/github.go
deleted file mode 100644
index ffb880423..000000000
--- a/releaser/github.go
+++ /dev/null
@@ -1,143 +0,0 @@
-package releaser
-
-import (
- "encoding/json"
- "fmt"
- "io/ioutil"
- "net/http"
- "os"
- "strings"
-)
-
-var (
- gitHubCommitsAPI = "https://api.github.com/repos/gohugoio/REPO/commits/%s"
- gitHubRepoAPI = "https://api.github.com/repos/gohugoio/REPO"
- gitHubContributorsAPI = "https://api.github.com/repos/gohugoio/REPO/contributors"
-)
-
-type gitHubAPI struct {
- commitsAPITemplate string
- repoAPI string
- contributorsAPITemplate string
-}
-
-func newGitHubAPI(repo string) *gitHubAPI {
- return &gitHubAPI{
- commitsAPITemplate: strings.Replace(gitHubCommitsAPI, "REPO", repo, -1),
- repoAPI: strings.Replace(gitHubRepoAPI, "REPO", repo, -1),
- contributorsAPITemplate: strings.Replace(gitHubContributorsAPI, "REPO", repo, -1),
- }
-}
-
-type gitHubCommit struct {
- Author gitHubAuthor `json:"author"`
- HTMLURL string `json:"html_url"`
-}
-
-type gitHubAuthor struct {
- ID int `json:"id"`
- Login string `json:"login"`
- HTMLURL string `json:"html_url"`
- AvatarURL string `json:"avatar_url"`
-}
-
-type gitHubRepo struct {
- ID int `json:"id"`
- Name string `json:"name"`
- Description string `json:"description"`
- HTMLURL string `json:"html_url"`
- Stars int `json:"stargazers_count"`
- Contributors []gitHubContributor
-}
-
-type gitHubContributor struct {
- ID int `json:"id"`
- Login string `json:"login"`
- HTMLURL string `json:"html_url"`
- Contributions int `json:"contributions"`
-}
-
-func (g *gitHubAPI) fetchCommit(ref string) (gitHubCommit, error) {
- var commit gitHubCommit
-
- u := fmt.Sprintf(g.commitsAPITemplate, ref)
-
- req, err := http.NewRequest("GET", u, nil)
- if err != nil {
- return commit, err
- }
-
- err = doGitHubRequest(req, &commit)
-
- return commit, err
-}
-
-func (g *gitHubAPI) fetchRepo() (gitHubRepo, error) {
- var repo gitHubRepo
-
- req, err := http.NewRequest("GET", g.repoAPI, nil)
- if err != nil {
- return repo, err
- }
-
- err = doGitHubRequest(req, &repo)
- if err != nil {
- return repo, err
- }
-
- var contributors []gitHubContributor
- page := 0
- for {
- page++
- var currPage []gitHubContributor
- url := fmt.Sprintf(g.contributorsAPITemplate+"?page=%d", page)
-
- req, err = http.NewRequest("GET", url, nil)
- if err != nil {
- return repo, err
- }
-
- err = doGitHubRequest(req, &currPage)
- if err != nil {
- return repo, err
- }
- if len(currPage) == 0 {
- break
- }
-
- contributors = append(contributors, currPage...)
-
- }
-
- repo.Contributors = contributors
-
- return repo, err
-}
-
-func doGitHubRequest(req *http.Request, v any) error {
- addGitHubToken(req)
-
- resp, err := http.DefaultClient.Do(req)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
-
- if isError(resp) {
- b, _ := ioutil.ReadAll(resp.Body)
- return fmt.Errorf("GitHub lookup failed: %s", string(b))
- }
-
- return json.NewDecoder(resp.Body).Decode(v)
-}
-
-func isError(resp *http.Response) bool {
- return resp.StatusCode < 200 || resp.StatusCode > 299
-}
-
-func addGitHubToken(req *http.Request) {
- gitHubToken := os.Getenv("GITHUB_TOKEN")
- if gitHubToken != "" {
- req.Header.Add("Authorization", "token "+gitHubToken)
- }
-}
diff --git a/releaser/github_test.go b/releaser/github_test.go
deleted file mode 100644
index 23331bf38..000000000
--- a/releaser/github_test.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2017-present The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package releaser
-
-import (
- "fmt"
- "os"
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestGitHubLookupCommit(t *testing.T) {
- skipIfNoToken(t)
- c := qt.New(t)
- client := newGitHubAPI("hugo")
- commit, err := client.fetchCommit("793554108763c0984f1a1b1a6ee5744b560d78d0")
- c.Assert(err, qt.IsNil)
- fmt.Println(commit)
-}
-
-func TestFetchRepo(t *testing.T) {
- skipIfNoToken(t)
- c := qt.New(t)
- client := newGitHubAPI("hugo")
- repo, err := client.fetchRepo()
- c.Assert(err, qt.IsNil)
- fmt.Println(">>", len(repo.Contributors))
-}
-
-func skipIfNoToken(t *testing.T) {
- if os.Getenv("GITHUB_TOKEN") == "" {
- t.Skip("Skip test against GitHub as no GITHUB_TOKEN set.")
- }
-}
diff --git a/releaser/releasenotes_writer.go b/releaser/releasenotes_writer.go
deleted file mode 100644
index 5c50e4de4..000000000
--- a/releaser/releasenotes_writer.go
+++ /dev/null
@@ -1,191 +0,0 @@
-// Copyright 2017-present The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package releaser implements a set of utilities and a wrapper around Goreleaser
-// to help automate the Hugo release process.
-package releaser
-
-import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "os"
- "path/filepath"
- "strings"
- "text/template"
-)
-
-const (
- issueLinkTemplate = "#%d"
- linkTemplate = "[%s](%s)"
- releaseNotesMarkdownTemplatePatchRelease = `
-{{ if eq (len .All) 1 }}
-This is a bug-fix release with one important fix.
-{{ else }}
-This is a bug-fix release with a couple of important fixes.
-{{ end }}
-{{ range .All }}
-{{- if .GitHubCommit -}}
-* {{ .Subject }} {{ .Hash }} {{ . | author }} {{ range .Issues }}{{ . | issue }} {{ end }}
-{{ else -}}
-* {{ .Subject }} {{ range .Issues }}{{ . | issue }} {{ end }}
-{{ end -}}
-{{- end }}
-
-
-`
- releaseNotesMarkdownTemplate = `
-{{- $contribsPerAuthor := .All.ContribCountPerAuthor -}}
-{{- $docsContribsPerAuthor := .Docs.ContribCountPerAuthor -}}
-
-This release represents **{{ len .All }} contributions by {{ len $contribsPerAuthor }} contributors** to the main Hugo code base.
-
-{{- if gt (len $contribsPerAuthor) 3 -}}
-{{- $u1 := index $contribsPerAuthor 0 -}}
-{{- $u2 := index $contribsPerAuthor 1 -}}
-{{- $u3 := index $contribsPerAuthor 2 -}}
-{{- $u4 := index $contribsPerAuthor 3 -}}
-{{- $u1.AuthorLink }} leads the Hugo development with a significant amount of contributions, but also a big shoutout to {{ $u2.AuthorLink }}, {{ $u3.AuthorLink }}, and {{ $u4.AuthorLink }} for their ongoing contributions.
-And thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his ongoing work on keeping the themes site in pristine condition.
-{{ end }}
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **{{ len .Docs }} contributions by {{ len $docsContribsPerAuthor }} contributors**.
-{{- if gt (len $docsContribsPerAuthor) 3 -}}
-{{- $u1 := index $docsContribsPerAuthor 0 -}}
-{{- $u2 := index $docsContribsPerAuthor 1 -}}
-{{- $u3 := index $docsContribsPerAuthor 2 -}}
-{{- $u4 := index $docsContribsPerAuthor 3 }} A special thanks to {{ $u1.AuthorLink }}, {{ $u2.AuthorLink }}, {{ $u3.AuthorLink }}, and {{ $u4.AuthorLink }} for their work on the documentation site.
-{{ end }}
-
-Hugo now has:
-
-{{ with .Repo -}}
-* {{ .Stars }}+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* {{ len .Contributors }}+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-{{- end -}}
-{{ with .ThemeCount }}
-* {{ . }}+ [themes](http://themes.gohugo.io/)
-{{ end }}
-{{ with .Notes }}
-## Notes
-{{ template "change-section" . }}
-{{- end -}}
-{{ with .All }}
-## Changes
-{{ template "change-section" . }}
-{{ end }}
-
-{{ define "change-section" }}
-{{ range . }}
-{{- if .GitHubCommit -}}
-* {{ .Subject }} {{ .Hash }} {{ . | author }} {{ range .Issues }}{{ . | issue }} {{ end }}
-{{ else -}}
-* {{ .Subject }} {{ range .Issues }}{{ . | issue }} {{ end }}
-{{ end -}}
-{{- end }}
-{{ end }}
-`
-)
-
-var templateFuncs = template.FuncMap{
- "isPatch": func(c changeLog) bool {
- return !strings.HasSuffix(c.Version, "0")
- },
- "issue": func(id int) string {
- return fmt.Sprintf(issueLinkTemplate, id)
- },
- "commitURL": func(info gitInfo) string {
- if info.GitHubCommit.HTMLURL == "" {
- return ""
- }
- return fmt.Sprintf(linkTemplate, info.Hash, info.GitHubCommit.HTMLURL)
- },
- "author": func(info gitInfo) string {
- return "@" + info.GitHubCommit.Author.Login
- },
-}
-
-func writeReleaseNotes(version string, infosMain, infosDocs gitInfos, to io.Writer) error {
- client := newGitHubAPI("hugo")
- changes := newChangeLog(infosMain, infosDocs)
- changes.Version = version
- repo, err := client.fetchRepo()
- if err == nil {
- changes.Repo = &repo
- }
- themeCount, err := fetchThemeCount()
- if err == nil {
- changes.ThemeCount = themeCount
- }
-
- mtempl := releaseNotesMarkdownTemplate
-
- if !strings.HasSuffix(version, "0") {
- mtempl = releaseNotesMarkdownTemplatePatchRelease
- }
-
- tmpl, err := template.New("").Funcs(templateFuncs).Parse(mtempl)
- if err != nil {
- return err
- }
-
- err = tmpl.Execute(to, changes)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func fetchThemeCount() (int, error) {
- resp, err := http.Get("https://raw.githubusercontent.com/gohugoio/hugoThemesSiteBuilder/main/themes.txt")
- if err != nil {
- return 0, err
- }
- defer resp.Body.Close()
-
- b, _ := ioutil.ReadAll(resp.Body)
- return bytes.Count(b, []byte("\n")) - bytes.Count(b, []byte("#")), nil
-}
-
-func getReleaseNotesFilename(version string) string {
- return filepath.FromSlash(fmt.Sprintf("temp/%s-relnotes-ready.md", version))
-}
-
-func (r *ReleaseHandler) writeReleaseNotesToTemp(version string, isPatch bool, infosMain, infosDocs gitInfos) (string, error) {
- filename := getReleaseNotesFilename(version)
-
- var w io.WriteCloser
-
- if !r.try {
- f, err := os.Create(filename)
- if err != nil {
- return "", err
- }
-
- defer f.Close()
-
- w = f
-
- } else {
- w = os.Stdout
- }
-
- if err := writeReleaseNotes(version, infosMain, infosDocs, w); err != nil {
- return "", err
- }
-
- return filename, nil
-}
diff --git a/releaser/releasenotes_writer_test.go b/releaser/releasenotes_writer_test.go
deleted file mode 100644
index 7dcd0ccaa..000000000
--- a/releaser/releasenotes_writer_test.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2017-present The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package commands defines and implements command-line commands and flags
-// used by Hugo. Commands and flags are implemented using Cobra.
-
-package releaser
-
-import (
- "bytes"
- "fmt"
- "os"
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func _TestReleaseNotesWriter(t *testing.T) {
- skipIfNoToken(t)
- if os.Getenv("CI") != "" {
- // Travis has an ancient git with no --invert-grep: https://github.com/travis-ci/travis-ci/issues/6328
- t.Skip("Skip git test on CI to make Travis happy..")
- }
-
- c := qt.New(t)
-
- var b bytes.Buffer
-
- // TODO(bep) consider to query GitHub directly for the gitlog with author info, probably faster.
- infos, err := getGitInfosBefore("HEAD", "v0.89.0", "hugo", "", false)
- c.Assert(err, qt.IsNil)
-
- c.Assert(writeReleaseNotes("0.89.0", infos, infos, &b), qt.IsNil)
-
- fmt.Println(b.String())
-}
diff --git a/releaser/releaser.go b/releaser/releaser.go
index ebc344e98..fc16a2572 100644
--- a/releaser/releaser.go
+++ b/releaser/releaser.go
@@ -17,7 +17,6 @@ package releaser
import (
"fmt"
- "io/ioutil"
"log"
"os"
"path/filepath"
@@ -25,167 +24,105 @@ import (
"strings"
"github.com/gohugoio/hugo/common/hexec"
-
- "errors"
-
"github.com/gohugoio/hugo/common/hugo"
)
const commitPrefix = "releaser:"
-// 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
-
- skipPublish bool
-
- // Just simulate, no actual changes.
- try bool
-
- 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
+// 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")
+ }
- if newVersion.Suffix != "-test" {
- newVersion.Suffix = ""
+ 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)
}
- finalVersion.Suffix = "-DEV"
+ logf("Branch: %s\n", branch)
- 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(branch, prefix)
version = strings.TrimPrefix(version, "v")
- rh := &ReleaseHandler{cliVersion: version, skipPublish: skipPublish, try: try}
+ rh := &ReleaseHandler{branchVersion: version, skipPush: skipPush, try: try, step: step}
if try {
rh.git = func(args ...string) (string, error) {
- fmt.Println("git", strings.Join(args, " "))
+ logln("git", strings.Join(args, " "))
return "", nil
}
} else {
rh.git = git
}
- return rh
+ return rh, nil
}
-// 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")
- }
+// 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 {
+ branchVersion string
- fmt.Printf("Start release from %q\n", wd())
+ // 1 or 2.
+ step int
- newVersion, finalVersion := r.calculateVersions()
+ // No remote pushes.
+ skipPush bool
+
+ // Just simulate, no actual changes.
+ try bool
+
+ git func(args ...string) (string, error)
+}
+// Run creates a new release.
+func (r *ReleaseHandler) Run() error {
+ 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
- }
-
- if exists {
- return fmt.Errorf("tag %q already exists", tag)
- }
-
- var changeLogFromTag string
+ defer r.gitPush()
- 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 = ""
+ if r.step == 1 {
+ if err := r.bumpVersions(newVersion); err != nil {
+ return err
}
- }
-
- 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("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
- }
+ // 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
+ }
- if !r.skipPublish {
- if _, err := r.git("push", "origin", tag); err != nil {
+ // 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)
- if err := r.release(releaseNotesFile); err != nil {
- return err
+ 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())
-}
-
-func isCI() bool {
- return os.Getenv("CI") != ""
+ return os.WriteFile(filename, []byte(newContent), fi.Mode())
}
-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...)
}