From 4b9105f644e54285e3f8b4c5f100e3fbb7fe68ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Wed, 19 Apr 2017 14:08:45 +0200 Subject: [PATCH] Exctract git log to release notes Updates #3358 --- release/git.go | 108 ++++++++++++++++++++++++++++ release/git_test.go | 47 ++++++++++++ release/github.go | 40 +++++++++++ release/github_test.go | 27 +++++++ release/release.go | 23 +++--- release/releasenotes_writer.go | 65 +++++++++++++++++ release/releasenotes_writer_test.go | 38 ++++++++++ 7 files changed, 336 insertions(+), 12 deletions(-) create mode 100644 release/git.go create mode 100644 release/git_test.go create mode 100644 release/github.go create mode 100644 release/github_test.go create mode 100644 release/releasenotes_writer.go create mode 100644 release/releasenotes_writer_test.go diff --git a/release/git.go b/release/git.go new file mode 100644 index 00000000000..65047aa17b6 --- /dev/null +++ b/release/git.go @@ -0,0 +1,108 @@ +// 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 + +import ( + "fmt" + "os/exec" + "regexp" + "strconv" + "strings" +) + +var issueRe = regexp.MustCompile(`(?i)[Updates?|Closes?|Fix.*|See] #(\d+)`) + +type gitInfo struct { + Hash string + Author string + Subject string + Body string + + GitHubCommit *gitHubCommit +} + +func (g gitInfo) Issues() []int { + return extractIssues(g.Body) +} + +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() (gitInfos, error) { + var g gitInfos + + log, err := gitLog() + 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], + } + gc, err := fetchCommit(gi.Hash) + if err == nil { + gi.GitHubCommit = &gc + } + g = append(g, gi) + } + + return g, nil +} + +func gitLog() (string, error) { + prevTag, err := gitShort("describe", "--tags", "--abbrev=0", "--always", "HEAD^") + if err != nil { + return "", err + } + log, err := git("log", "--pretty=format:%x1e%h%x1f%aE%x1f%s%x1f%b", "--abbrev-commit", prevTag+"..HEAD") + if err != nil { + return ",", err + } + + return log, err +} + +func gitShort(args ...string) (output string, err error) { + output, err = git(args...) + return strings.Replace(strings.Split(output, "\n")[0], "'", "", -1), err +} diff --git a/release/git_test.go b/release/git_test.go new file mode 100644 index 00000000000..c6c1e552523 --- /dev/null +++ b/release/git_test.go @@ -0,0 +1,47 @@ +// 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 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGitInfos(t *testing.T) { + infos, err := getGitInfos() + + 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]) + +} diff --git a/release/github.go b/release/github.go new file mode 100644 index 00000000000..705a7eb4241 --- /dev/null +++ b/release/github.go @@ -0,0 +1,40 @@ +package release + +import ( + "encoding/json" + "fmt" + "net/http" +) + +const gitHubCommitsApi = "https://api.github.com/repos/spf13/hugo/commits/%s" + +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"` +} + +func fetchCommit(ref string) (gitHubCommit, error) { + var commit gitHubCommit + + u := fmt.Sprintf(gitHubCommitsApi, ref) + + resp, err := http.Get(u) + if err != nil { + return commit, err + } + defer resp.Body.Close() + + err = json.NewDecoder(resp.Body).Decode(&commit) + if err != nil { + return commit, err + } + + return commit, nil +} diff --git a/release/github_test.go b/release/github_test.go new file mode 100644 index 00000000000..f7096c604cc --- /dev/null +++ b/release/github_test.go @@ -0,0 +1,27 @@ +// 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 + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGitHubLookupCommit(t *testing.T) { + commit, err := fetchCommit("86a97dbd") + require.NoError(t, err) + fmt.Println(commit) +} diff --git a/release/release.go b/release/release.go index 81eccb4b314..00e4770d4cd 100644 --- a/release/release.go +++ b/release/release.go @@ -11,9 +11,8 @@ // 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 release implements a set of utilities and a wrapper around Goreleaser +// to help automate the Hugo release process. package release import ( @@ -100,6 +99,15 @@ func (r *ReleaseHandler) Run() error { } if r.shouldPrepare() { + log, err := gitLog() + if err != nil { + return err + } + fmt.Println("LOG:\n", log) + + if true { + return nil + } if err := bumpVersions(newVersion); err != nil { return err } @@ -170,15 +178,6 @@ func release() error { return nil } -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 bumpVersions(ver helpers.HugoVersion) error { fromDev := "" toDev := "" diff --git a/release/releasenotes_writer.go b/release/releasenotes_writer.go new file mode 100644 index 00000000000..826f352a1fe --- /dev/null +++ b/release/releasenotes_writer.go @@ -0,0 +1,65 @@ +// 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 release + +import ( + "fmt" + "io" + "text/template" +) + +const ( + issueLinkTemplate = "[#%d](https://github.com/spf13/hugo/issues/%d)" + linkTemplate = "[%s](%s)" + releaseNotesMarkdownTemplate = ` +# Changes + +{{ range . }} +{{- if .GitHubCommit -}} +* {{ . | commitURL }} {{ .Subject }} {{ . | authorURL }} {{ range .Issues }}{{. | issue }} {{ end }} +{{ else }} +* {{ .Hash}} {{ .Subject }} {{ range .Issues }}#{{ . }} {{ end }} +{{ end -}} +{{- end -}} +` +) + +var templateFuncs = template.FuncMap{ + "issue": func(id int) string { + return fmt.Sprintf(issueLinkTemplate, id, id) + }, + "commitURL": func(info gitInfo) string { + return fmt.Sprintf(linkTemplate, info.Hash, info.GitHubCommit.HtmlURL) + }, + "authorURL": func(info gitInfo) string { + return fmt.Sprintf(linkTemplate, "@"+info.GitHubCommit.Author.Login, info.GitHubCommit.Author.HtmlURL) + }, +} + +func writeReleaseNotes(infos gitInfos, to io.Writer) error { + tmpl, err := template.New("").Funcs(templateFuncs).Parse(releaseNotesMarkdownTemplate) + if err != nil { + return err + } + + err = tmpl.Execute(to, infos) + if err != nil { + return err + } + + return nil + +} diff --git a/release/releasenotes_writer_test.go b/release/releasenotes_writer_test.go new file mode 100644 index 00000000000..750b224e19d --- /dev/null +++ b/release/releasenotes_writer_test.go @@ -0,0 +1,38 @@ +// 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 release + +import ( + "bytes" + "fmt" + "testing" + + // "github.com/spf13/hugo/helpers" + "github.com/stretchr/testify/require" +) + +func TestReleaseNotesWriter(t *testing.T) { + + var b bytes.Buffer + + infos, err := getGitInfos() + require.NoError(t, err) + + require.NoError(t, writeReleaseNotes(infos, &b)) + + fmt.Println(">>>", b.String()) +}