diff --git a/.circleci/config.yml b/.circleci/config.yml index 07008673b..9f16e900c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,51 +1,114 @@ -defaults: &defaults - docker: - - image: bepsays/ci-goreleaser:1.21900.20000 - environment: - CGO_ENABLED: "0" +parameters: +# v2: 11m. +defaults: &defaults + resource_class: large + docker: + - image: bepsays/ci-hugoreleaser:1.21900.20000 +environment: &buildenv + GOMODCACHE: /root/project/gomodcache version: 2 jobs: - build: - <<: *defaults + prepare_release: + <<: *defaults + environment: &buildenv + GOMODCACHE: /root/project/gomodcache steps: - - checkout: + - &remote-docker + setup_remote_docker: + version: 20.10.14 + - checkout: path: hugo + - &git-config + run: + command: | + git config --global user.email "bjorn.erik.pedersen+hugoreleaser@gmail.com" + git config --global user.name "hugoreleaser" - run: - command: | - git clone git@github.com:gohugoio/hugoDocs.git - cd hugo - go mod download - sleep 5 - go mod verify - - persist_to_workspace: - root: . - paths: . - release: - <<: *defaults + command: | + cd hugo + go mod download + go run -tags release main.go release --step 1 + - save_cache: + key: git-sha-{{ .Revision }} + paths: + - hugo + - gomodcache + build_container1: + <<: [*defaults] + environment: + <<: [*buildenv] steps: - - attach_workspace: - at: /root/project + - &restore-cache + restore_cache: + key: git-sha-{{ .Revision }} - run: - command: | - cd hugo - git config --global user.email "bjorn.erik.pedersen+hugoreleaser@gmail.com" - git config --global user.name "hugoreleaser" - go run -tags release main.go release -r ${CIRCLE_BRANCH} - + no_output_timeout: 20m + command: | + mkdir -p /tmp/files/dist1 + cd hugo + hugoreleaser build -paths "builds/container1/**" -workers 3 -dist /tmp/files/dist1 -chunks $CIRCLE_NODE_TOTAL -chunk-index $CIRCLE_NODE_INDEX + - &persist-workspace + persist_to_workspace: + root: /tmp/files + paths: + - dist1 + - dist2 + parallelism: 7 + build_container2: + <<: [*defaults] + environment: + <<: [*buildenv] + docker: + - image: bepsays/ci-hugoreleaser-linux-arm64:1.21900.20000 + steps: + - *restore-cache + - &attach-workspace + attach_workspace: + at: /tmp/workspace + - run: + command: | + mkdir -p /tmp/files/dist2 + cd hugo + hugoreleaser build -paths "builds/container2/**" -workers 1 -dist /tmp/files/dist2 + - *persist-workspace + archive_and_release: + <<: [*defaults] + environment: + <<: [*buildenv] + steps: + - *restore-cache + - *attach-workspace + - *git-config + - run: + command: | + cp -a /tmp/workspace/dist1/. ./hugo/dist + cp -a /tmp/workspace/dist2/. ./hugo/dist + - run: + command: | + cd hugo + hugoreleaser archive + hugoreleaser release + go run -tags release main.go release --step 2 workflows: version: 2 release: jobs: - - build: + - prepare_release: filters: branches: only: /release-.*/ - - hold: - type: approval + - build_container1: requires: - - build - - release: + - prepare_release + - build_container2: + requires: + - prepare_release + - archive_and_release: context: org-global requires: - - hold + - build_container1 + - build_container2 + + + diff --git a/commands/release.go b/commands/release.go index 6decda9ea..2072f3eb2 100644 --- a/commands/release.go +++ b/commands/release.go @@ -17,8 +17,6 @@ package commands import ( - "errors" - "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/releaser" "github.com/spf13/cobra" @@ -29,10 +27,9 @@ var _ cmder = (*releaseCommandeer)(nil) type releaseCommandeer struct { cmd *cobra.Command - version string - - skipPublish bool - try bool + step int + skipPush bool + try bool } func createReleaser() cmder { @@ -50,9 +47,9 @@ func createReleaser() cmder { return r.release() } - r.cmd.PersistentFlags().StringVarP(&r.version, "rel", "r", "", "new release version, i.e. 0.25.1") - r.cmd.PersistentFlags().BoolVarP(&r.skipPublish, "skip-publish", "", false, "skip all publishing pipes of the release") - r.cmd.PersistentFlags().BoolVarP(&r.try, "try", "", false, "simulate a release, i.e. no changes") + r.cmd.PersistentFlags().BoolVarP(&r.skipPush, "skip-push", "", false, "skip pushing to remote") + r.cmd.PersistentFlags().BoolVarP(&r.try, "try", "", false, "no changes") + r.cmd.PersistentFlags().IntVarP(&r.step, "step", "", 0, "step to run (1: set new version 2: prepare next dev version)") return r } @@ -65,8 +62,10 @@ func (c *releaseCommandeer) flagsToConfig(cfg config.Provider) { } func (r *releaseCommandeer) release() error { - if r.version == "" { - return errors.New("must set the --rel flag to the relevant version number") + rel, err := releaser.New(r.skipPush, r.try, r.step) + if err != nil { + return err } - return releaser.New(r.version, r.skipPublish, r.try).Run() + + return rel.Run() } diff --git a/goreleaser.yml b/goreleaser.yml deleted file mode 100644 index d2cd273ce..000000000 --- a/goreleaser.yml +++ /dev/null @@ -1,214 +0,0 @@ -project_name: hugo -env: - - GO111MODULE=on - - GOPROXY=https://proxy.golang.org -before: - hooks: - - go mod download -builds: - - - binary: hugo - id: hugo - ldflags: -s -w -X github.com/gohugoio/hugo/common/hugo.vendorInfo=gohugoio - env: - - CGO_ENABLED=0 - flags: - - -buildmode - - exe - goos: - - darwin - - linux - - windows - goarch: - - amd64 - - 386 - - arm - - arm64 - goarm: - - 7 - ignore: - - goos: darwin - goarch: 386 - - - binary: hugo - id: hugo_unix - ldflags: -s -w -X github.com/gohugoio/hugo/common/hugo.vendorInfo=gohugoio - env: - - CGO_ENABLED=0 - flags: - - -buildmode - - exe - goos: - - freebsd - - netbsd - - openbsd - - dragonfly - goarch: - - amd64 - - - binary: hugo - id: hugo_extended_windows - ldflags: - - -s -w -X github.com/gohugoio/hugo/common/hugo.vendorInfo=gohugoio - - "-extldflags '-static'" - env: - - CGO_ENABLED=1 - - CC=x86_64-w64-mingw32-gcc - - CXX=x86_64-w64-mingw32-g++ - flags: - - -buildmode - - exe - - -tags - - extended - goos: - - windows - goarch: - - amd64 - - binary: hugo - id: hugo_extended_darwin - ldflags: -s -w -X github.com/gohugoio/hugo/common/hugo.vendorInfo=gohugoio - env: - - CGO_ENABLED=1 - - CC=o64-clang - - CXX=o64-clang++ - flags: - - -buildmode - - exe - - -tags - - extended - goos: - - darwin - goarch: - - amd64 - - arm64 - - binary: hugo - id: hugo_extended_linux - ldflags: -s -w -X github.com/gohugoio/hugo/common/hugo.vendorInfo=gohugoio - env: - - CGO_ENABLED=1 - flags: - - -buildmode - - exe - - -tags - - extended - goos: - - linux - goarch: - - amd64 - hooks: - post: ./goreleaser-hook-post-linux.sh -release: - draft: true - -archives: - - - id: "hugo" - builds: ['hugo', 'hugo_unix', 'hugo_fat_darwin'] - format: tar.gz - format_overrides: - - goos: windows - format: zip - name_template: "{{.ProjectName}}_{{.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 - - - id: "hugo_extended" - builds: ['hugo_extended_windows', 'hugo_extended_linux', 'hugo_extended_fat_darwin'] - format: tar.gz - format_overrides: - - goos: windows - format: zip - name_template: "{{.ProjectName}}_extended_{{.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 - -universal_binaries: -- - id: hugo_fat_darwin - - ids: - - hugo - - replace: true - -- - id: hugo_extended_fat_darwin - - ids: - - hugo_extended_darwin - - replace: true - -nfpms: - - - id: "hugo" - builds: ['hugo'] - formats: - - deb - vendor: "gohugo.io" - homepage: "https://gohugo.io/" - maintainer: "Bjørn Erik Pedersen " - description: "A Fast and Flexible Static Site Generator built with love in GoLang." - license: "Apache-2.0" - file_name_template: "{{.ProjectName}}_{{.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 - - - id: "hugo_extended" - builds: ['hugo_extended_linux'] - formats: - - deb - vendor: "gohugo.io" - homepage: "https://gohugo.io/" - maintainer: "Bjørn Erik Pedersen " - description: "A Fast and Flexible Static Site Generator built with love in GoLang." - license: "Apache-2.0" - file_name_template: "{{.ProjectName}}_extended_{{.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 diff --git a/hugoreleaser.env b/hugoreleaser.env new file mode 100644 index 000000000..2c8d64913 --- /dev/null +++ b/hugoreleaser.env @@ -0,0 +1,6 @@ +HUGO_RELEASE_NAME=New release setup + +# Release env. +# These will be replaced by script before release. +HUGORELEASER_TAG=xxx +HUGORELEASER_COMMITISH=xxx \ No newline at end of file diff --git a/hugoreleaser.toml b/hugoreleaser.toml new file mode 100644 index 000000000..e9bf8b6f1 --- /dev/null +++ b/hugoreleaser.toml @@ -0,0 +1,232 @@ +project = "hugo" + +[go_settings] + go_proxy = "https://proxy.golang.org" + go_exe = "go" + +[build_settings] + binary = "hugo" + flags = ["-buildmode", "exe"] + env = ["CGO_ENABLED=0"] + ldflags = "-s -w -X github.com/gohugoio/hugo/common/hugo.vendorInfo=gohugoio" + +[archive_settings] + name_template = "{{ .Project }}_{{ .Tag | trimPrefix `v` }}_{{ .Goos }}-{{ .Goarch }}" + extra_files = [ + { source_path = "README.md", target_path = "README.md" }, + { source_path = "LICENSE", target_path = "LICENSE" }, + ] + [archive_settings.type] + format = "tar.gz" + extension = ".tar.gz" + [archive_settings.replacements] + amd64 = "64bit" + 386 = "32bit" + arm = "ARM" + arm64 = "ARM64" + darwin = "macOS" + linux = "Linux" + windows = "Windows" + openbsd = "OpenBSD" + netbsd = "NetBSD" + freebsd = "FreeBSD" + dragonfly = "DragonFlyBSD" + +[release_settings] + name = "${HUGO_RELEASE_NAME}" + type = "github" + repository = "hugo" + repository_owner = "gohugoio" + draft = true + prerelease = false + + [release_settings.release_notes_settings] + # Use Hugoreleaser's autogenerated release notes. + generate = true + + # Collapse relases with < 10 changes below one title. + short_threshold = 10 + short_title = "What's Changed" + + groups = [ + # Group the changes in the release notes by title. + # You need at least one. + # The groups will be tested in order until a match is found. + # The titles will so be listed in the given order in the release note. + # Any match with ignore=true title will be dropped. + { regexp = "Merge commit|Squashed", ignore = true }, + { title = "Bug fixes", regexp = "fix", ordinal = 10 }, + { title = "Dependency Updates", regexp = "deps", ordinal = 30 }, + { title = "Build Setup", regexp = "(snap|release|update to)", ordinal = 40 }, + { title = "Documentation", regexp = "(doc|readme)", ordinal = 40 }, + { title = "Improvements", regexp = ".*", ordinal = 20 }, + ] + +[[builds]] + path = "container1/unix/regular" + + [[builds.os]] + goos = "darwin" + [[builds.os.archs]] + goarch = "universal" + [[builds.os]] + goos = "linux" + [[builds.os.archs]] + goarch = "amd64" + [[builds.os.archs]] + goarch = "arm64" + [[builds.os.archs]] + goarch = "arm" + [builds.os.archs.build_settings] + env = ["CGO_ENABLED=0", "GOARM=7"] + + # Unix BSD variants + [[builds.os]] + goos = "dragonfly" + [[builds.os.archs]] + goarch = "amd64" + [[builds.os]] + goos = "freebsd" + [[builds.os.archs]] + goarch = "amd64" + [[builds.os]] + goos = "netbsd" + [[builds.os.archs]] + goarch = "amd64" + [[builds.os]] + goos = "openbsd" + [[builds.os.archs]] + goarch = "amd64" + +[[builds]] + path = "container1/unix/extended" + + [builds.build_settings] + flags = ["-buildmode", "exe", "-tags", "extended"] + env = ["CGO_ENABLED=1"] + + [[builds.os]] + goos = "darwin" + [builds.os.build_settings] + env = ["CGO_ENABLED=1", "CC=o64-clang", "CXX=o64-clang++"] + [[builds.os.archs]] + goarch = "universal" + [[builds.os]] + goos = "linux" + [[builds.os.archs]] + goarch = "amd64" + +[[builds]] + path = "container2/linux/extended" + + [builds.build_settings] + flags = ["-buildmode", "exe", "-tags", "extended"] + + [[builds.os]] + goos = "linux" + [builds.os.build_settings] + env = [ + "CGO_ENABLED=1", + "CC=aarch64-linux-gnu-gcc", + "CXX=aarch64-linux-gnu-g++", + ] + [[builds.os.archs]] + goarch = "arm64" + +[[builds]] + path = "container1/windows/regular" + + [[builds.os]] + goos = "windows" + [builds.os.build_settings] + binary = "hugo.exe" + [[builds.os.archs]] + goarch = "amd64" + [[builds.os.archs]] + goarch = "arm64" + +[[builds]] + path = "container1/windows/extended" + + [builds.build_settings] + flags = ["-buildmode", "exe", "-tags", "extended"] + env = [ + "CGO_ENABLED=1", + "CC=x86_64-w64-mingw32-gcc", + "CXX=x86_64-w64-mingw32-g++", + ] + ldflags = "-s -w -X github.com/gohugoio/hugo/common/hugo.vendorInfo=gohugoio -extldflags '-static'" + + [[builds.os]] + goos = "windows" + [builds.os.build_settings] + binary = "hugo.exe" + [[builds.os.archs]] + goarch = "amd64" + +[[archives]] + paths = ["builds/container1/unix/regular/**"] +[[archives]] + paths = ["builds/container1/unix/extended/**"] + [archives.archive_settings] + name_template = "{{ .Project }}_extended_{{ .Tag | trimPrefix `v` }}_{{ .Goos }}-{{ .Goarch }}" +[[archives]] + # Only extended builds in container2. + paths = ["builds/container2/**"] + [archives.archive_settings] + name_template = "{{ .Project }}_extended_{{ .Tag | trimPrefix `v` }}_{{ .Goos }}-{{ .Goarch }}" +[[archives]] + paths = ["builds/**/windows/regular/**"] + [archives.archive_settings.type] + format = "zip" + extension = ".zip" +[[archives]] + paths = ["builds/**/windows/extended/**"] + [archives.archive_settings] + name_template = "{{ .Project }}_extended_{{ .Tag | trimPrefix `v` }}_{{ .Goos }}-{{ .Goarch }}" + [archives.archive_settings.type] + format = "zip" + extension = ".zip" +[[archives]] + paths = ["builds/**/regular/linux/{arm64,amd64}"] + [archives.archive_settings] + [archives.archive_settings.type] + format = "_plugin" + extension = ".deb" + [archives.archive_settings.plugin] + id = "deb" + type = "gorun" + command = "github.com/gohugoio/hugoreleaser-archive-plugins/deb@v0.5.0" + [archives.archive_settings.custom_settings] + vendor = "gohugo.io" + homepage = "https://github.com/gohugoio/hugoreleaser" + maintainer = "Bjørn Erik Pedersen " + description = "Build, archive and release Go programs." + license = "Apache-2.0" +[[archives]] + paths = ["builds/**/extended/linux/{arm64,amd64}"] + [archives.archive_settings] + name_template = "{{ .Project }}_extended_{{ .Tag | trimPrefix `v` }}_{{ .Goos }}-{{ .Goarch }}" + [archives.archive_settings.type] + format = "_plugin" + extension = ".deb" + [archives.archive_settings.plugin] + id = "deb" + type = "gorun" + command = "github.com/gohugoio/hugoreleaser-archive-plugins/deb@latest" + [archives.archive_settings.custom_settings] + vendor = "gohugo.io" + homepage = "https://github.com/gohugoio/hugoreleaser" + maintainer = "Bjørn Erik Pedersen " + description = "Build, archive and release Go programs." + license = "Apache-2.0" + +[[releases]] + paths = ["archives/**"] + path = "r1" + + # The above should allow the following build commands: + # hugoreleaser build -paths "builds/container1/**" + # hugoreleaser build -paths "builds/container2/**" + # hugoreleaser archive + # hugoreleaser release diff --git a/goreleaser-hook-post-linux.sh b/release-hook-post-linux.sh similarity index 100% rename from goreleaser-hook-post-linux.sh rename to release-hook-post-linux.sh diff --git a/releaser/git.go b/releaser/git.go deleted file mode 100644 index ced363a9d..000000000 --- a/releaser/git.go +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package releaser - -import ( - "fmt" - "regexp" - "sort" - "strconv" - "strings" - - "github.com/gohugoio/hugo/common/hexec" -) - -var issueRe = regexp.MustCompile(`(?i)(?:Updates?|Closes?|Fix.*|See) #(\d+)`) - -type changeLog struct { - Version string - Notes gitInfos - All gitInfos - Docs gitInfos - - // Overall stats - Repo *gitHubRepo - ContributorCount int - ThemeCount int -} - -func newChangeLog(infos, docInfos gitInfos) *changeLog { - log := &changeLog{ - Docs: docInfos, - } - - for _, info := range infos { - // TODO(bep) improve - if regexp.MustCompile("(?i)deprecate|note").MatchString(info.Subject) { - log.Notes = append(log.Notes, info) - } - - log.All = append(log.All, info) - info.Subject = strings.TrimSpace(info.Subject) - - } - - return log -} - -type gitInfo struct { - Hash string - Author string - Subject string - Body string - - GitHubCommit *gitHubCommit -} - -func (g gitInfo) Issues() []int { - return extractIssues(g.Body) -} - -func (g gitInfo) AuthorID() string { - if g.GitHubCommit != nil { - return g.GitHubCommit.Author.Login - } - return g.Author -} - -func extractIssues(body string) []int { - var i []int - m := issueRe.FindAllStringSubmatch(body, -1) - for _, mm := range m { - issueID, err := strconv.Atoi(mm[1]) - if err != nil { - continue - } - i = append(i, issueID) - } - return i -} - -type gitInfos []gitInfo - -func git(args ...string) (string, error) { - cmd, _ := hexec.SafeCommand("git", args...) - out, err := cmd.CombinedOutput() - if err != nil { - return "", fmt.Errorf("git failed: %q: %q (%q)", err, out, args) - } - return string(out), nil -} - -func getGitInfos(tag, repo, repoPath string, remote bool) (gitInfos, error) { - return getGitInfosBefore("HEAD", tag, repo, repoPath, remote) -} - -type countribCount struct { - Author string - GitHubAuthor gitHubAuthor - Count int -} - -func (c countribCount) AuthorLink() string { - if c.GitHubAuthor.HTMLURL != "" { - return fmt.Sprintf("[@%s](%s)", c.GitHubAuthor.Login, c.GitHubAuthor.HTMLURL) - } - - if !strings.Contains(c.Author, "@") { - return c.Author - } - - return c.Author[:strings.Index(c.Author, "@")] -} - -type contribCounts []countribCount - -func (c contribCounts) Less(i, j int) bool { return c[i].Count > c[j].Count } -func (c contribCounts) Len() int { return len(c) } -func (c contribCounts) Swap(i, j int) { c[i], c[j] = c[j], c[i] } - -func (g gitInfos) ContribCountPerAuthor() contribCounts { - var c contribCounts - - counters := make(map[string]countribCount) - - for _, gi := range g { - authorID := gi.AuthorID() - if count, ok := counters[authorID]; ok { - count.Count = count.Count + 1 - counters[authorID] = count - } else { - var ghA gitHubAuthor - if gi.GitHubCommit != nil { - ghA = gi.GitHubCommit.Author - } - authorCount := countribCount{Count: 1, Author: gi.Author, GitHubAuthor: ghA} - counters[authorID] = authorCount - } - } - - for _, v := range counters { - c = append(c, v) - } - - sort.Sort(c) - return c -} - -func getGitInfosBefore(ref, tag, repo, repoPath string, remote bool) (gitInfos, error) { - client := newGitHubAPI(repo) - var g gitInfos - - log, err := gitLogBefore(ref, tag, repoPath) - if err != nil { - return g, err - } - - log = strings.Trim(log, "\n\x1e'") - entries := strings.Split(log, "\x1e") - - for _, entry := range entries { - items := strings.Split(entry, "\x1f") - gi := gitInfo{} - - if len(items) > 0 { - gi.Hash = items[0] - } - if len(items) > 1 { - gi.Author = items[1] - } - if len(items) > 2 { - gi.Subject = items[2] - } - if len(items) > 3 { - gi.Body = items[3] - } - - if remote && gi.Hash != "" { - gc, err := client.fetchCommit(gi.Hash) - if err == nil { - gi.GitHubCommit = &gc - } - } - g = append(g, gi) - } - - return g, nil -} - -// Ignore autogenerated commits etc. in change log. This is a regexp. -const ignoredCommits = "snapcraft:|Merge commit|Squashed" - -func gitLogBefore(ref, tag, repoPath string) (string, error) { - var prevTag string - var err error - if tag != "" { - prevTag = tag - } else { - prevTag, err = gitVersionTagBefore(ref) - if err != nil { - return "", err - } - } - - defaultArgs := []string{"log", "-E", fmt.Sprintf("--grep=%s", ignoredCommits), "--invert-grep", "--pretty=format:%x1e%h%x1f%aE%x1f%s%x1f%b", "--abbrev-commit", prevTag + ".." + ref} - - var args []string - - if repoPath != "" { - args = append([]string{"-C", repoPath}, defaultArgs...) - } else { - args = defaultArgs - } - - log, err := git(args...) - if err != nil { - return ",", err - } - - return log, err -} - -func gitVersionTagBefore(ref string) (string, error) { - return gitShort("describe", "--tags", "--abbrev=0", "--always", "--match", "v[0-9]*", ref+"^") -} - -func gitShort(args ...string) (output string, err error) { - output, err = git(args...) - return strings.Replace(strings.Split(output, "\n")[0], "'", "", -1), err -} - -func tagExists(tag string) (bool, error) { - out, err := git("tag", "-l", tag) - if err != nil { - return false, err - } - - if strings.Contains(out, tag) { - return true, nil - } - - return false, nil -} diff --git a/releaser/git_test.go b/releaser/git_test.go deleted file mode 100644 index ff77eb8c6..000000000 --- a/releaser/git_test.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package releaser - -import ( - "testing" - - qt "github.com/frankban/quicktest" -) - -func TestGitInfos(t *testing.T) { - c := qt.New(t) - skipIfCI(t) - infos, err := getGitInfos("v0.20", "hugo", "", false) - - c.Assert(err, qt.IsNil) - c.Assert(len(infos) > 0, qt.Equals, true) -} - -func TestIssuesRe(t *testing.T) { - c := qt.New(t) - - body := ` -This is a commit message. - -Updates #123 -Fix #345 -closes #543 -See #456 - ` - - issues := extractIssues(body) - - c.Assert(len(issues), qt.Equals, 4) - c.Assert(issues[0], qt.Equals, 123) - c.Assert(issues[2], qt.Equals, 543) - - bodyNoIssues := ` -This is a commit message without issue refs. - -But it has e #10 to make old regexp confused. -Streets #20. - ` - - emptyIssuesList := extractIssues(bodyNoIssues) - c.Assert(len(emptyIssuesList), qt.Equals, 0) -} - -func TestGitVersionTagBefore(t *testing.T) { - skipIfCI(t) - c := qt.New(t) - v1, err := gitVersionTagBefore("v0.18") - c.Assert(err, qt.IsNil) - c.Assert(v1, qt.Equals, "v0.17") -} - -func TestTagExists(t *testing.T) { - skipIfCI(t) - c := qt.New(t) - b1, err := tagExists("v0.18") - c.Assert(err, qt.IsNil) - c.Assert(b1, qt.Equals, true) - - b2, err := tagExists("adfagdsfg") - c.Assert(err, qt.IsNil) - c.Assert(b2, qt.Equals, false) -} - -func skipIfCI(t *testing.T) { - if isCI() { - // Travis has an ancient git with no --invert-grep: https://github.com/travis-ci/travis-ci/issues/6328 - // Also Travis clones very shallowly, making some of the tests above shaky. - t.Skip("Skip git test on Linux to make Travis happy.") - } -} diff --git a/releaser/github.go b/releaser/github.go deleted file mode 100644 index ffb880423..000000000 --- a/releaser/github.go +++ /dev/null @@ -1,143 +0,0 @@ -package releaser - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "os" - "strings" -) - -var ( - gitHubCommitsAPI = "https://api.github.com/repos/gohugoio/REPO/commits/%s" - gitHubRepoAPI = "https://api.github.com/repos/gohugoio/REPO" - gitHubContributorsAPI = "https://api.github.com/repos/gohugoio/REPO/contributors" -) - -type gitHubAPI struct { - commitsAPITemplate string - repoAPI string - contributorsAPITemplate string -} - -func newGitHubAPI(repo string) *gitHubAPI { - return &gitHubAPI{ - commitsAPITemplate: strings.Replace(gitHubCommitsAPI, "REPO", repo, -1), - repoAPI: strings.Replace(gitHubRepoAPI, "REPO", repo, -1), - contributorsAPITemplate: strings.Replace(gitHubContributorsAPI, "REPO", repo, -1), - } -} - -type gitHubCommit struct { - Author gitHubAuthor `json:"author"` - HTMLURL string `json:"html_url"` -} - -type gitHubAuthor struct { - ID int `json:"id"` - Login string `json:"login"` - HTMLURL string `json:"html_url"` - AvatarURL string `json:"avatar_url"` -} - -type gitHubRepo struct { - ID int `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - HTMLURL string `json:"html_url"` - Stars int `json:"stargazers_count"` - Contributors []gitHubContributor -} - -type gitHubContributor struct { - ID int `json:"id"` - Login string `json:"login"` - HTMLURL string `json:"html_url"` - Contributions int `json:"contributions"` -} - -func (g *gitHubAPI) fetchCommit(ref string) (gitHubCommit, error) { - var commit gitHubCommit - - u := fmt.Sprintf(g.commitsAPITemplate, ref) - - req, err := http.NewRequest("GET", u, nil) - if err != nil { - return commit, err - } - - err = doGitHubRequest(req, &commit) - - return commit, err -} - -func (g *gitHubAPI) fetchRepo() (gitHubRepo, error) { - var repo gitHubRepo - - req, err := http.NewRequest("GET", g.repoAPI, nil) - if err != nil { - return repo, err - } - - err = doGitHubRequest(req, &repo) - if err != nil { - return repo, err - } - - var contributors []gitHubContributor - page := 0 - for { - page++ - var currPage []gitHubContributor - url := fmt.Sprintf(g.contributorsAPITemplate+"?page=%d", page) - - req, err = http.NewRequest("GET", url, nil) - if err != nil { - return repo, err - } - - err = doGitHubRequest(req, &currPage) - if err != nil { - return repo, err - } - if len(currPage) == 0 { - break - } - - contributors = append(contributors, currPage...) - - } - - repo.Contributors = contributors - - return repo, err -} - -func doGitHubRequest(req *http.Request, v any) error { - addGitHubToken(req) - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if isError(resp) { - b, _ := ioutil.ReadAll(resp.Body) - return fmt.Errorf("GitHub lookup failed: %s", string(b)) - } - - return json.NewDecoder(resp.Body).Decode(v) -} - -func isError(resp *http.Response) bool { - return resp.StatusCode < 200 || resp.StatusCode > 299 -} - -func addGitHubToken(req *http.Request) { - gitHubToken := os.Getenv("GITHUB_TOKEN") - if gitHubToken != "" { - req.Header.Add("Authorization", "token "+gitHubToken) - } -} diff --git a/releaser/github_test.go b/releaser/github_test.go deleted file mode 100644 index 23331bf38..000000000 --- a/releaser/github_test.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package releaser - -import ( - "fmt" - "os" - "testing" - - qt "github.com/frankban/quicktest" -) - -func TestGitHubLookupCommit(t *testing.T) { - skipIfNoToken(t) - c := qt.New(t) - client := newGitHubAPI("hugo") - commit, err := client.fetchCommit("793554108763c0984f1a1b1a6ee5744b560d78d0") - c.Assert(err, qt.IsNil) - fmt.Println(commit) -} - -func TestFetchRepo(t *testing.T) { - skipIfNoToken(t) - c := qt.New(t) - client := newGitHubAPI("hugo") - repo, err := client.fetchRepo() - c.Assert(err, qt.IsNil) - fmt.Println(">>", len(repo.Contributors)) -} - -func skipIfNoToken(t *testing.T) { - if os.Getenv("GITHUB_TOKEN") == "" { - t.Skip("Skip test against GitHub as no GITHUB_TOKEN set.") - } -} diff --git a/releaser/releasenotes_writer.go b/releaser/releasenotes_writer.go deleted file mode 100644 index 5c50e4de4..000000000 --- a/releaser/releasenotes_writer.go +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package releaser implements a set of utilities and a wrapper around Goreleaser -// to help automate the Hugo release process. -package releaser - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "net/http" - "os" - "path/filepath" - "strings" - "text/template" -) - -const ( - issueLinkTemplate = "#%d" - linkTemplate = "[%s](%s)" - releaseNotesMarkdownTemplatePatchRelease = ` -{{ if eq (len .All) 1 }} -This is a bug-fix release with one important fix. -{{ else }} -This is a bug-fix release with a couple of important fixes. -{{ end }} -{{ range .All }} -{{- if .GitHubCommit -}} -* {{ .Subject }} {{ .Hash }} {{ . | author }} {{ range .Issues }}{{ . | issue }} {{ end }} -{{ else -}} -* {{ .Subject }} {{ range .Issues }}{{ . | issue }} {{ end }} -{{ end -}} -{{- end }} - - -` - releaseNotesMarkdownTemplate = ` -{{- $contribsPerAuthor := .All.ContribCountPerAuthor -}} -{{- $docsContribsPerAuthor := .Docs.ContribCountPerAuthor -}} - -This release represents **{{ len .All }} contributions by {{ len $contribsPerAuthor }} contributors** to the main Hugo code base. - -{{- if gt (len $contribsPerAuthor) 3 -}} -{{- $u1 := index $contribsPerAuthor 0 -}} -{{- $u2 := index $contribsPerAuthor 1 -}} -{{- $u3 := index $contribsPerAuthor 2 -}} -{{- $u4 := index $contribsPerAuthor 3 -}} -{{- $u1.AuthorLink }} leads the Hugo development with a significant amount of contributions, but also a big shoutout to {{ $u2.AuthorLink }}, {{ $u3.AuthorLink }}, and {{ $u4.AuthorLink }} for their ongoing contributions. -And thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his ongoing work on keeping the themes site in pristine condition. -{{ end }} -Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs), -which has received **{{ len .Docs }} contributions by {{ len $docsContribsPerAuthor }} contributors**. -{{- if gt (len $docsContribsPerAuthor) 3 -}} -{{- $u1 := index $docsContribsPerAuthor 0 -}} -{{- $u2 := index $docsContribsPerAuthor 1 -}} -{{- $u3 := index $docsContribsPerAuthor 2 -}} -{{- $u4 := index $docsContribsPerAuthor 3 }} A special thanks to {{ $u1.AuthorLink }}, {{ $u2.AuthorLink }}, {{ $u3.AuthorLink }}, and {{ $u4.AuthorLink }} for their work on the documentation site. -{{ end }} - -Hugo now has: - -{{ with .Repo -}} -* {{ .Stars }}+ [stars](https://github.com/gohugoio/hugo/stargazers) -* {{ len .Contributors }}+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors) -{{- end -}} -{{ with .ThemeCount }} -* {{ . }}+ [themes](http://themes.gohugo.io/) -{{ end }} -{{ with .Notes }} -## Notes -{{ template "change-section" . }} -{{- end -}} -{{ with .All }} -## Changes -{{ template "change-section" . }} -{{ end }} - -{{ define "change-section" }} -{{ range . }} -{{- if .GitHubCommit -}} -* {{ .Subject }} {{ .Hash }} {{ . | author }} {{ range .Issues }}{{ . | issue }} {{ end }} -{{ else -}} -* {{ .Subject }} {{ range .Issues }}{{ . | issue }} {{ end }} -{{ end -}} -{{- end }} -{{ end }} -` -) - -var templateFuncs = template.FuncMap{ - "isPatch": func(c changeLog) bool { - return !strings.HasSuffix(c.Version, "0") - }, - "issue": func(id int) string { - return fmt.Sprintf(issueLinkTemplate, id) - }, - "commitURL": func(info gitInfo) string { - if info.GitHubCommit.HTMLURL == "" { - return "" - } - return fmt.Sprintf(linkTemplate, info.Hash, info.GitHubCommit.HTMLURL) - }, - "author": func(info gitInfo) string { - return "@" + info.GitHubCommit.Author.Login - }, -} - -func writeReleaseNotes(version string, infosMain, infosDocs gitInfos, to io.Writer) error { - client := newGitHubAPI("hugo") - changes := newChangeLog(infosMain, infosDocs) - changes.Version = version - repo, err := client.fetchRepo() - if err == nil { - changes.Repo = &repo - } - themeCount, err := fetchThemeCount() - if err == nil { - changes.ThemeCount = themeCount - } - - mtempl := releaseNotesMarkdownTemplate - - if !strings.HasSuffix(version, "0") { - mtempl = releaseNotesMarkdownTemplatePatchRelease - } - - tmpl, err := template.New("").Funcs(templateFuncs).Parse(mtempl) - if err != nil { - return err - } - - err = tmpl.Execute(to, changes) - if err != nil { - return err - } - - return nil -} - -func fetchThemeCount() (int, error) { - resp, err := http.Get("https://raw.githubusercontent.com/gohugoio/hugoThemesSiteBuilder/main/themes.txt") - if err != nil { - return 0, err - } - defer resp.Body.Close() - - b, _ := ioutil.ReadAll(resp.Body) - return bytes.Count(b, []byte("\n")) - bytes.Count(b, []byte("#")), nil -} - -func getReleaseNotesFilename(version string) string { - return filepath.FromSlash(fmt.Sprintf("temp/%s-relnotes-ready.md", version)) -} - -func (r *ReleaseHandler) writeReleaseNotesToTemp(version string, isPatch bool, infosMain, infosDocs gitInfos) (string, error) { - filename := getReleaseNotesFilename(version) - - var w io.WriteCloser - - if !r.try { - f, err := os.Create(filename) - if err != nil { - return "", err - } - - defer f.Close() - - w = f - - } else { - w = os.Stdout - } - - if err := writeReleaseNotes(version, infosMain, infosDocs, w); err != nil { - return "", err - } - - return filename, nil -} diff --git a/releaser/releasenotes_writer_test.go b/releaser/releasenotes_writer_test.go deleted file mode 100644 index 7dcd0ccaa..000000000 --- a/releaser/releasenotes_writer_test.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package commands defines and implements command-line commands and flags -// used by Hugo. Commands and flags are implemented using Cobra. - -package releaser - -import ( - "bytes" - "fmt" - "os" - "testing" - - qt "github.com/frankban/quicktest" -) - -func _TestReleaseNotesWriter(t *testing.T) { - skipIfNoToken(t) - if os.Getenv("CI") != "" { - // Travis has an ancient git with no --invert-grep: https://github.com/travis-ci/travis-ci/issues/6328 - t.Skip("Skip git test on CI to make Travis happy..") - } - - c := qt.New(t) - - var b bytes.Buffer - - // TODO(bep) consider to query GitHub directly for the gitlog with author info, probably faster. - infos, err := getGitInfosBefore("HEAD", "v0.89.0", "hugo", "", false) - c.Assert(err, qt.IsNil) - - c.Assert(writeReleaseNotes("0.89.0", infos, infos, &b), qt.IsNil) - - fmt.Println(b.String()) -} diff --git a/releaser/releaser.go b/releaser/releaser.go index ebc344e98..fc16a2572 100644 --- a/releaser/releaser.go +++ b/releaser/releaser.go @@ -17,7 +17,6 @@ package releaser import ( "fmt" - "io/ioutil" "log" "os" "path/filepath" @@ -25,22 +24,56 @@ import ( "strings" "github.com/gohugoio/hugo/common/hexec" - - "errors" - "github.com/gohugoio/hugo/common/hugo" ) const commitPrefix = "releaser:" +// New initialises a ReleaseHandler. +func New(skipPush, try bool, step int) (*ReleaseHandler, error) { + if step < 1 || step > 2 { + return nil, fmt.Errorf("step must be 1 or 2") + } + + prefix := "release-" + branch, err := git("rev-parse", "--abbrev-ref", "HEAD") + if err != nil { + return nil, err + } + if !strings.HasPrefix(branch, prefix) { + return nil, fmt.Errorf("branch %q is not a release branch", branch) + } + + logf("Branch: %s\n", branch) + + version := strings.TrimPrefix(branch, prefix) + version = strings.TrimPrefix(version, "v") + rh := &ReleaseHandler{branchVersion: version, skipPush: skipPush, try: try, step: step} + + if try { + rh.git = func(args ...string) (string, error) { + logln("git", strings.Join(args, " ")) + return "", nil + } + } else { + rh.git = git + } + + return rh, nil +} + // ReleaseHandler provides functionality to release a new version of Hugo. // Test this locally without doing an actual release: // go run -tags release main.go release --skip-publish --try -r 0.90.0 // Or a variation of the above -- the skip-publish flag makes sure that any changes are performed to the local Git only. type ReleaseHandler struct { - cliVersion string + branchVersion string - skipPublish bool + // 1 or 2. + step int + + // No remote pushes. + skipPush bool // Just simulate, no actual changes. try bool @@ -48,144 +81,48 @@ type ReleaseHandler struct { git func(args ...string) (string, error) } -func (r ReleaseHandler) calculateVersions() (hugo.Version, hugo.Version) { - newVersion := hugo.MustParseVersion(r.cliVersion) - finalVersion := newVersion.Next() - finalVersion.PatchLevel = 0 - - if newVersion.Suffix != "-test" { - newVersion.Suffix = "" - } - - finalVersion.Suffix = "-DEV" - - return newVersion, finalVersion -} - -// New initialises a ReleaseHandler. -func New(version string, skipPublish, try bool) *ReleaseHandler { - // When triggered from CI release branch - version = strings.TrimPrefix(version, "release-") - version = strings.TrimPrefix(version, "v") - rh := &ReleaseHandler{cliVersion: version, skipPublish: skipPublish, try: try} - - if try { - rh.git = func(args ...string) (string, error) { - fmt.Println("git", strings.Join(args, " ")) - return "", nil - } - } else { - rh.git = git - } - - return rh -} - // Run creates a new release. func (r *ReleaseHandler) Run() error { - if os.Getenv("GITHUB_TOKEN") == "" { - return errors.New("GITHUB_TOKEN not set, create one here with the repo scope selected: https://github.com/settings/tokens/new") - } - - fmt.Printf("Start release from %q\n", wd()) - newVersion, finalVersion := r.calculateVersions() - version := newVersion.String() tag := "v" + version - isPatch := newVersion.PatchLevel > 0 mainVersion := newVersion mainVersion.PatchLevel = 0 - // Exit early if tag already exists - exists, err := tagExists(tag) - if err != nil { - return err - } + defer r.gitPush() - if exists { - return fmt.Errorf("tag %q already exists", tag) - } - - var changeLogFromTag string - - if newVersion.PatchLevel == 0 { - // There may have been patch releases between, so set the tag explicitly. - changeLogFromTag = "v" + newVersion.Prev().String() - exists, _ := tagExists(changeLogFromTag) - if !exists { - // fall back to one that exists. - changeLogFromTag = "" - } - } - - var ( - gitCommits gitInfos - gitCommitsDocs gitInfos - ) - - defer r.gitPush() // TODO(bep) - - gitCommits, err = getGitInfos(changeLogFromTag, "hugo", "", !r.try) - if err != nil { - return err - } - - // TODO(bep) explicit tag? - gitCommitsDocs, err = getGitInfos("", "hugoDocs", "../hugoDocs", !r.try) - if err != nil { - return err - } - - releaseNotesFile, err := r.writeReleaseNotesToTemp(version, isPatch, gitCommits, gitCommitsDocs) - if err != nil { - return err - } - - if _, err := r.git("add", releaseNotesFile); err != nil { - return err - } - - commitMsg := fmt.Sprintf("%s Add release notes for %s", commitPrefix, newVersion) - commitMsg += "\n[ci skip]" - - if _, err := r.git("commit", "-m", commitMsg); err != nil { - return err - } - - if err := r.bumpVersions(newVersion); err != nil { - return err - } - - if _, err := r.git("commit", "-a", "-m", fmt.Sprintf("%s Bump versions for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil { - return err - } - - if _, err := r.git("tag", "-a", tag, "-m", fmt.Sprintf("%s %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil { - return err - } - - if !r.skipPublish { - if _, err := r.git("push", "origin", tag); err != nil { + if r.step == 1 { + if err := r.bumpVersions(newVersion); err != nil { return err } - } - if err := r.release(releaseNotesFile); err != nil { - return err + if _, err := r.git("commit", "-a", "-m", fmt.Sprintf("%s Bump versions for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil { + return err + } + + // The above commit will be the target for this release, so print it to the console in a env friendly way. + sha, err := git("rev-parse", "HEAD") + if err != nil { + return err + } + + // Hugoreleaser will do the actual release using these values. + if err := r.replaceInFile("hugoreleaser.env", + `HUGORELEASER_TAG=(\S*)`, "HUGORELEASER_TAG="+tag, + `HUGORELEASER_COMMITISH=(\S*)`, "HUGORELEASER_COMMITISH="+sha, + ); err != nil { + return err + } + logf("HUGORELEASER_TAG=%s\n", tag) + logf("HUGORELEASER_COMMITISH=%s\n", sha) + + return nil } if err := r.bumpVersions(finalVersion); err != nil { return err } - if !r.try { - // No longer needed. - if err := os.Remove(releaseNotesFile); err != nil { - return err - } - } - if _, err := r.git("commit", "-a", "-m", fmt.Sprintf("%s Prepare repository for %s\n\n[ci skip]", commitPrefix, finalVersion)); err != nil { return err } @@ -193,36 +130,6 @@ func (r *ReleaseHandler) Run() error { return nil } -func (r *ReleaseHandler) gitPush() { - if r.skipPublish { - return - } - if _, err := r.git("push", "origin", "HEAD"); err != nil { - log.Fatal("push failed:", err) - } -} - -func (r *ReleaseHandler) release(releaseNotesFile string) error { - if r.try { - fmt.Println("Skip goreleaser...") - return nil - } - - args := []string{"--parallelism", "2", "--timeout", "120m", "--rm-dist", "--release-notes", releaseNotesFile} - if r.skipPublish { - args = append(args, "--skip-publish") - } - - cmd, _ := hexec.SafeCommand("goreleaser", args...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Run() - if err != nil { - return fmt.Errorf("goreleaser failed: %w", err) - } - return nil -} - func (r *ReleaseHandler) bumpVersions(ver hugo.Version) error { toDev := "" @@ -264,6 +171,29 @@ func (r *ReleaseHandler) bumpVersions(ver hugo.Version) error { return nil } +func (r ReleaseHandler) calculateVersions() (hugo.Version, hugo.Version) { + newVersion := hugo.MustParseVersion(r.branchVersion) + finalVersion := newVersion.Next() + finalVersion.PatchLevel = 0 + + if newVersion.Suffix != "-test" { + newVersion.Suffix = "" + } + + finalVersion.Suffix = "-DEV" + + return newVersion, finalVersion +} + +func (r *ReleaseHandler) gitPush() { + if r.skipPush { + return + } + if _, err := r.git("push", "origin", "HEAD"); err != nil { + log.Fatal("push failed:", err) + } +} + func (r *ReleaseHandler) replaceInFile(filename string, oldNew ...string) error { filename = filepath.FromSlash(filename) fi, err := os.Stat(filename) @@ -272,11 +202,11 @@ func (r *ReleaseHandler) replaceInFile(filename string, oldNew ...string) error } if r.try { - fmt.Printf("Replace in %q: %q\n", filename, oldNew) + logf("Replace in %q: %q\n", filename, oldNew) return nil } - b, err := ioutil.ReadFile(filename) + b, err := os.ReadFile(filename) if err != nil { return err } @@ -287,18 +217,22 @@ func (r *ReleaseHandler) replaceInFile(filename string, oldNew ...string) error newContent = re.ReplaceAllString(newContent, oldNew[i+1]) } - return ioutil.WriteFile(filename, []byte(newContent), fi.Mode()) + return os.WriteFile(filename, []byte(newContent), fi.Mode()) } -func isCI() bool { - return os.Getenv("CI") != "" -} - -func wd() string { - p, err := os.Getwd() +func git(args ...string) (string, error) { + cmd, _ := hexec.SafeCommand("git", args...) + out, err := cmd.CombinedOutput() if err != nil { - log.Fatal(err) + return "", fmt.Errorf("git failed: %q: %q (%q)", err, out, args) } - return p - + return string(out), nil +} + +func logf(format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, format, args...) +} + +func logln(args ...interface{}) { + fmt.Fprintln(os.Stderr, args...) }