Compare commits

...

11 Commits

Author SHA1 Message Date
Ubuntu 37d4001881 releaser: Add relase notes to /docs for release of 0.20.3
[ci skip]
2017-04-24 11:07:47 +00:00
Bjørn Erik Pedersen 659f54abdb release: Adjust release notes for 0.20.3 2017-04-24 12:55:18 +02:00
Bjørn Erik Pedersen 69939a5864 releaser: Add relase notes draft for release of 0.20.3
[ci skip]
2017-04-24 12:47:32 +02:00
Bjørn Erik Pedersen 9cb8e7194b releaser: Bump versions for release of 0.20.3
[ci skip]
2017-04-24 12:47:25 +02:00
Bjørn Erik Pedersen 8d0c042a69 releaser: Fix a Go vet complaint
See #3358
2017-04-24 12:43:34 +02:00
Bjørn Erik Pedersen 550eba6470 Automate the Hugo release process
This commit adds a work flow aroung GoReleaser to get the Hugo release process automated and more uniform:

* It can be run fully automated or in two steps to allow for manual edits of the relase notes.
* It supports both patch and full releases.
* It fetches author, issue, repo info. etc. for the release notes from GitHub.
* The file names produced are mainly the same as before, but we no use tar.gz as archive for all Unix versions.
* There isn't a fully automated CI setup in place yet, but the release tag is marked in the commit message with "[ci deploy]"

Fixes #3358
2017-04-24 12:35:39 +02:00
Bjørn Erik Pedersen 9bf5c381b6 hugolib: Fix handling of zero-length files
This was a regression in Hugo 0.20. This commit makes sure that zeron-length files are not rendered to file.

Fixes #3355
2017-04-24 12:30:50 +02:00
Bjørn Erik Pedersen 1726e90201 docs: Add Netlify badge if on Netlify 2017-04-16 11:09:50 +02:00
Bjørn Erik Pedersen 9cd6f69bdd Bump version to 0.20.2 2017-04-16 11:08:35 +02:00
Bjørn Erik Pedersen 68384622bb docs: Add 0.20.2 release notes 2017-04-16 11:05:00 +02:00
Bjørn Erik Pedersen 5f600fdd9f tplimpl: Allow text partials in HTML templates
Most obvius benefit of this is to include CSS partials with css file suffix into HTML templates.

A valid workaround would be to rename the file `mystyles.html`, but that doesn't work too good for external editors etc.

The css partial is  a method used in some themes before Hugo 0.20, but then it stopped working.

This commit reintroduces that behaviour.

Note that the regular layout lookups for text templates, i.e. "single.json" will be
prefixed with "_text/" on lookup and will only match in the text collection.

Fixes #3273
2017-04-16 11:04:52 +02:00
36 changed files with 1466 additions and 83 deletions
+2 -1
View File
@@ -14,4 +14,5 @@ vendor/*/
*.bench
coverage*.out
GoBuilds
GoBuilds
dist
+1 -1
View File
@@ -36,7 +36,7 @@ in the "man" directory under the current directory.`,
header := &doc.GenManHeader{
Section: "1",
Manual: "Hugo Manual",
Source: fmt.Sprintf("Hugo %s", helpers.HugoVersion()),
Source: fmt.Sprintf("Hugo %s", helpers.CurrentHugoVersion),
}
if !strings.HasSuffix(genmandir, helpers.FilePathSeparator) {
genmandir += helpers.FilePathSeparator
+1 -1
View File
@@ -399,7 +399,7 @@ func InitializeConfig(subCmdVs ...*cobra.Command) (*deps.DepsCfg, error) {
if themeVersionMismatch {
cfg.Logger.ERROR.Printf("Current theme does not support Hugo version %s. Minimum version required is %s\n",
helpers.HugoReleaseVersion(), minVersion)
helpers.CurrentHugoVersion.ReleaseVersion(), minVersion)
}
return cfg, nil
+1 -1
View File
@@ -320,7 +320,7 @@ description = ""
homepage = "http://siteforthistheme.com/"
tags = []
features = []
min_version = "0.20"
min_version = "0.20.3"
[author]
name = ""
+62
View File
@@ -0,0 +1,62 @@
// +build release
// 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
import (
"github.com/spf13/cobra"
"github.com/spf13/hugo/releaser"
)
func init() {
HugoCmd.AddCommand(createReleaser().cmd)
}
type releaseCommandeer struct {
cmd *cobra.Command
// Will be zero for main releases.
patchLevel int
skipPublish bool
step int
}
func createReleaser() *releaseCommandeer {
// Note: This is a command only meant for internal use and must be run
// via "go run -tags release main.go release" on the actual code base that is in the release.
r := &releaseCommandeer{
cmd: &cobra.Command{
Use: "release",
Short: "Release a new version of Hugo.",
Hidden: true,
},
}
r.cmd.RunE = func(cmd *cobra.Command, args []string) error {
return r.release()
}
r.cmd.PersistentFlags().IntVarP(&r.patchLevel, "patch", "p", 0, "Patch level, defaults to 0 for main releases")
r.cmd.PersistentFlags().IntVarP(&r.step, "step", "s", -1, "Release step, defaults to -1 for all steps.")
r.cmd.PersistentFlags().BoolVarP(&r.skipPublish, "skip-publish", "", false, "Skip all publishing pipes of the release")
return r
}
func (r *releaseCommandeer) release() error {
return releaser.New(r.patchLevel, r.step, r.skipPublish).Run()
}
+2 -2
View File
@@ -44,9 +44,9 @@ func printHugoVersion() {
formatBuildDate() // format the compile time
}
if hugolib.CommitHash == "" {
jww.FEEDBACK.Printf("Hugo Static Site Generator v%s %s/%s BuildDate: %s\n", helpers.HugoVersion(), runtime.GOOS, runtime.GOARCH, hugolib.BuildDate)
jww.FEEDBACK.Printf("Hugo Static Site Generator v%s %s/%s BuildDate: %s\n", helpers.CurrentHugoVersion, runtime.GOOS, runtime.GOARCH, hugolib.BuildDate)
} else {
jww.FEEDBACK.Printf("Hugo Static Site Generator v%s-%s %s/%s BuildDate: %s\n", helpers.HugoVersion(), strings.ToUpper(hugolib.CommitHash), runtime.GOOS, runtime.GOARCH, hugolib.BuildDate)
jww.FEEDBACK.Printf("Hugo Static Site Generator v%s-%s %s/%s BuildDate: %s\n", helpers.CurrentHugoVersion, strings.ToUpper(hugolib.CommitHash), runtime.GOOS, runtime.GOARCH, hugolib.BuildDate)
}
}
+6 -1
View File
@@ -9,7 +9,7 @@ pluralizeListTitles = false
[params]
description = "Documentation of Hugo, a fast and flexible static site generator built with love by spf13, bep and friends in Go"
author = "Steve Francia (spf13) and friends"
release = "0.20.1"
release = "0.20.3"
[taxonomies]
tag = "tags"
@@ -45,6 +45,11 @@ pluralizeListTitles = false
identifier = "about"
pre = "<i class='fa fa-heart'></i>"
weight = -110
[[menu.main]]
name = "Release Notes"
url = "/release-notes/"
pre = "<i class='fa fa-newspaper-o'></i>"
weight = -111
[[menu.main]]
name = "Getting Started"
identifier = "getting started"
@@ -0,0 +1,23 @@
---
date: 2017-04-24
title: 0.20.3
---
This is a bug-fix relase with one important fix. But it also adds some harness around [GoReleaser](https://github.com/goreleaser/goreleaser) to automate the Hugo release process. Big thanks to [@caarlos0](https://github.com/caarlos0) for great and super-fast support fixing issues along the way.
Hugo now has:
* 16619+ [stars](https://github.com/spf13/hugo/stargazers)
* 458+ [contributors](https://github.com/spf13/hugo/graphs/contributors)
* 156+ [themes](http://themes.gohugo.io/)
## Enhancement
* Automate the Hugo release process [550eba64](https://github.com/spf13/hugo/commit/550eba64705725eb54fdb1042e0fb4dbf6f29fd0) [@bep](https://github.com/bep) [#3358](https://github.com/spf13/hugo/issues/3358)
## Fix
* Fix handling of zero-length files [9bf5c381](https://github.com/spf13/hugo/commit/9bf5c381b6b3e69d4d8dbfd7a40074ac44792bbf) [@bep](https://github.com/bep) [#3355](https://github.com/spf13/hugo/issues/3355)
+8
View File
@@ -0,0 +1,8 @@
---
date: 2017-04-17
aliases:
- /doc/release-notes/
- /meta/release-notes/
title: Release Notes
weight: 10
---
@@ -2,13 +2,55 @@
aliases:
- /doc/release-notes/
- /meta/release-notes/
date: 2013-07-01
menu:
main:
parent: about
title: Release Notes
weight: 10
date: 2017-04-16
title: Older Release Notes
---
# **0.20.2** April 16th 2017
Hugo `0.20.2` adds support for plain text partials included into `HTML` templates. This was a side-effect of the big new [Custom Output Format](https://gohugo.io/extras/output-formats/) feature in `0.20`, and while the change was intentional and there was an ongoing discussion about fixing it in {{< gh 3273 >}}, it did break some themes. There were valid workarounds for these themes, but we might as well get it right.
The most obvious use case for this is inline `CSS` styles, which you now can do without having to name your partials with a `html` suffix.
A simple example:
In `layouts/partials/mystyles.css`:
```css
body {
background-color: {{ .Param "colors.main" }}
}
```
Then in `config.toml` (note that by using the `.Param` lookup func, we can override the color in a page's front matter if we want):
```toml
[params]
[params.colors]
main = "green"
text = "blue"
```
And then in `layouts/partials/head.html` (or the partial used to include the head section into your layout):
```html
<head>
<style type="text/css">
{{ partial "mystyles.css" . | safeCSS }}
</style>
</head>
```
Of course, `0.20` also made it super-easy to create external `CSS` stylesheets based on your site and page configuration. A simple example:
Add "CSS" to your home page's `outputs` list, create the template `/layouts/index.css` using Go template syntax for the dynamic parts, and then include it into your `HTML` template with:
```html
{{ with .OutputFormats.Get "css" }}
<link rel="{{ .Rel }}" type="{{ .MediaType.Type }}" href="{{ .Permalink | safeURL }}">
{{ end }}`
```
# **0.20.1** April 13th 2017
Hugo `0.20.1` is a bug fix release, fixing some important regressions introduced in `0.20` a couple of days ago:
+5
View File
@@ -3,6 +3,11 @@
<p>Last revision: {{ .Lastmod.Format "January 2, 2006" }}{{ if .IsPage }}{{ with .GitInfo }} | <a href="https://github.com/spf13/hugo/commit/{{ .Hash }}">{{ .Subject }} ({{ .AbbreviatedHash }})</a>{{end }}{{ end }}
<span style="float: right;">Hugo v{{ .Site.Params.release }} documentation</span>
</p>
{{ with getenv "REPOSITORY_URL" -}}
<a href="https://www.netlify.com" style="float: right; padding-right: 20px;">
<img src="https://www.netlify.com/img/global/badges/netlify-color-bg.svg"/>
</a>
{{- end }}
</div>
</div>
+6
View File
@@ -0,0 +1,6 @@
{{ define "main" }}
{{ range .Pages }}
<h1>{{ .Title }} {{ .Date.Format "Jan 2, 2006" }}</h1>
{{ .Content }}
{{ end }}
{{ end }}
+17
View File
@@ -0,0 +1,17 @@
This is a bug-fix relase with one important fix. But it also adds some harness around [GoReleaser](https://github.com/goreleaser/goreleaser) to automate the Hugo release process. Big thanks to [@caarlos0](https://github.com/caarlos0) for great and super-fast support fixing issues along the way.
Hugo now has:
* 16619+ [stars](https://github.com/spf13/hugo/stargazers)
* 458+ [contributors](https://github.com/spf13/hugo/graphs/contributors)
* 156+ [themes](http://themes.gohugo.io/)
## Enhancement
* Automate the Hugo release process [550eba64](https://github.com/spf13/hugo/commit/550eba64705725eb54fdb1042e0fb4dbf6f29fd0) [@bep](https://github.com/bep) [#3358](https://github.com/spf13/hugo/issues/3358)
## Fix
* Fix handling of zero-length files [9bf5c381](https://github.com/spf13/hugo/commit/9bf5c381b6b3e69d4d8dbfd7a40074ac44792bbf) [@bep](https://github.com/bep) [#3355](https://github.com/spf13/hugo/issues/3355)
+48
View File
@@ -0,0 +1,48 @@
build:
main: main.go
binary: hugo
ldflags_template: -s -w -X hugolib.BuildDate={{.Date}}
goos:
- darwin
- linux
- windows
- freebsd
- netbsd
- openbsd
- dragonfly
goarch:
- amd64
- 386
- arm
- arm64
fpm:
formats:
- deb
vendor: "gohugo.io"
url: "https://gohugo.io/"
maintainer: "<Bjørn Erik Pedersen bjorn.erik.pedersen@gmail.com>"
description: "A Fast and Flexible Static Site Generator built with love in GoLang."
license: "Apache 2.0"
archive:
format: tar.gz
format_overrides:
- goos: windows
format: zip
name_template: "{{.Binary}}_{{.Version}}_{{.Os}}-{{.Arch}}"
replacements:
amd64: 64bit
386: 32bit
arm: ARM
arm64: ARM64
darwin: macOS
linux: Linux
windows: Windows
openbsd: OpenBSD
netbsd: NetBSD
freebsd: FreeBSD
dragonfly: DragonFlyBSD
files:
- README.md
- LICENSE.md
release:
draft: true
+1 -1
View File
@@ -286,7 +286,7 @@ func InitLoggers() {
// plenty of time to fix their templates.
func Deprecated(object, item, alternative string, err bool) {
if err {
DistinctErrorLog.Printf("%s's %s is deprecated and will be removed in Hugo %s. %s.", object, item, NextHugoReleaseVersion(), alternative)
DistinctErrorLog.Printf("%s's %s is deprecated and will be removed in Hugo %s. %s.", object, item, CurrentHugoVersion.Next().ReleaseVersion(), alternative)
} else {
// Make sure the users see this while avoiding build breakage. This will not lead to an os.Exit(-1)
+38 -29
View File
@@ -20,35 +20,51 @@ import (
"github.com/spf13/cast"
)
// HugoVersionNumber represents the current build version.
// This should be the only one
const (
// HugoVersion represents the Hugo build version.
type HugoVersion struct {
// Major and minor version.
HugoVersionNumber = 0.20
Number float32
// Increment this for bug releases
HugoPatchVersion = 1
)
PatchLevel int
// HugoVersionSuffix is the suffix used in the Hugo version string.
// It will be blank for release versions.
//const HugoVersionSuffix = "-DEV" // use this when not doing a release
const HugoVersionSuffix = "" // use this line when doing a release
// HugoVersion returns the current Hugo version. It will include
// a suffix, typically '-DEV', if it's development version.
func HugoVersion() string {
return hugoVersion(HugoVersionNumber, HugoPatchVersion, HugoVersionSuffix)
// HugoVersionSuffix is the suffix used in the Hugo version string.
// It will be blank for release versions.
Suffix string
}
// HugoReleaseVersion is same as HugoVersion, but no suffix.
func HugoReleaseVersion() string {
return hugoVersionNoSuffix(HugoVersionNumber, HugoPatchVersion)
func (v HugoVersion) String() string {
return hugoVersion(v.Number, v.PatchLevel, v.Suffix)
}
// NextHugoReleaseVersion returns the next Hugo release version.
func NextHugoReleaseVersion() string {
return hugoVersionNoSuffix(HugoVersionNumber+0.01, 0)
// ReleaseVersion represents the release version.
func (v HugoVersion) ReleaseVersion() HugoVersion {
v.Suffix = ""
return v
}
// Next returns the next Hugo release version.
func (v HugoVersion) Next() HugoVersion {
return HugoVersion{Number: v.Number + 0.01}
}
// Pre returns the previous Hugo release version.
func (v HugoVersion) Prev() HugoVersion {
return HugoVersion{Number: v.Number - 0.01}
}
// NextPatchLevel returns the next patch/bugfix Hugo version.
// This will be a patch increment on the previous Hugo version.
func (v HugoVersion) NextPatchLevel(level int) HugoVersion {
return HugoVersion{Number: v.Number - 0.01, PatchLevel: level}
}
// CurrentHugoVersion represents the current build version.
// This should be the only one.
var CurrentHugoVersion = HugoVersion{
Number: 0.20,
PatchLevel: 3,
Suffix: "",
}
func hugoVersion(version float32, patchVersion int, suffix string) string {
@@ -58,19 +74,12 @@ func hugoVersion(version float32, patchVersion int, suffix string) string {
return fmt.Sprintf("%.2f%s", version, suffix)
}
func hugoVersionNoSuffix(version float32, patchVersion int) string {
if patchVersion > 0 {
return fmt.Sprintf("%.2f.%d", version, patchVersion)
}
return fmt.Sprintf("%.2f", version)
}
// CompareVersion compares the given version string or number against the
// running Hugo version.
// It returns -1 if the given version is less than, 0 if equal and 1 if greater than
// the running version.
func CompareVersion(version interface{}) int {
return compareVersions(HugoVersionNumber, HugoPatchVersion, version)
return compareVersions(CurrentHugoVersion.Number, CurrentHugoVersion.PatchLevel, version)
}
func compareVersions(inVersion float32, inPatchVersion int, in interface{}) int {
+7 -3
View File
@@ -22,10 +22,14 @@ import (
func TestHugoVersion(t *testing.T) {
assert.Equal(t, "0.15-DEV", hugoVersion(0.15, 0, "-DEV"))
assert.Equal(t, "0.17", hugoVersionNoSuffix(0.16+0.01, 0))
assert.Equal(t, "0.20", hugoVersionNoSuffix(0.20, 0))
assert.Equal(t, "0.15.2-DEV", hugoVersion(0.15, 2, "-DEV"))
assert.Equal(t, "0.17.3", hugoVersionNoSuffix(0.16+0.01, 3))
v := HugoVersion{Number: 0.21, PatchLevel: 0, Suffix: "-DEV"}
require.Equal(t, v.ReleaseVersion().String(), "0.21")
require.Equal(t, "0.21-DEV", v.String())
require.Equal(t, "0.22", v.Next().String())
require.Equal(t, "0.20.3", v.NextPatchLevel(3).String())
}
func TestCompareVersions(t *testing.T) {
+2 -2
View File
@@ -41,9 +41,9 @@ type HugoInfo struct {
func init() {
hugoInfo = &HugoInfo{
Version: helpers.HugoVersion(),
Version: helpers.CurrentHugoVersion.String(),
CommitHash: CommitHash,
BuildDate: BuildDate,
Generator: template.HTML(fmt.Sprintf(`<meta name="generator" content="Hugo %s" />`, helpers.HugoVersion())),
Generator: template.HTML(fmt.Sprintf(`<meta name="generator" content="Hugo %s" />`, helpers.CurrentHugoVersion.String())),
}
}
+4
View File
@@ -1926,6 +1926,10 @@ func (s *Site) renderAndWritePage(name string, dest string, p *PageOutput, layou
return nil
}
if renderBuffer.Len() == 0 {
return nil
}
outBuffer := bp.GetBuffer()
defer bp.PutBuffer(outBuffer)
+4
View File
@@ -340,6 +340,10 @@ func (s *Site) renderRobotsTXT() error {
return nil
}
if outBuffer.Len() == 0 {
return nil
}
return s.publish("robots.txt", outBuffer)
}
+14
View File
@@ -376,6 +376,20 @@ func TestNewSiteDefaultLang(t *testing.T) {
require.Equal(t, hugofs.Os, s.Fs.Destination)
}
// Issue #3355
func TestShouldNotWriteZeroLengthFilesToDestination(t *testing.T) {
cfg, fs := newTestCfg()
writeSource(t, fs, filepath.Join("content", "simple.html"), "simple")
writeSource(t, fs, filepath.Join("layouts", "_default/single.html"), "{{.Content}}")
writeSource(t, fs, filepath.Join("layouts", "_default/list.html"), "")
s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
th := testHelper{s.Cfg, s.Fs, t}
th.assertFileNotExist(filepath.Join("public", "index.html"))
}
// Issue #1176
func TestSectionNaming(t *testing.T) {
t.Parallel()
+6
View File
@@ -61,6 +61,12 @@ func (th testHelper) assertFileContentRegexp(filename string, matches ...string)
}
}
func (th testHelper) assertFileNotExist(filename string) {
exists, err := helpers.Exists(filename, th.Fs.Destination)
require.NoError(th.T, err)
require.False(th.T, exists)
}
func (th testHelper) replaceDefaultContentLanguageValue(value string) string {
defaultInSubDir := th.Cfg.GetBool("defaultContentLanguageInSubDir")
replace := th.Cfg.GetString("defaultContentLanguage") + "/"
+265
View File
@@ -0,0 +1,265 @@
// 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/exec"
"regexp"
"sort"
"strconv"
"strings"
)
var issueRe = regexp.MustCompile(`(?i)[Updates?|Closes?|Fix.*|See] #(\d+)`)
const (
templateChanges = "templateChanges"
coreChanges = "coreChanges"
outChanges = "outChanges"
docsChanges = "docsChanges"
otherChanges = "otherChanges"
)
type changeLog struct {
Version string
Enhancements map[string]gitInfos
Fixes map[string]gitInfos
All gitInfos
// Overall stats
Repo *gitHubRepo
ContributorCount int
ThemeCount int
}
func newChangeLog(infos gitInfos) changeLog {
return changeLog{
Enhancements: make(map[string]gitInfos),
Fixes: make(map[string]gitInfos),
All: infos,
}
}
func (l changeLog) addGitInfo(isFix bool, info gitInfo, category string) {
var (
infos gitInfos
found bool
segment map[string]gitInfos
)
if isFix {
segment = l.Fixes
} else {
segment = l.Enhancements
}
infos, found = segment[category]
if !found {
infos = gitInfos{}
}
infos = append(infos, info)
segment[category] = infos
}
func gitInfosToChangeLog(infos gitInfos) changeLog {
log := newChangeLog(infos)
for _, info := range infos {
los := strings.ToLower(info.Subject)
isFix := strings.Contains(los, "fix")
var category = otherChanges
// TODO(bep) improve
if regexp.MustCompile("(?i)tpl:|tplimpl:|layout").MatchString(los) {
category = templateChanges
} else if regexp.MustCompile("(?i)docs?:|documentation:").MatchString(los) {
category = docsChanges
} else if regexp.MustCompile("(?i)hugolib:").MatchString(los) {
category = coreChanges
} else if regexp.MustCompile("(?i)out(put)?:|media:|Output|Media").MatchString(los) {
category = outChanges
}
// Trim package prefix.
colonIdx := strings.Index(info.Subject, ":")
if colonIdx != -1 && colonIdx < (len(info.Subject)/2) {
info.Subject = info.Subject[colonIdx+1:]
}
info.Subject = strings.TrimSpace(info.Subject)
log.addGitInfo(isFix, info, category)
}
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 := exec.Command("git", args...)
out, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("git failed: %q: %q", err, out)
}
return string(out), nil
}
func getGitInfos(remote bool) (gitInfos, error) {
return getGitInfosBefore("HEAD", 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 string, remote bool) (gitInfos, error) {
var g gitInfos
log, err := gitLogBefore(ref)
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{
Hash: items[0],
Author: items[1],
Subject: items[2],
Body: items[3],
}
if remote {
gc, err := 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 = "release:|vendor:|snapcraft:"
func gitLogBefore(ref string) (string, error) {
prevTag, err := gitShort("describe", "--tags", "--abbrev=0", "--always", ref+"^")
if err != nil {
return "", err
}
log, err := git("log", "-E", fmt.Sprintf("--grep=%s", ignoredCommits), "--invert-grep", "--pretty=format:%x1e%h%x1f%aE%x1f%s%x1f%b", "--abbrev-commit", prevTag+".."+ref)
if err != nil {
return ",", err
}
return log, err
}
func gitLog() (string, error) {
return gitLogBefore("HEAD")
}
func gitShort(args ...string) (output string, err error) {
output, err = git(args...)
return strings.Replace(strings.Split(output, "\n")[0], "'", "", -1), err
}
+53
View File
@@ -0,0 +1,53 @@
// 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"
"runtime"
"github.com/stretchr/testify/require"
)
func TestGitInfos(t *testing.T) {
if runtime.GOOS == "linux" {
// Travis has an ancient git with no --invert-grep: https://github.com/travis-ci/travis-ci/issues/6328
t.Skip("Skip git test on Linux to make Travis happy.")
}
infos, err := getGitInfos(false)
require.NoError(t, err)
require.True(t, len(infos) > 0)
}
func TestIssuesRe(t *testing.T) {
body := `
This is a commit message.
Updates #123
Fix #345
closes #543
See #456
`
issues := extractIssues(body)
require.Len(t, issues, 4)
require.Equal(t, 123, issues[0])
require.Equal(t, 543, issues[2])
}
+129
View File
@@ -0,0 +1,129 @@
package releaser
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
)
var (
gitHubCommitsApi = "https://api.github.com/repos/spf13/hugo/commits/%s"
gitHubRepoApi = "https://api.github.com/repos/spf13/hugo"
gitHubContributorsApi = "https://api.github.com/repos/spf13/hugo/contributors"
)
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 fetchCommit(ref string) (gitHubCommit, error) {
var commit gitHubCommit
u := fmt.Sprintf(gitHubCommitsApi, ref)
req, err := http.NewRequest("GET", u, nil)
if err != nil {
return commit, err
}
err = doGitHubRequest(req, &commit)
return commit, err
}
func fetchRepo() (gitHubRepo, error) {
var repo gitHubRepo
req, err := http.NewRequest("GET", gitHubRepoApi, 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(gitHubContributorsApi+"?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 interface{}) 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)
}
}
+42
View File
@@ -0,0 +1,42 @@
// 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"
"github.com/stretchr/testify/require"
)
func TestGitHubLookupCommit(t *testing.T) {
skipIfNoToken(t)
commit, err := fetchCommit("793554108763c0984f1a1b1a6ee5744b560d78d0")
require.NoError(t, err)
fmt.Println(commit)
}
func TestFetchRepo(t *testing.T) {
skipIfNoToken(t)
repo, err := fetchRepo()
require.NoError(t, err)
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.")
}
}
+245
View File
@@ -0,0 +1,245 @@
// 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 release 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"
"time"
)
const (
issueLinkTemplate = "[#%d](https://github.com/spf13/hugo/issues/%d)"
linkTemplate = "[%s](%s)"
releaseNotesMarkdownTemplate = `
{{- $patchRelease := isPatch . -}}
{{- $contribsPerAuthor := .All.ContribCountPerAuthor -}}
{{- if $patchRelease }}
{{ if eq (len .All) 1 }}
This is a bug-fix release with one important fix.
{{ else }}
This is a bug-fix relase with a couple of important fixes.
{{ end }}
{{ else }}
This release represents **{{ len .All }} contributions by {{ len $contribsPerAuthor }} contributors** to the main Hugo code base.
{{ end -}}
{{- 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 as always a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the documentation and the themes site in pristine condition.
{{ end }}
Hugo now has:
{{ with .Repo -}}
* {{ .Stars }}+ [stars](https://github.com/spf13/hugo/stargazers)
* {{ len .Contributors }}+ [contributors](https://github.com/spf13/hugo/graphs/contributors)
{{- end -}}
{{ with .ThemeCount }}
* 156+ [themes](http://themes.gohugo.io/)
{{- end }}
## Enhancements
{{ template "change-headers" .Enhancements -}}
## Fixes
{{ template "change-headers" .Fixes -}}
{{ define "change-headers" }}
{{ $tmplChanges := index . "templateChanges" -}}
{{- $outChanges := index . "outChanges" -}}
{{- $coreChanges := index . "coreChanges" -}}
{{- $docsChanges := index . "docsChanges" -}}
{{- $otherChanges := index . "otherChanges" -}}
{{- with $tmplChanges -}}
### Templates
{{ template "change-section" . }}
{{- end -}}
{{- with $outChanges -}}
### Output
{{- template "change-section" . }}
{{- end -}}
{{- with $coreChanges -}}
### Core
{{ template "change-section" . }}
{{- end -}}
{{- with $docsChanges -}}
### Docs
{{- template "change-section" . }}
{{- end -}}
{{- with $otherChanges -}}
### Other
{{ template "change-section" . }}
{{- end -}}
{{ end }}
{{ define "change-section" }}
{{ range . }}
{{- if .GitHubCommit -}}
* {{ .Subject }} {{ . | commitURL }} {{ . | authorURL }} {{ range .Issues }}{{ . | issue }} {{ end }}
{{ else -}}
* {{ .Subject }} {{ range .Issues }}{{ . | issue }} {{ end }}
{{ end -}}
{{- end }}
{{ end }}
`
)
var templateFuncs = template.FuncMap{
"isPatch": func(c changeLog) bool {
return strings.Count(c.Version, ".") > 1
},
"issue": func(id int) string {
return fmt.Sprintf(issueLinkTemplate, id, id)
},
"commitURL": func(info gitInfo) string {
if info.GitHubCommit.HtmlURL == "" {
return ""
}
return fmt.Sprintf(linkTemplate, info.Hash, info.GitHubCommit.HtmlURL)
},
"authorURL": func(info gitInfo) string {
if info.GitHubCommit.Author.Login == "" {
return ""
}
return fmt.Sprintf(linkTemplate, "@"+info.GitHubCommit.Author.Login, info.GitHubCommit.Author.HtmlURL)
},
}
func writeReleaseNotes(version string, infos gitInfos, to io.Writer) error {
changes := gitInfosToChangeLog(infos)
changes.Version = version
repo, err := fetchRepo()
if err == nil {
changes.Repo = &repo
}
themeCount, err := fetchThemeCount()
if err == nil {
changes.ThemeCount = themeCount
}
tmpl, err := template.New("").Funcs(templateFuncs).Parse(releaseNotesMarkdownTemplate)
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://github.com/spf13/hugoThemes/blob/master/.gitmodules")
if err != nil {
return 0, err
}
defer resp.Body.Close()
b, _ := ioutil.ReadAll(resp.Body)
return bytes.Count(b, []byte("submodule")), nil
}
func writeReleaseNotesToTmpFile(version string, infos gitInfos) (string, error) {
f, err := ioutil.TempFile("", "hugorelease")
if err != nil {
return "", err
}
defer f.Close()
if err := writeReleaseNotes(version, infos, f); err != nil {
return "", err
}
return f.Name(), nil
}
func getRelaseNotesDocsTempDirAndName(version string) (string, string) {
return hugoFilepath("docs/temp"), fmt.Sprintf("%s-relnotes.md", version)
}
func getRelaseNotesDocsTempFilename(version string) string {
return filepath.Join(getRelaseNotesDocsTempDirAndName(version))
}
func writeReleaseNotesToDocsTemp(version string, infos gitInfos) (string, error) {
docsTempPath, name := getRelaseNotesDocsTempDirAndName(version)
os.Mkdir(docsTempPath, os.ModePerm)
f, err := os.Create(filepath.Join(docsTempPath, name))
if err != nil {
return "", err
}
defer f.Close()
if err := writeReleaseNotes(version, infos, f); err != nil {
return "", err
}
return f.Name(), nil
}
func writeReleaseNotesToDocs(title, sourceFilename string) (string, error) {
targetFilename := filepath.Base(sourceFilename)
contentDir := hugoFilepath("docs/content/release-notes")
targetFullFilename := filepath.Join(contentDir, targetFilename)
os.Mkdir(contentDir, os.ModePerm)
b, err := ioutil.ReadFile(sourceFilename)
if err != nil {
return "", err
}
f, err := os.Create(targetFullFilename)
if err != nil {
return "", err
}
defer f.Close()
if _, err := f.WriteString(fmt.Sprintf(`
---
date: %s
title: %s
---
`, time.Now().Format("2006-01-02"), title)); err != nil {
return "", err
}
if _, err := f.Write(b); err != nil {
return "", err
}
return targetFullFilename, nil
}
+44
View File
@@ -0,0 +1,44 @@
// 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"
"testing"
"runtime"
"github.com/stretchr/testify/require"
)
func TestReleaseNotesWriter(t *testing.T) {
if runtime.GOOS == "linux" {
// Travis has an ancient git with no --invert-grep: https://github.com/travis-ci/travis-ci/issues/6328
t.Skip("Skip git test on Linux to make Travis happy.")
}
var b bytes.Buffer
// TODO(bep) consider to query GitHub directly for the gitlog with author info, probably faster.
infos, err := getGitInfosBefore("v0.20", false)
require.NoError(t, err)
require.NoError(t, writeReleaseNotes("0.20", infos, &b))
fmt.Println(">>>", b.String())
}
+265
View File
@@ -0,0 +1,265 @@
// 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 (
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"github.com/spf13/hugo/helpers"
)
const commitPrefix = "releaser:"
type ReleaseHandler struct {
patch int
step int
skipPublish bool
}
func (r ReleaseHandler) shouldRelease() bool {
return r.step < 1 || r.shouldContinue()
}
func (r ReleaseHandler) shouldContinue() bool {
return r.step == 2
}
func (r ReleaseHandler) shouldPrepare() bool {
return r.step < 1 || r.step == 1
}
func (r ReleaseHandler) calculateVersions(current helpers.HugoVersion) (helpers.HugoVersion, helpers.HugoVersion) {
var (
newVersion = current
finalVersion = current
)
newVersion.Suffix = ""
if r.shouldContinue() {
// The version in the current code base is in the state we want for
// the release.
if r.patch == 0 {
finalVersion = newVersion.Next()
}
} else if r.patch > 0 {
newVersion = helpers.CurrentHugoVersion.NextPatchLevel(r.patch)
} else {
finalVersion = newVersion.Next()
}
finalVersion.Suffix = "-DEV"
return newVersion, finalVersion
}
func New(patch, step int, skipPublish bool) *ReleaseHandler {
return &ReleaseHandler{patch: patch, step: step, skipPublish: skipPublish}
}
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")
}
newVersion, finalVersion := r.calculateVersions(helpers.CurrentHugoVersion)
version := newVersion.String()
tag := "v" + version
// Exit early if tag already exists
out, err := git("tag", "-l", tag)
if err != nil {
return err
}
if strings.Contains(out, tag) {
return fmt.Errorf("Tag %q already exists", tag)
}
var gitCommits gitInfos
if r.shouldPrepare() || r.shouldRelease() {
gitCommits, err = getGitInfos(true)
if err != nil {
return err
}
}
if r.shouldPrepare() {
if err := bumpVersions(newVersion); err != nil {
return err
}
if _, err := git("commit", "-a", "-m", fmt.Sprintf("%s Bump versions for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
return err
}
releaseNotesFile, err := writeReleaseNotesToDocsTemp(version, gitCommits)
if err != nil {
return err
}
if _, err := git("add", releaseNotesFile); err != nil {
return err
}
if _, err := git("commit", "-m", fmt.Sprintf("%s Add relase notes draft for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
return err
}
}
if !r.shouldRelease() {
fmt.Println("Skip release ... Use --state=2 to continue.")
return nil
}
releaseNotesFile := getRelaseNotesDocsTempFilename(version)
// Write the release notes to the docs site as well.
docFile, err := writeReleaseNotesToDocs(version, releaseNotesFile)
if err != nil {
return err
}
if _, err := git("add", docFile); err != nil {
return err
}
if _, err := git("commit", "-m", fmt.Sprintf("%s Add relase notes to /docs for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
return err
}
if _, err := git("tag", "-a", tag, "-m", fmt.Sprintf("%s %s [ci deploy]", commitPrefix, newVersion)); err != nil {
return err
}
if err := r.release(releaseNotesFile); err != nil {
return err
}
if err := bumpVersions(finalVersion); err != nil {
return err
}
// No longer needed.
if err := os.Remove(releaseNotesFile); err != nil {
return err
}
if _, err := git("commit", "-a", "-m", fmt.Sprintf("%s Prepare repository for %s\n\n[ci skip]", commitPrefix, finalVersion)); err != nil {
return err
}
return nil
}
func (r *ReleaseHandler) release(releaseNotesFile string) error {
cmd := exec.Command("goreleaser", "--release-notes", releaseNotesFile, "--skip-publish="+fmt.Sprint(r.skipPublish))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return fmt.Errorf("goreleaser failed: %s", err)
}
return nil
}
func bumpVersions(ver helpers.HugoVersion) error {
fromDev := ""
toDev := ""
if ver.Suffix != "" {
toDev = "-DEV"
} else {
fromDev = "-DEV"
}
if err := 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 {
return err
}
snapcraftGrade := "stable"
if ver.Suffix != "" {
snapcraftGrade = "devel"
}
if err := replaceInFile("snapcraft.yaml",
`version: "(.*)"`, fmt.Sprintf(`version: "%s"`, ver),
`grade: (.*) #`, fmt.Sprintf(`grade: %s #`, snapcraftGrade)); err != nil {
return err
}
var minVersion string
if ver.Suffix != "" {
// People use the DEV version in daily use, and we cannot create new themes
// with the next version before it is released.
minVersion = ver.Prev().String()
} else {
minVersion = ver.String()
}
if err := replaceInFile("commands/new.go",
`min_version = "(.*)"`, fmt.Sprintf(`min_version = "%s"`, minVersion)); err != nil {
return err
}
// docs/config.toml
if err := replaceInFile("docs/config.toml",
`release = "(.*)"`, fmt.Sprintf(`release = "%s"`, ver)); err != nil {
return err
}
return nil
}
func replaceInFile(filename string, oldNew ...string) error {
fullFilename := hugoFilepath(filename)
fi, err := os.Stat(fullFilename)
if err != nil {
return err
}
b, err := ioutil.ReadFile(fullFilename)
if err != nil {
return err
}
newContent := string(b)
for i := 0; i < len(oldNew); i += 2 {
re := regexp.MustCompile(oldNew[i])
newContent = re.ReplaceAllString(newContent, oldNew[i+1])
}
return ioutil.WriteFile(fullFilename, []byte(newContent), fi.Mode())
}
func hugoFilepath(filename string) string {
pwd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
return filepath.Join(pwd, filename)
}
+78
View File
@@ -0,0 +1,78 @@
// 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 (
"testing"
"github.com/spf13/hugo/helpers"
"github.com/stretchr/testify/require"
)
func TestCalculateVersions(t *testing.T) {
startVersion := helpers.HugoVersion{Number: 0.20, Suffix: "-DEV"}
tests := []struct {
handler *ReleaseHandler
version helpers.HugoVersion
v1 string
v2 string
}{
{
New(0, 0, true),
startVersion,
"0.20",
"0.21-DEV",
},
{
New(2, 0, true),
startVersion,
"0.20.2",
"0.20-DEV",
},
{
New(0, 1, true),
startVersion,
"0.20",
"0.21-DEV",
},
{
New(0, 2, true),
startVersion,
"0.20",
"0.21-DEV",
},
{
New(3, 1, true),
startVersion,
"0.20.3",
"0.20-DEV",
},
{
New(3, 2, true),
startVersion.Next(),
"0.21",
"0.21-DEV",
},
}
for _, test := range tests {
v1, v2 := test.handler.calculateVersions(test.version)
require.Equal(t, test.v1, v1.String(), "Release version")
require.Equal(t, test.v2, v2.String(), "Final version")
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
name: hugo
version: "0.20.1"
version: "0.20.3"
summary: Fast and Flexible Static Site Generator
description: |
Hugo is a static HTML and CSS website generator written in Go. It is
+11 -12
View File
@@ -97,23 +97,21 @@ func (t *templateHandler) PrintErrors() {
// Lookup tries to find a template with the given name in both template
// collections: First HTML, then the plain text template collection.
func (t *templateHandler) Lookup(name string) *tpl.TemplateAdapter {
var te *tpl.TemplateAdapter
isTextTemplate := strings.HasPrefix(name, textTmplNamePrefix)
if isTextTemplate {
if strings.HasPrefix(name, textTmplNamePrefix) {
// The caller has explicitly asked for a text template, so only look
// in the text template collection.
// The templates are stored without the prefix identificator.
name = strings.TrimPrefix(name, textTmplNamePrefix)
te = t.text.Lookup(name)
} else {
te = t.html.Lookup(name)
return t.text.Lookup(name)
}
if te == nil {
return nil
// Look in both
if te := t.html.Lookup(name); te != nil {
return te
}
return te
return t.text.Lookup(name)
}
func (t *templateHandler) clone(d *deps.Deps) *templateHandler {
@@ -459,9 +457,10 @@ func (t *templateHandler) loadTemplates(absPath string, prefix string) {
func (t *templateHandler) initFuncs() {
// The template funcs need separation between text and html templates.
// Both template types will get their own funcster instance, which
// in the current case contains the same set of funcs.
for _, funcsterHolder := range []templateFuncsterTemplater{t.html, t.text} {
funcster := newTemplateFuncster(t.Deps, funcsterHolder)
funcster := newTemplateFuncster(t.Deps)
// The URL funcs in the funcMap is somewhat language dependent,
// so we need to wait until the language and site config is loaded.
+6 -12
View File
@@ -17,6 +17,7 @@ import (
"fmt"
"html/template"
"strings"
texttemplate "text/template"
bp "github.com/spf13/hugo/bufferpool"
@@ -31,17 +32,12 @@ type templateFuncster struct {
cachedPartials partialCache
image *imageHandler
// Make sure each funcster gets its own TemplateFinder to get
// proper text and HTML template separation.
Tmpl templateFuncsterTemplater
*deps.Deps
}
func newTemplateFuncster(deps *deps.Deps, t templateFuncsterTemplater) *templateFuncster {
func newTemplateFuncster(deps *deps.Deps) *templateFuncster {
return &templateFuncster{
Deps: deps,
Tmpl: t,
cachedPartials: partialCache{p: make(map[string]interface{})},
image: &imageHandler{fs: deps.Fs, imageConfigCache: map[string]image.Config{}},
}
@@ -75,14 +71,12 @@ func (t *templateFuncster) partial(name string, contextList ...interface{}) (int
return "", err
}
switch t.Tmpl.(type) {
case *htmlTemplates:
return template.HTML(b.String()), nil
case *textTemplates:
if _, ok := templ.Template.(*texttemplate.Template); ok {
return b.String(), nil
default:
panic("Unknown type")
}
return template.HTML(b.String()), nil
}
}
+1 -1
View File
@@ -2221,5 +2221,5 @@ func (t *templateFuncster) initFuncMap() {
}
t.funcMap = funcMap
t.Tmpl.setFuncs(funcMap)
t.Tmpl.(*templateHandler).setFuncs(funcMap)
}
+19 -8
View File
@@ -2869,20 +2869,27 @@ func TestPartialHTMLAndText(t *testing.T) {
}
config.WithTemplate = func(templ tpl.TemplateHandler) error {
if err := templ.AddTemplate("htmlTemplate.html", `HTML Test Partial: {{ partial "test.foo" . -}}`); err != nil {
if err := templ.AddTemplate("htmlTemplate.html", `HTML Test|HTML:{{ partial "test.html" . -}}|Text:{{ partial "test.txt" . }}
CSS plain: <style type="text/css">{{ partial "mystyles.css" . -}}</style>
CSS safe: <style type="text/css">{{ partial "mystyles.css" . | safeCSS -}}</style>
`); err != nil {
return err
}
if err := templ.AddTemplate("_text/textTemplate.txt", `Text Test Partial: {{ partial "test.foo" . -}}`); err != nil {
if err := templ.AddTemplate("_text/textTemplate.txt", `Text Test|HTML:{{ partial "test.html" . -}}|Text:{{ partial "test.txt" . }}
CSS plain: <style type="text/css">{{ partial "mystyles.css" . -}}</style>`); err != nil {
return err
}
// Use "foo" here to say that the extension doesn't really matter in this scenario.
// It will look for templates in "partials/test.foo" and "partials/test.foo.html".
if err := templ.AddTemplate("partials/test.foo", "HTML Name: {{ .Name }}"); err != nil {
if err := templ.AddTemplate("partials/test.html", "HTML Name: {{ .Name }}"); err != nil {
return err
}
if err := templ.AddTemplate("_text/partials/test.foo", "Text Name: {{ .Name }}"); err != nil {
if err := templ.AddTemplate("_text/partials/test.txt", "Text Name: {{ .Name }}"); err != nil {
return err
}
if err := templ.AddTemplate("_text/partials/mystyles.css",
`body { background-color: blue; }
`); err != nil {
return err
}
@@ -2903,8 +2910,12 @@ func TestPartialHTMLAndText(t *testing.T) {
resultText, err := templ.ExecuteToString(data)
require.NoError(t, err)
require.Contains(t, resultHTML, "HTML Test Partial: HTML Name: a&#43;b&#43;c")
require.Contains(t, resultText, "Text Test Partial: Text Name: a+b+c")
require.Contains(t, resultHTML, "HTML Test|HTML:HTML Name: a&#43;b&#43;c|Text:Text Name: a&#43;b&#43;c")
require.Contains(t, resultHTML, `CSS plain: <style type="text/css">ZgotmplZ</style>`)
require.Contains(t, resultHTML, `CSS safe: <style type="text/css">body { background-color: blue; }`)
require.Contains(t, resultText, "Text Test|HTML:HTML Name: a&#43;b&#43;c|Text:Text Name: a+b+c")
require.Contains(t, resultText, `CSS plain: <style type="text/css">body { background-color: blue; }`)
}
+1 -1
View File
@@ -22,7 +22,7 @@ import (
)
var metaTagsCheck = regexp.MustCompile(`(?i)<meta\s+name=['|"]?generator['|"]?`)
var hugoGeneratorTag = fmt.Sprintf(`<meta name="generator" content="Hugo %s" />`, helpers.HugoVersion())
var hugoGeneratorTag = fmt.Sprintf(`<meta name="generator" content="Hugo %s" />`, helpers.CurrentHugoVersion)
// HugoGeneratorInject injects a meta generator tag for Hugo if none present.
func HugoGeneratorInject(ct contentTransformer) {