Skip to content

Commit

Permalink
Add 'check changelog' command (#168)
Browse files Browse the repository at this point in the history
Signed-off-by: Simon Pasquier <[email protected]>
  • Loading branch information
simonpasquier authored Sep 10, 2019
1 parent d3dd945 commit 86bdcd5
Show file tree
Hide file tree
Showing 24 changed files with 2,270 additions and 356 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## v0.5.0 / 2019-06-21
## 0.5.0 / 2019-06-21

* [CHANGE] Remove --broken from git describe. #145
* [FEATURE] Add support for aix/ppc64. #151
Expand Down
3 changes: 2 additions & 1 deletion cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/pkg/errors"
kingpin "gopkg.in/alecthomas/kingpin.v2"

"github.com/prometheus/promu/pkg/repository"
"github.com/prometheus/promu/util/sh"
)

Expand Down Expand Up @@ -141,7 +142,7 @@ func runBuild(binariesString string) {
}
}

func getLdflags(info ProjectInfo) string {
func getLdflags(info repository.Info) string {
var ldflags []string

if len(strings.TrimSpace(config.Build.LDFlags)) > 0 {
Expand Down
38 changes: 38 additions & 0 deletions cmd/check_licenses.go → cmd/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"strings"

"github.com/pkg/errors"

"github.com/prometheus/promu/pkg/changelog"
)

var (
Expand All @@ -36,6 +38,12 @@ var (
Short('n').Default("10").Int()
checkLicLocation = checkLicensescmd.Arg("location", "Directory path to check licenses").
Default(".").Strings()

checkChangelogcmd = checkcmd.Command("changelog", "Check that CHANGELOG.md follows the guidelines")
checkChangelogPath = checkChangelogcmd.Flag("location", "Path to CHANGELOG.md").
Default("CHANGELOG.md").String()
checkChangelogVersion = checkChangelogcmd.Flag("version", "Version to check (defaults to the current version)").
Default("").String()
)

func runCheckLicenses(path string, n int, extensions []string) {
Expand Down Expand Up @@ -129,3 +137,33 @@ func suffixInSlice(needle string, haystack []string) bool {

return exists
}

func runCheckChangelog(path string, version string) error {
if version == "" {
_, err := projInfo.ToSemver()
if err != nil {
return errors.Wrap(err, "invalid semver version")
}

version = projInfo.Version
}

f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()

entry, err := changelog.ReadEntry(f, version)
if err != nil {
return errors.Wrapf(err, "%s:", path)
}

// Check that the changes are ordered correctly.
err = entry.Changes.Sorted()
if err != nil {
return errors.Wrap(err, "invalid changelog entry")
}

return nil
}
111 changes: 0 additions & 111 deletions cmd/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,85 +16,10 @@ package cmd

import (
"fmt"
"net/url"
"os"
"os/exec"
"os/user"
"path/filepath"
"regexp"
"strings"

"github.com/pkg/errors"
)

var infocmd = app.Command("info", "Print info about current project and exit")

// ProjectInfo represents current project useful informations.
type ProjectInfo struct {
Branch string
Name string
Owner string
Repo string
Revision string
Version string
}

// NewProjectInfo returns a new ProjectInfo.
func NewProjectInfo() (ProjectInfo, error) {
projectInfo := ProjectInfo{}

cmd := exec.Command("git", "rev-parse", "--show-toplevel")
cmd.Stdout = nil
cmd.Stderr = nil
if err := cmd.Run(); err != nil {
repo, err := os.Getwd()
if err != nil {
return projectInfo, errors.Wrapf(err, "Couldn't get current working directory")
}
repo = strings.TrimPrefix(repo, os.Getenv("GOPATH"))
repo = strings.TrimPrefix(repo, "/src/")

user, err := user.Current()
if err != nil {
return projectInfo, errors.Wrapf(err, "Couldn't get current user")
}

projectInfo = ProjectInfo{
Branch: "non-git",
Name: filepath.Base(repo),
Owner: user.Username,
Repo: repo,
Revision: "non-git",
}
} else {
cmd := exec.Command("git", "config", "--get", "remote.origin.url")
repoURL, err := cmd.Output()
if err != nil {
warn(errors.Wrap(err, "Unable to get repo location info from 'origin' remote"))
}
repo, err := repoLocation(strings.Trim(string(repoURL), " \n\r"))
if err != nil {
return projectInfo, errors.Wrapf(err, "Couldn't parse repo location")
}
projectInfo = ProjectInfo{
Branch: shellOutput("git rev-parse --abbrev-ref HEAD"),
Name: filepath.Base(repo),
Owner: filepath.Base(filepath.Dir(repo)),
Repo: repo,
Revision: shellOutput("git rev-parse HEAD"),
}
}

version, err := findVersion()
if err != nil {
warn(errors.Wrap(err, "Unable to find project's version"))
} else {
projectInfo.Version = version
}

return projectInfo, nil
}

func runInfo() {
fmt.Println("Name:", projInfo.Name)
fmt.Println("Version:", projInfo.Version)
Expand All @@ -103,39 +28,3 @@ func runInfo() {
fmt.Println("Branch:", projInfo.Branch)
fmt.Println("Revision:", projInfo.Revision)
}

// Convert SCP-like URL to SSH URL(e.g. [user@]host.xz:path/to/repo.git/)
// ref. http://git-scm.com/docs/git-fetch#_git_urls
// (golang hasn't supported Perl-like negative look-behind match)
var hasSchemePattern = regexp.MustCompile("^[^:]+://")
var scpLikeURLPattern = regexp.MustCompile("^([^@]+@)?([^:]+):/?(.+)$")

func repoLocation(repo string) (string, error) {
if !hasSchemePattern.MatchString(repo) && scpLikeURLPattern.MatchString(repo) {
matched := scpLikeURLPattern.FindStringSubmatch(repo)
user := matched[1]
host := matched[2]
path := matched[3]
repo = fmt.Sprintf("ssh://%s%s/%s", user, host, path)
}

u, err := url.Parse(repo)
if err != nil {
return "", err
}

repo = fmt.Sprintf("%s%s", strings.Split(u.Host, ":")[0], u.Path)
repo = strings.TrimSuffix(repo, ".git")
return repo, nil
}

func findVersion() (string, error) {
var files = []string{"VERSION", "version/VERSION"}
for _, file := range files {
if fileExists(file) {
return readFile(file), nil
}
}

return shellOutput("git describe --tags --always --dirty"), nil
}
9 changes: 7 additions & 2 deletions cmd/promu.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"path/filepath"
"strings"

"github.com/prometheus/promu/pkg/repository"
"github.com/prometheus/promu/util/sh"
kingpin "gopkg.in/alecthomas/kingpin.v2"
yaml "gopkg.in/yaml.v2"
Expand Down Expand Up @@ -95,7 +96,7 @@ var (
Default(DefaultConfigFilename).String()
verbose = app.Flag("verbose", "Verbose output").Short('v').Bool()
config *Config
projInfo ProjectInfo
projInfo repository.Info

// app represents the base command
app = kingpin.New("promu", "promu is the utility tool for building and releasing Prometheus projects")
Expand All @@ -110,7 +111,7 @@ func init() {
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
var err error
projInfo, err = NewProjectInfo()
projInfo, err = repository.NewInfo(warn)
checkError(err, "Unable to initialize project info")

command := kingpin.MustParse(app.Parse(os.Args[1:]))
Expand All @@ -124,6 +125,10 @@ func Execute() {
runBuild(optArg(*binariesArg, 0, "all"))
case checkLicensescmd.FullCommand():
runCheckLicenses(optArg(*checkLicLocation, 0, "."), *headerLength, *sourceExtensions)
case checkChangelogcmd.FullCommand():
if err := runCheckChangelog(*checkChangelogPath, *checkChangelogVersion); err != nil {
fatal(err)
}
case checksumcmd.FullCommand():
runChecksum(optArg(*checksumLocation, 0, "."))
case crossbuildcmd.FullCommand():
Expand Down
79 changes: 13 additions & 66 deletions cmd/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,18 @@
package cmd

import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"time"

"github.com/google/go-github/v25/github"
"github.com/pkg/errors"
"golang.org/x/oauth2"

"github.com/prometheus/promu/pkg/changelog"
"github.com/prometheus/promu/util/retry"
)

Expand All @@ -40,17 +36,8 @@ var (
allowedRetries = releasecmd.Flag("retry", "Number of retries to perform when upload fails").
Default("2").Int()
releaseLocation = releasecmd.Arg("location", "Location of files to release").Default(".").Strings()
versionRe = regexp.MustCompile(`^\d+\.\d+\.\d+(-.+)?$`)
)

func isPrerelease(version string) (bool, error) {
matches := versionRe.FindStringSubmatch(version)
if matches == nil {
return false, errors.Errorf("invalid version %s", version)
}
return matches[1] != "", nil
}

func runRelease(location string) {
token := os.Getenv("GITHUB_TOKEN")
if len(token) == 0 {
Expand All @@ -72,7 +59,7 @@ func runRelease(location string) {
),
)

prerelease, err := isPrerelease(projInfo.Version)
semVer, err := projInfo.ToSemver()
if err != nil {
fatal(err)
}
Expand Down Expand Up @@ -101,21 +88,29 @@ func runRelease(location string) {
opts.Page = resp.NextPage
}
if release == nil {
releaseName, releaseBody, err := getChangelog(projInfo.Version, readChangelog())
f, err := os.Open("CHANGELOG.md")
if err != nil {
fatal(err)
}
defer f.Close()

entry, err := changelog.ReadEntry(f, projInfo.Version)
if err != nil {
fatal(err)
}
name := entry.Name()
// Create a draft release if none exists already.
draft := true
prerelease := semVer.Prerelease() != ""
release, _, err = client.Repositories.CreateRelease(
ctx,
projInfo.Owner,
projInfo.Name,
&github.RepositoryRelease{
TagName: &tag,
TargetCommitish: &projInfo.Revision,
Name: &releaseName,
Body: &releaseBody,
Name: &name,
Body: &entry.Text,
Draft: &draft,
Prerelease: &prerelease,
})
Expand Down Expand Up @@ -217,51 +212,3 @@ func releaseFile(ctx context.Context, client *github.Client, release *github.Rep
return nil
}
}

func readChangelog() io.ReadCloser {
f, err := os.Open("CHANGELOG.md")
if err != nil {
fmt.Printf("fail to read CHANGELOG.md: %v\n", err)
return ioutil.NopCloser(&bytes.Buffer{})
}
return f
}

// getChangelog returns the changelog's header/name and body for a given release version.
// Returns an error if the version is not found.
func getChangelog(version string, rc io.ReadCloser) (releaseHeader string, releaseBody string, err error) {
defer rc.Close()

var (
scanner = bufio.NewScanner(rc)
s []string
reading bool

releaseHeaderPattern = "## " + strings.ReplaceAll(version, ".", "\\.") + " / \\d{4}-\\d{2}-\\d{2}"
)
for (len(s) == 0 || reading) && scanner.Scan() {
text := scanner.Text()
switch {
case strings.HasPrefix(text, "## "+version):
if valid, _ := regexp.MatchString(releaseHeaderPattern, text); !valid {
return "", "", errors.New("Found invalid release header in changelog for version '" + version + "'. " +
"Expected format = '" + releaseHeaderPattern + "'. Found '" + text + "'")
}
reading = true
releaseHeader = strings.TrimSpace(strings.TrimPrefix(text, "##"))
case strings.HasPrefix(text, "## "):
reading = false
case reading:
if len(s) == 0 && strings.TrimSpace(text) == "" {
continue
}
s = append(s, scanner.Text())
}
}

if releaseHeader == "" {
return "", "", errors.New("unable to locate release information in changelog for selected version '" + projInfo.Version + "'." +
"Expected format = '" + releaseHeaderPattern + "'.")
}
return releaseHeader, strings.Join(s, "\n"), nil
}
Loading

0 comments on commit 86bdcd5

Please sign in to comment.