diff --git a/build.go b/build.go index 430fc24d4..3c0358019 100644 --- a/build.go +++ b/build.go @@ -49,6 +49,7 @@ var ( debugBinary bool coverage bool timeout = "120s" + numVersions = 5 ) type target struct { @@ -333,6 +334,20 @@ func runCommand(cmd string, target target) { case "version": fmt.Println(getVersion()) + case "changelog": + vers, err := currentAndLatestVersions(numVersions) + if err != nil { + log.Fatal(err) + } + for _, ver := range vers { + underline := strings.Repeat("=", len(ver)) + msg, err := tagMessage(ver) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%s\n%s\n\n%s\n\n", ver, underline, msg) + } + default: log.Fatalf("Unknown command %q", cmd) } @@ -351,6 +366,7 @@ func parseFlags() { flag.StringVar(&cc, "cc", os.Getenv("CC"), "Set CC environment variable for `go build`") flag.BoolVar(&debugBinary, "debug-binary", debugBinary, "Create unoptimized binary to use with delve, set -gcflags='-N -l' and omit -ldflags") flag.BoolVar(&coverage, "coverage", coverage, "Write coverage profile of tests to coverage.txt") + flag.IntVar(&numVersions, "num-versions", numVersions, "Number of versions for changelog command") flag.Parse() } @@ -1240,3 +1256,62 @@ func protobufVersion() string { } return string(bs) } + +func currentAndLatestVersions(n int) ([]string, error) { + bs, err := runError("git", "tag", "--sort", "taggerdate") + if err != nil { + return nil, err + } + + lines := strings.Split(string(bs), "\n") + reverseStrings(lines) + + // The one at the head is the latest version. We always keep that one. + // Then we filter out remaining ones with dashes (pre-releases etc). + + latest := lines[:1] + nonPres := filterStrings(lines[1:], func(s string) bool { return !strings.Contains(s, "-") }) + vers := append(latest, nonPres...) + return vers[:n], nil +} + +func reverseStrings(ss []string) { + for i := 0; i < len(ss)/2; i++ { + ss[i], ss[len(ss)-1-i] = ss[len(ss)-1-i], ss[i] + } +} + +func filterStrings(ss []string, op func(string) bool) []string { + n := ss[:0] + for _, s := range ss { + if op(s) { + n = append(n, s) + } + } + return n +} + +func tagMessage(tag string) (string, error) { + hash, err := runError("git", "rev-parse", tag) + if err != nil { + return "", err + } + obj, err := runError("git", "cat-file", "-p", string(hash)) + if err != nil { + return "", err + } + return trimTagMessage(string(obj), tag), nil +} + +func trimTagMessage(msg, tag string) string { + firstBlank := strings.Index(msg, "\n\n") + if firstBlank > 0 { + msg = msg[firstBlank+2:] + } + msg = strings.TrimPrefix(msg, tag) + beginSig := strings.Index(msg, "-----BEGIN PGP") + if beginSig > 0 { + msg = msg[:beginSig] + } + return strings.TrimSpace(msg) +}