mirror of
https://github.com/gohugoio/hugo.git
synced 2026-09-02 11:42:37 +00:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e632833a6 | |||
| c2b9fcfb20 | |||
| 3a4ddc38d4 | |||
| 3682bf5279 | |||
| 4bfb013468 | |||
| 161b2cc367 | |||
| 09fad505da | |||
| c8e41a94e8 | |||
| 63b7ce151f | |||
| b30ca4bec8 | |||
| 3bd1d0571d | |||
| 914729199b | |||
| eb3d1c5a4a | |||
| 4e91e56cdd | |||
| 75f6cabd98 | |||
| 8fa8c33459 | |||
| 549ddd1193 | |||
| a16bcd6abc | |||
| 77a878e684 | |||
| bc3b73579d | |||
| ad5bcaf18a | |||
| 275bcf566c | |||
| cb3c6b6f76 | |||
| 37d4001881 | |||
| 659f54abdb | |||
| 69939a5864 | |||
| 9cb8e7194b | |||
| 8d0c042a69 | |||
| 550eba6470 | |||
| 9bf5c381b6 | |||
| 1726e90201 | |||
| 9cd6f69bdd | |||
| 68384622bb | |||
| 5f600fdd9f |
+2
-1
@@ -14,4 +14,5 @@ vendor/*/
|
|||||||
*.bench
|
*.bench
|
||||||
coverage*.out
|
coverage*.out
|
||||||
|
|
||||||
GoBuilds
|
GoBuilds
|
||||||
|
dist
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@ in the "man" directory under the current directory.`,
|
|||||||
header := &doc.GenManHeader{
|
header := &doc.GenManHeader{
|
||||||
Section: "1",
|
Section: "1",
|
||||||
Manual: "Hugo Manual",
|
Manual: "Hugo Manual",
|
||||||
Source: fmt.Sprintf("Hugo %s", helpers.HugoVersion()),
|
Source: fmt.Sprintf("Hugo %s", helpers.CurrentHugoVersion),
|
||||||
}
|
}
|
||||||
if !strings.HasSuffix(genmandir, helpers.FilePathSeparator) {
|
if !strings.HasSuffix(genmandir, helpers.FilePathSeparator) {
|
||||||
genmandir += helpers.FilePathSeparator
|
genmandir += helpers.FilePathSeparator
|
||||||
|
|||||||
+1
-1
@@ -399,7 +399,7 @@ func InitializeConfig(subCmdVs ...*cobra.Command) (*deps.DepsCfg, error) {
|
|||||||
|
|
||||||
if themeVersionMismatch {
|
if themeVersionMismatch {
|
||||||
cfg.Logger.ERROR.Printf("Current theme does not support Hugo version %s. Minimum version required is %s\n",
|
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
|
return cfg, nil
|
||||||
|
|||||||
+1
-1
@@ -320,7 +320,7 @@ description = ""
|
|||||||
homepage = "http://siteforthistheme.com/"
|
homepage = "http://siteforthistheme.com/"
|
||||||
tags = []
|
tags = []
|
||||||
features = []
|
features = []
|
||||||
min_version = "0.20"
|
min_version = "0.20.7"
|
||||||
|
|
||||||
[author]
|
[author]
|
||||||
name = ""
|
name = ""
|
||||||
|
|||||||
@@ -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
@@ -44,9 +44,9 @@ func printHugoVersion() {
|
|||||||
formatBuildDate() // format the compile time
|
formatBuildDate() // format the compile time
|
||||||
}
|
}
|
||||||
if hugolib.CommitHash == "" {
|
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 {
|
} 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
@@ -9,7 +9,7 @@ pluralizeListTitles = false
|
|||||||
[params]
|
[params]
|
||||||
description = "Documentation of Hugo, a fast and flexible static site generator built with love by spf13, bep and friends in Go"
|
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"
|
author = "Steve Francia (spf13) and friends"
|
||||||
release = "0.20.1"
|
release = "0.20.7"
|
||||||
|
|
||||||
[taxonomies]
|
[taxonomies]
|
||||||
tag = "tags"
|
tag = "tags"
|
||||||
@@ -45,6 +45,11 @@ pluralizeListTitles = false
|
|||||||
identifier = "about"
|
identifier = "about"
|
||||||
pre = "<i class='fa fa-heart'></i>"
|
pre = "<i class='fa fa-heart'></i>"
|
||||||
weight = -110
|
weight = -110
|
||||||
|
[[menu.main]]
|
||||||
|
name = "Release Notes"
|
||||||
|
url = "/release-notes/"
|
||||||
|
pre = "<i class='fa fa-newspaper-o'></i>"
|
||||||
|
weight = -111
|
||||||
[[menu.main]]
|
[[menu.main]]
|
||||||
name = "Getting Started"
|
name = "Getting Started"
|
||||||
identifier = "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)
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
date: 2017-04-24
|
||||||
|
title: 0.20.4
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
This is the second bug-fix relase of the day, fixing a couple of issues related to the new release scripts.
|
||||||
|
|
||||||
|
|
||||||
|
Hugo now has:
|
||||||
|
|
||||||
|
* 16626+ [stars](https://github.com/spf13/hugo/stargazers)
|
||||||
|
* 457+ [contributors](https://github.com/spf13/hugo/graphs/contributors)
|
||||||
|
* 156+ [themes](http://themes.gohugo.io/)
|
||||||
|
|
||||||
|
|
||||||
|
* Fix statically linked binaries [275bcf56](https://github.com/spf13/hugo/commit/275bcf566c7cb72367d4423cf4810319311ff680) [@munnerz](https://github.com/munnerz) [#3382](https://github.com/spf13/hugo/issues/3382)
|
||||||
|
* Filename change in Hugo 0.20.3 binaries [\](https://github.com/spf13/hugo/issues/3385)
|
||||||
|
* Fix version calculation [cb3c6b6f](https://github.com/spf13/hugo/commit/cb3c6b6f7670f85189a4a3637e7132901d1ed6e9) [@bep](https://github.com/bep)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
date: 2017-04-25
|
||||||
|
title: 0.20.5
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
This is a bug-fix relase which fixes the version number of `0.20.4` (which wrongly shows up as `0.21-DEV`) ([#3388](https://github.com/spf13/hugo/issues/3388)).
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
date: 2017-04-27
|
||||||
|
title: 0.20.6
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
There have been some [shouting on discuss.gohugo.io](https://discuss.gohugo.io/t/index-md-is-generated-in-subfolder-index-index-html-hugo-0-20/6338/15) about some broken sites after the release of Hugo `0.20`. This release reintroduces the old behaviour, making `/my-blog-post/index.md` work as expected.
|
||||||
|
|
||||||
|
Hugo now has:
|
||||||
|
|
||||||
|
* 16675+ [stars](https://github.com/spf13/hugo/stargazers)
|
||||||
|
* 456+ [contributors](https://github.com/spf13/hugo/graphs/contributors)
|
||||||
|
* 156+ [themes](http://themes.gohugo.io/)
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
* Avoid index.md in /index/index.html [#3396](https://github.com/spf13/hugo/issues/3396)
|
||||||
|
* Make missing GitInfo a WARNING [b30ca4be](https://github.com/spf13/hugo/commit/b30ca4bec811dbc17e9fd05925544db2b75e0e49) [@bep](https://github.com/bep) [#3376](https://github.com/spf13/hugo/issues/3376)
|
||||||
|
* Fix some of the fpm fields for deb [3bd1d057](https://github.com/spf13/hugo/commit/3bd1d0571d5f2f6bf0dc8f90a8adf2dbfcb2fdfd) [@anthonyfok](https://github.com/anthonyfok)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
date: 2017-05-03
|
||||||
|
title: 0.20.7
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
This is a bug-fix release with one important fix.
|
||||||
|
|
||||||
|
|
||||||
|
Hugo now has:
|
||||||
|
|
||||||
|
* 16782+ [stars](https://github.com/spf13/hugo/stargazers)
|
||||||
|
* 458+ [contributors](https://github.com/spf13/hugo/graphs/contributors)
|
||||||
|
* 156+ [themes](http://themes.gohugo.io/)
|
||||||
|
|
||||||
|
## Enhancements
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
* Push the tag before goreleaser is run [3682bf52](https://github.com/spf13/hugo/commit/3682bf527989e86d9da32d76809306cb576383e8) [@bep](https://github.com/bep) [#3405](https://github.com/spf13/hugo/issues/3405)
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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:
|
aliases:
|
||||||
- /doc/release-notes/
|
- /doc/release-notes/
|
||||||
- /meta/release-notes/
|
- /meta/release-notes/
|
||||||
date: 2013-07-01
|
date: 2017-04-16
|
||||||
menu:
|
title: Older Release Notes
|
||||||
main:
|
|
||||||
parent: about
|
|
||||||
title: Release Notes
|
|
||||||
weight: 10
|
|
||||||
---
|
---
|
||||||
|
# **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
|
# **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:
|
Hugo `0.20.1` is a bug fix release, fixing some important regressions introduced in `0.20` a couple of days ago:
|
||||||
|
|
||||||
@@ -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 }}
|
<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>
|
<span style="float: right;">Hugo v{{ .Site.Params.release }} documentation</span>
|
||||||
</p>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{{ define "main" }}
|
||||||
|
{{ range .Pages }}
|
||||||
|
<h1>{{ .Title }} {{ .Date.Format "Jan 2, 2006" }}</h1>
|
||||||
|
{{ .Content }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
@@ -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)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
|
||||||
|
This is the second bug-fix relase of the day, fixing a couple of issues related to the new release scripts.
|
||||||
|
|
||||||
|
|
||||||
|
Hugo now has:
|
||||||
|
|
||||||
|
* 16626+ [stars](https://github.com/spf13/hugo/stargazers)
|
||||||
|
* 457+ [contributors](https://github.com/spf13/hugo/graphs/contributors)
|
||||||
|
* 156+ [themes](http://themes.gohugo.io/)
|
||||||
|
|
||||||
|
|
||||||
|
* Fix statically linked binaries [275bcf56](https://github.com/spf13/hugo/commit/275bcf566c7cb72367d4423cf4810319311ff680) [@munnerz](https://github.com/munnerz) [#3382](https://github.com/spf13/hugo/issues/3382)
|
||||||
|
* Filename change in Hugo 0.20.3 binaries [\](https://github.com/spf13/hugo/issues/3385)
|
||||||
|
* Fix version calculation [cb3c6b6f](https://github.com/spf13/hugo/commit/cb3c6b6f7670f85189a4a3637e7132901d1ed6e9) [@bep](https://github.com/bep)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
|
This is a bug-fix relase which fixes the version number of `0.20.4` (which wrongly shows up as `0.21-DEV`) ([#3388](https://github.com/spf13/hugo/issues/3388)).
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
|
||||||
|
There have been some [shouting on discuss.gohugo.io](https://discuss.gohugo.io/t/index-md-is-generated-in-subfolder-index-index-html-hugo-0-20/6338/15) about some broken sites after the release of Hugo `0.20`. This release reintroduces the old behaviour, making `/my-blog-post/index.md` work as expected.
|
||||||
|
|
||||||
|
Hugo now has:
|
||||||
|
|
||||||
|
* 16675+ [stars](https://github.com/spf13/hugo/stargazers)
|
||||||
|
* 456+ [contributors](https://github.com/spf13/hugo/graphs/contributors)
|
||||||
|
* 156+ [themes](http://themes.gohugo.io/)
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
* Avoid index.md in /index/index.html [#3396](https://github.com/spf13/hugo/issues/3396)
|
||||||
|
* Make missing GitInfo a WARNING [b30ca4be](https://github.com/spf13/hugo/commit/b30ca4bec811dbc17e9fd05925544db2b75e0e49) [@bep](https://github.com/bep) [#3376](https://github.com/spf13/hugo/issues/3376)
|
||||||
|
* Fix some of the fpm fields for deb [3bd1d057](https://github.com/spf13/hugo/commit/3bd1d0571d5f2f6bf0dc8f90a8adf2dbfcb2fdfd) [@anthonyfok](https://github.com/anthonyfok)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
|
||||||
|
This is a bug-fix release with one important fix.
|
||||||
|
|
||||||
|
|
||||||
|
Hugo now has:
|
||||||
|
|
||||||
|
* 16782+ [stars](https://github.com/spf13/hugo/stargazers)
|
||||||
|
* 458+ [contributors](https://github.com/spf13/hugo/graphs/contributors)
|
||||||
|
* 156+ [themes](http://themes.gohugo.io/)
|
||||||
|
|
||||||
|
## Enhancements
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
* Push the tag before goreleaser is run [3682bf52](https://github.com/spf13/hugo/commit/3682bf527989e86d9da32d76809306cb576383e8) [@bep](https://github.com/bep) [#3405](https://github.com/spf13/hugo/issues/3405)
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
build:
|
||||||
|
main: main.go
|
||||||
|
binary: hugo
|
||||||
|
ldflags_template: -s -w -X hugolib.BuildDate={{.Date}} -linkmode external -extldflags "-static"
|
||||||
|
goos:
|
||||||
|
- darwin
|
||||||
|
- linux
|
||||||
|
- windows
|
||||||
|
- freebsd
|
||||||
|
- netbsd
|
||||||
|
- openbsd
|
||||||
|
- dragonfly
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
- 386
|
||||||
|
- arm
|
||||||
|
- arm64
|
||||||
|
fpm:
|
||||||
|
formats:
|
||||||
|
- deb
|
||||||
|
vendor: "gohugo.io"
|
||||||
|
homepage: "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
@@ -286,7 +286,7 @@ func InitLoggers() {
|
|||||||
// plenty of time to fix their templates.
|
// plenty of time to fix their templates.
|
||||||
func Deprecated(object, item, alternative string, err bool) {
|
func Deprecated(object, item, alternative string, err bool) {
|
||||||
if err {
|
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 {
|
} else {
|
||||||
// Make sure the users see this while avoiding build breakage. This will not lead to an os.Exit(-1)
|
// Make sure the users see this while avoiding build breakage. This will not lead to an os.Exit(-1)
|
||||||
|
|||||||
+41
-29
@@ -20,35 +20,54 @@ import (
|
|||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HugoVersionNumber represents the current build version.
|
// HugoVersion represents the Hugo build version.
|
||||||
// This should be the only one
|
type HugoVersion struct {
|
||||||
const (
|
|
||||||
// Major and minor version.
|
// Major and minor version.
|
||||||
HugoVersionNumber = 0.20
|
Number float32
|
||||||
|
|
||||||
// Increment this for bug releases
|
// Increment this for bug releases
|
||||||
HugoPatchVersion = 1
|
PatchLevel int
|
||||||
)
|
|
||||||
|
|
||||||
// HugoVersionSuffix is the suffix used in the Hugo version string.
|
// HugoVersionSuffix is the suffix used in the Hugo version string.
|
||||||
// It will be blank for release versions.
|
// It will be blank for release versions.
|
||||||
//const HugoVersionSuffix = "-DEV" // use this when not doing a release
|
Suffix string
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HugoReleaseVersion is same as HugoVersion, but no suffix.
|
func (v HugoVersion) String() string {
|
||||||
func HugoReleaseVersion() string {
|
return hugoVersion(v.Number, v.PatchLevel, v.Suffix)
|
||||||
return hugoVersionNoSuffix(HugoVersionNumber, HugoPatchVersion)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextHugoReleaseVersion returns the next Hugo release version.
|
// ReleaseVersion represents the release version.
|
||||||
func NextHugoReleaseVersion() string {
|
func (v HugoVersion) ReleaseVersion() HugoVersion {
|
||||||
return hugoVersionNoSuffix(HugoVersionNumber+0.01, 0)
|
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 {
|
||||||
|
if v.PatchLevel > 0 {
|
||||||
|
return HugoVersion{Number: v.Number, PatchLevel: level}
|
||||||
|
}
|
||||||
|
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: 7,
|
||||||
|
Suffix: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
func hugoVersion(version float32, patchVersion int, suffix string) string {
|
func hugoVersion(version float32, patchVersion int, suffix string) string {
|
||||||
@@ -58,19 +77,12 @@ func hugoVersion(version float32, patchVersion int, suffix string) string {
|
|||||||
return fmt.Sprintf("%.2f%s", version, suffix)
|
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
|
// CompareVersion compares the given version string or number against the
|
||||||
// running Hugo version.
|
// running Hugo version.
|
||||||
// It returns -1 if the given version is less than, 0 if equal and 1 if greater than
|
// It returns -1 if the given version is less than, 0 if equal and 1 if greater than
|
||||||
// the running version.
|
// the running version.
|
||||||
func CompareVersion(version interface{}) int {
|
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 {
|
func compareVersions(inVersion float32, inPatchVersion int, in interface{}) int {
|
||||||
|
|||||||
@@ -22,10 +22,14 @@ import (
|
|||||||
|
|
||||||
func TestHugoVersion(t *testing.T) {
|
func TestHugoVersion(t *testing.T) {
|
||||||
assert.Equal(t, "0.15-DEV", hugoVersion(0.15, 0, "-DEV"))
|
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.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) {
|
func TestCompareVersions(t *testing.T) {
|
||||||
|
|||||||
+1
-1
@@ -58,7 +58,7 @@ func (h *HugoSites) assembleGitInfo() {
|
|||||||
filename := path.Join(filepath.ToSlash(contentRoot), contentDir, filepath.ToSlash(p.Path()))
|
filename := path.Join(filepath.ToSlash(contentRoot), contentDir, filepath.ToSlash(p.Path()))
|
||||||
g, ok := gitMap[filename]
|
g, ok := gitMap[filename]
|
||||||
if !ok {
|
if !ok {
|
||||||
h.Log.ERROR.Printf("Failed to find GitInfo for %q", filename)
|
h.Log.WARN.Printf("Failed to find GitInfo for %q", filename)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ type HugoInfo struct {
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
hugoInfo = &HugoInfo{
|
hugoInfo = &HugoInfo{
|
||||||
Version: helpers.HugoVersion(),
|
Version: helpers.CurrentHugoVersion.String(),
|
||||||
CommitHash: CommitHash,
|
CommitHash: CommitHash,
|
||||||
BuildDate: BuildDate,
|
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())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,6 +141,15 @@ func createTargetPath(d targetPathDescriptor) string {
|
|||||||
|
|
||||||
isUgly := d.UglyURLs && !d.Type.NoUgly
|
isUgly := d.UglyURLs && !d.Type.NoUgly
|
||||||
|
|
||||||
|
// If the page output format's base name is the same as the page base name,
|
||||||
|
// we treat it as an ugly path, i.e.
|
||||||
|
// my-blog-post-1/index.md => my-blog-post-1/index.html
|
||||||
|
// (given the default values for that content file, i.e. no slug set etc.).
|
||||||
|
// This introduces the behaviour from < Hugo 0.20, see issue #3396.
|
||||||
|
if d.BaseName != "" && d.BaseName == d.Type.BaseName {
|
||||||
|
isUgly = true
|
||||||
|
}
|
||||||
|
|
||||||
if d.Kind != KindPage && len(d.Sections) > 0 {
|
if d.Kind != KindPage && len(d.Sections) > 0 {
|
||||||
pagePath = filepath.Join(d.Sections...)
|
pagePath = filepath.Join(d.Sections...)
|
||||||
needsBase = false
|
needsBase = false
|
||||||
|
|||||||
@@ -62,6 +62,16 @@ func TestPageTargetPath(t *testing.T) {
|
|||||||
BaseName: "mypage",
|
BaseName: "mypage",
|
||||||
Sections: []string{"a"},
|
Sections: []string{"a"},
|
||||||
Type: output.HTMLFormat}, "/a/b/mypage/index.html"},
|
Type: output.HTMLFormat}, "/a/b/mypage/index.html"},
|
||||||
|
|
||||||
|
{
|
||||||
|
// Issue #3396
|
||||||
|
"HTML page with index as base", targetPathDescriptor{
|
||||||
|
Kind: KindPage,
|
||||||
|
Dir: "/a/b",
|
||||||
|
BaseName: "index",
|
||||||
|
Sections: []string{"a"},
|
||||||
|
Type: output.HTMLFormat}, "/a/b/index.html"},
|
||||||
|
|
||||||
{
|
{
|
||||||
"HTML page with special chars", targetPathDescriptor{
|
"HTML page with special chars", targetPathDescriptor{
|
||||||
Kind: KindPage,
|
Kind: KindPage,
|
||||||
@@ -139,7 +149,9 @@ func TestPageTargetPath(t *testing.T) {
|
|||||||
expected := test.expected
|
expected := test.expected
|
||||||
|
|
||||||
// TODO(bep) simplify
|
// TODO(bep) simplify
|
||||||
if test.d.Kind == KindHome && test.d.Type.Path != "" {
|
if test.d.BaseName == test.d.Type.BaseName {
|
||||||
|
|
||||||
|
} else if test.d.Kind == KindHome && test.d.Type.Path != "" {
|
||||||
} else if (!strings.HasPrefix(expected, "/index") || test.d.Addends != "") && test.d.URL == "" && isUgly {
|
} else if (!strings.HasPrefix(expected, "/index") || test.d.Addends != "") && test.d.URL == "" && isUgly {
|
||||||
expected = strings.Replace(expected,
|
expected = strings.Replace(expected,
|
||||||
"/"+test.d.Type.BaseName+"."+test.d.Type.MediaType.Suffix,
|
"/"+test.d.Type.BaseName+"."+test.d.Type.MediaType.Suffix,
|
||||||
|
|||||||
@@ -1926,6 +1926,10 @@ func (s *Site) renderAndWritePage(name string, dest string, p *PageOutput, layou
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if renderBuffer.Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
outBuffer := bp.GetBuffer()
|
outBuffer := bp.GetBuffer()
|
||||||
defer bp.PutBuffer(outBuffer)
|
defer bp.PutBuffer(outBuffer)
|
||||||
|
|
||||||
|
|||||||
@@ -340,6 +340,10 @@ func (s *Site) renderRobotsTXT() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if outBuffer.Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return s.publish("robots.txt", outBuffer)
|
return s.publish("robots.txt", outBuffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+24
-6
@@ -376,6 +376,20 @@ func TestNewSiteDefaultLang(t *testing.T) {
|
|||||||
require.Equal(t, hugofs.Os, s.Fs.Destination)
|
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
|
// Issue #1176
|
||||||
func TestSectionNaming(t *testing.T) {
|
func TestSectionNaming(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
@@ -945,11 +959,13 @@ func TestRefLinking(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
// Note: There are no magic in the index.md name. This was fixed in Hugo 0.20.
|
// Note: There are no magic in the index.md name. This was fixed in Hugo 0.20.
|
||||||
// Before that, index.md would wrongly resolve to "/".
|
// Before that, index.md would wrongly resolve to "/".
|
||||||
{"index.md", "", true, "/index/"},
|
// See #3396 -- there is an ambiguity in the examples below, even if they do work.
|
||||||
|
// TODO(bep) better test cases
|
||||||
|
{"index.md", "", true, "/"},
|
||||||
{"common.md", "", true, "/level2/common/"},
|
{"common.md", "", true, "/level2/common/"},
|
||||||
{"3-root.md", "", true, "/level2/level3/3-root/"},
|
{"3-root.md", "", true, "/level2/level3/3-root/"},
|
||||||
{"index.md", "amp", true, "/amp/index/"},
|
{"index.md", "amp", true, "/amp/"},
|
||||||
{"index.md", "amp", false, "http://auth/amp/index/"},
|
{"index.md", "amp", false, "http://auth/amp/"},
|
||||||
} {
|
} {
|
||||||
if out, err := site.Info.refLink(test.link, currentPage, test.relative, test.outputFormat); err != nil || out != test.expected {
|
if out, err := site.Info.refLink(test.link, currentPage, test.relative, test.outputFormat); err != nil || out != test.expected {
|
||||||
t.Errorf("[%d] Expected %s to resolve to (%s), got (%s) - error: %s", i, test.link, test.expected, out, err)
|
t.Errorf("[%d] Expected %s to resolve to (%s), got (%s) - error: %s", i, test.link, test.expected, out, err)
|
||||||
@@ -967,9 +983,11 @@ func TestSourceRelativeLinksing(t *testing.T) {
|
|||||||
|
|
||||||
okresults := map[string]resultMap{
|
okresults := map[string]resultMap{
|
||||||
"index.md": map[string]string{
|
"index.md": map[string]string{
|
||||||
"/docs/rootfile.md": "/rootfile/",
|
"/docs/rootfile.md": "/rootfile/",
|
||||||
"rootfile.md": "/rootfile/",
|
"rootfile.md": "/rootfile/",
|
||||||
"index.md": "/index/",
|
// See #3396 -- this may potentially be ambiguous (i.e. name conflict with home page).
|
||||||
|
// But the user have chosen so. This index.md patterns is more relevant in /sub-folders.
|
||||||
|
"index.md": "/",
|
||||||
"level2/2-root.md": "/level2/2-root/",
|
"level2/2-root.md": "/level2/2-root/",
|
||||||
"/docs/level2/2-root.md": "/level2/2-root/",
|
"/docs/level2/2-root.md": "/level2/2-root/",
|
||||||
"level2/level3/3-root.md": "/level2/level3/3-root/",
|
"level2/level3/3-root.md": "/level2/level3/3-root/",
|
||||||
|
|||||||
@@ -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 {
|
func (th testHelper) replaceDefaultContentLanguageValue(value string) string {
|
||||||
defaultInSubDir := th.Cfg.GetBool("defaultContentLanguageInSubDir")
|
defaultInSubDir := th.Cfg.GetBool("defaultContentLanguageInSubDir")
|
||||||
replace := th.Cfg.GetString("defaultContentLanguage") + "/"
|
replace := th.Cfg.GetString("defaultContentLanguage") + "/"
|
||||||
|
|||||||
+274
@@ -0,0 +1,274 @@
|
|||||||
|
// 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 getCurrentBranch() (string, error) {
|
||||||
|
return gitShort("rev-parse", "--abbrev-ref", "HEAD")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMaster() bool {
|
||||||
|
curr, _ := getCurrentBranch()
|
||||||
|
return curr == "master"
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
// 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])
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetCurrentBranch(t *testing.T) {
|
||||||
|
curr, err := getCurrentBranch()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, (curr == "master"), isMaster())
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 commands defines and implements command-line commands and flags
|
||||||
|
// used by Hugo. Commands and flags are implemented using Cobra.
|
||||||
|
|
||||||
|
package releaser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"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))
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
// 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 = "release:"
|
||||||
|
|
||||||
|
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.
|
||||||
|
finalVersion = newVersion.Next()
|
||||||
|
} else if r.patch > 0 {
|
||||||
|
newVersion = current.NextPatchLevel(r.patch)
|
||||||
|
finalVersion = newVersion.Next()
|
||||||
|
} 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 := git("push", "origin", tag); 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)
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
"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.19.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.19.3",
|
||||||
|
"0.20-DEV",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
New(3, 2, true),
|
||||||
|
helpers.HugoVersion{Number: 0.20, PatchLevel: 2},
|
||||||
|
"0.20.2",
|
||||||
|
"0.21-DEV",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
New(3, 0, true),
|
||||||
|
helpers.HugoVersion{Number: 0.20, Suffix: "", PatchLevel: 2},
|
||||||
|
"0.20.3",
|
||||||
|
"0.21-DEV",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
New(3, 1, true),
|
||||||
|
helpers.HugoVersion{Number: 0.20, Suffix: "", PatchLevel: 2},
|
||||||
|
"0.20.3",
|
||||||
|
"0.21-DEV",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
New(3, 2, true),
|
||||||
|
helpers.HugoVersion{Number: 0.20, Suffix: "", PatchLevel: 3},
|
||||||
|
"0.20.3",
|
||||||
|
"0.21-DEV",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
v1, v2 := test.handler.calculateVersions(test.version)
|
||||||
|
require.Equal(t, test.v1, v1.String(), fmt.Sprintf("[%d] Release version", i))
|
||||||
|
require.Equal(t, test.v2, v2.String(), fmt.Sprintf("[%d] Final version", i))
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
name: hugo
|
name: hugo
|
||||||
version: "0.20.1"
|
version: "0.20.7"
|
||||||
summary: Fast and Flexible Static Site Generator
|
summary: Fast and Flexible Static Site Generator
|
||||||
description: |
|
description: |
|
||||||
Hugo is a static HTML and CSS website generator written in Go. It is
|
Hugo is a static HTML and CSS website generator written in Go. It is
|
||||||
|
|||||||
+11
-12
@@ -97,23 +97,21 @@ func (t *templateHandler) PrintErrors() {
|
|||||||
// Lookup tries to find a template with the given name in both template
|
// Lookup tries to find a template with the given name in both template
|
||||||
// collections: First HTML, then the plain text template collection.
|
// collections: First HTML, then the plain text template collection.
|
||||||
func (t *templateHandler) Lookup(name string) *tpl.TemplateAdapter {
|
func (t *templateHandler) Lookup(name string) *tpl.TemplateAdapter {
|
||||||
var te *tpl.TemplateAdapter
|
|
||||||
|
|
||||||
isTextTemplate := strings.HasPrefix(name, textTmplNamePrefix)
|
if strings.HasPrefix(name, textTmplNamePrefix) {
|
||||||
|
// The caller has explicitly asked for a text template, so only look
|
||||||
if isTextTemplate {
|
// in the text template collection.
|
||||||
// The templates are stored without the prefix identificator.
|
// The templates are stored without the prefix identificator.
|
||||||
name = strings.TrimPrefix(name, textTmplNamePrefix)
|
name = strings.TrimPrefix(name, textTmplNamePrefix)
|
||||||
te = t.text.Lookup(name)
|
return t.text.Lookup(name)
|
||||||
} else {
|
|
||||||
te = t.html.Lookup(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if te == nil {
|
// Look in both
|
||||||
return nil
|
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 {
|
func (t *templateHandler) clone(d *deps.Deps) *templateHandler {
|
||||||
@@ -459,9 +457,10 @@ func (t *templateHandler) loadTemplates(absPath string, prefix string) {
|
|||||||
|
|
||||||
func (t *templateHandler) initFuncs() {
|
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} {
|
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,
|
// The URL funcs in the funcMap is somewhat language dependent,
|
||||||
// so we need to wait until the language and site config is loaded.
|
// so we need to wait until the language and site config is loaded.
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"strings"
|
"strings"
|
||||||
|
texttemplate "text/template"
|
||||||
|
|
||||||
bp "github.com/spf13/hugo/bufferpool"
|
bp "github.com/spf13/hugo/bufferpool"
|
||||||
|
|
||||||
@@ -31,17 +32,12 @@ type templateFuncster struct {
|
|||||||
cachedPartials partialCache
|
cachedPartials partialCache
|
||||||
image *imageHandler
|
image *imageHandler
|
||||||
|
|
||||||
// Make sure each funcster gets its own TemplateFinder to get
|
|
||||||
// proper text and HTML template separation.
|
|
||||||
Tmpl templateFuncsterTemplater
|
|
||||||
|
|
||||||
*deps.Deps
|
*deps.Deps
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTemplateFuncster(deps *deps.Deps, t templateFuncsterTemplater) *templateFuncster {
|
func newTemplateFuncster(deps *deps.Deps) *templateFuncster {
|
||||||
return &templateFuncster{
|
return &templateFuncster{
|
||||||
Deps: deps,
|
Deps: deps,
|
||||||
Tmpl: t,
|
|
||||||
cachedPartials: partialCache{p: make(map[string]interface{})},
|
cachedPartials: partialCache{p: make(map[string]interface{})},
|
||||||
image: &imageHandler{fs: deps.Fs, imageConfigCache: map[string]image.Config{}},
|
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
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t.Tmpl.(type) {
|
if _, ok := templ.Template.(*texttemplate.Template); ok {
|
||||||
case *htmlTemplates:
|
|
||||||
return template.HTML(b.String()), nil
|
|
||||||
case *textTemplates:
|
|
||||||
return b.String(), nil
|
return b.String(), nil
|
||||||
default:
|
|
||||||
panic("Unknown type")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return template.HTML(b.String()), nil
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2221,5 +2221,5 @@ func (t *templateFuncster) initFuncMap() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.funcMap = funcMap
|
t.funcMap = funcMap
|
||||||
t.Tmpl.setFuncs(funcMap)
|
t.Tmpl.(*templateHandler).setFuncs(funcMap)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2869,20 +2869,27 @@ func TestPartialHTMLAndText(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config.WithTemplate = func(templ tpl.TemplateHandler) error {
|
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
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use "foo" here to say that the extension doesn't really matter in this scenario.
|
if err := templ.AddTemplate("partials/test.html", "HTML Name: {{ .Name }}"); err != nil {
|
||||||
// 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 {
|
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2903,8 +2910,12 @@ func TestPartialHTMLAndText(t *testing.T) {
|
|||||||
resultText, err := templ.ExecuteToString(data)
|
resultText, err := templ.ExecuteToString(data)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Contains(t, resultHTML, "HTML Test Partial: HTML Name: a+b+c")
|
require.Contains(t, resultHTML, "HTML Test|HTML:HTML Name: a+b+c|Text:Text Name: a+b+c")
|
||||||
require.Contains(t, resultText, "Text Test Partial: Text Name: a+b+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+b+c|Text:Text Name: a+b+c")
|
||||||
|
require.Contains(t, resultText, `CSS plain: <style type="text/css">body { background-color: blue; }`)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var metaTagsCheck = regexp.MustCompile(`(?i)<meta\s+name=['|"]?generator['|"]?`)
|
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.
|
// HugoGeneratorInject injects a meta generator tag for Hugo if none present.
|
||||||
func HugoGeneratorInject(ct contentTransformer) {
|
func HugoGeneratorInject(ct contentTransformer) {
|
||||||
|
|||||||
Reference in New Issue
Block a user