Просмотр исходного кода

script: Better change log, based only on issue titles, understanding labels

Jakob Borg 8 лет назад
Родитель
Сommit
cd29e3c524
1 измененных файлов с 133 добавлено и 39 удалено
  1. 133 39
      script/changelog.go

+ 133 - 39
script/changelog.go

@@ -11,6 +11,7 @@ package main
 import (
 	"bytes"
 	"encoding/json"
+	"errors"
 	"flag"
 	"fmt"
 	"io/ioutil"
@@ -19,10 +20,9 @@ import (
 	"os"
 	"os/exec"
 	"regexp"
+	"sort"
 	"strconv"
 	"strings"
-
-	"github.com/mitchellh/go-wordwrap"
 )
 
 var (
@@ -30,11 +30,15 @@ var (
 	issueNumbers  = regexp.MustCompile(`(#\d+)`)
 )
 
+type issue struct {
+	number  int
+	subject string
+	labels  []string
+}
+
 func main() {
 	flag.Parse()
 
-	fmt.Printf("Resolved issues:\n\n")
-
 	// Display changelog since the version given on the command line, or
 	// figure out the last release if there were no arguments.
 	var prevRel string
@@ -49,52 +53,118 @@ func main() {
 	}
 
 	// Get the git log with subject and author nickname
-	bs, err := runError("git", "log", "--reverse", "--pretty=format:%s|%aN|%cN", prevRel+"..")
+	bs, err := runError("git", "log", "--reverse", "--pretty=format:%s", prevRel+"..")
 	if err != nil {
 		log.Fatal(err)
 	}
 
+	var resolved []issue
+
 	// Split into lines
 	for _, line := range bytes.Split(bs, []byte{'\n'}) {
-		// Split into subject and author
-		fields := bytes.Split(line, []byte{'|'})
-		subj := fields[0]
-		author := fields[1]
-		committer := fields[2]
-
 		// Check if subject contains a "(fixes ...)" or "(ref ...)""
-		if m := subjectIssues.FindSubmatch(subj); len(m) > 0 {
-			subj := m[1]
+		if m := subjectIssues.FindSubmatch(line); len(m) > 0 {
 			issues := issueNumbers.FindAll(m[2], -1)
-			for _, issue := range issues {
-				n, err := strconv.Atoi(string(issue[1:]))
+			for _, i := range issues {
+				n, err := strconv.Atoi(string(i[1:]))
 				if err != nil {
 					continue
 				}
-				title, err := githubIssueTitle(n)
+				title, labels, err := githubIssueTitleLabels(n)
 				if err != nil {
-					fmt.Println(err)
 					continue
 				}
 
-				// Format a changelog entry
-				reviewed := ""
-				if !bytes.Equal(committer, author) {
-					reviewed = fmt.Sprintf(", reviewed by @%s", committer)
-				}
-
-				message := fmt.Sprintf("%s: %s\n\n%s (by @%s%s)\n", issue, title, subj, author, reviewed)
-				para := wordwrap.WrapString(message, 74)
-				for i, line := range strings.Split(para, "\n") {
-					if i == 0 {
-						fmt.Println("*", line)
-					} else {
-						fmt.Println(" ", line)
-					}
-				}
+				resolved = append(resolved, issue{n, title, labels})
 			}
 		}
 	}
+
+	sort.Slice(resolved, func(a, b int) bool {
+		return resolved[a].number < resolved[b].number
+	})
+
+	var bugs, enhancements, other []issue
+	var prev int
+	for _, i := range resolved {
+		if i.number == prev {
+			continue
+		}
+		prev = i.number
+		switch {
+		case contains("unreleased", i.labels):
+			continue
+		case contains("bug", i.labels):
+			bugs = append(bugs, i)
+		case contains("enhancement", i.labels):
+			enhancements = append(enhancements, i)
+		default:
+			other = append(other, i)
+		}
+	}
+
+	fmt.Printf("--- markdown ---\n\n")
+	markdown(prevRel, bugs, enhancements, other)
+	fmt.Printf("\n--- text ---\n\n")
+	text(prevRel, bugs, enhancements, other)
+}
+
+func markdown(version string, bugs, enhancements, other []issue) {
+	fmt.Printf("## Resolved issues since %s\n\n", version)
+	if len(bugs) > 0 {
+		fmt.Printf("### Bugs\n\n")
+		for _, issue := range bugs {
+			fmt.Printf("* [#%d](https://github.com/syncthing/syncthing/issues/%d): %s\n", issue.number, issue.number, issue.subject)
+		}
+		fmt.Println()
+	}
+	if len(enhancements) > 0 {
+		fmt.Printf("### Enhancements\n\n")
+		for _, issue := range enhancements {
+			fmt.Printf("* [#%d](https://github.com/syncthing/syncthing/issues/%d): %s\n", issue.number, issue.number, issue.subject)
+		}
+		fmt.Println()
+	}
+	if len(other) > 0 {
+		fmt.Printf("### Unclassified\n\n")
+		for _, issue := range other {
+			fmt.Printf("* [#%d](https://github.com/syncthing/syncthing/issues/%d): %s\n", issue.number, issue.number, issue.subject)
+		}
+		fmt.Println()
+	}
+}
+
+func text(version string, bugs, enhancements, other []issue) {
+	fmt.Println(underline(fmt.Sprintf("Resolved issues since %s", version), "="))
+	fmt.Println()
+	if len(bugs) > 0 {
+		fmt.Println(underline("Bugs", "-"))
+		fmt.Println()
+		for _, issue := range bugs {
+			fmt.Printf("* #%d: %s\n", issue.number, issue.subject)
+		}
+		fmt.Println()
+	}
+	if len(enhancements) > 0 {
+		fmt.Println(underline("Enhancements", "-"))
+		fmt.Println()
+		for _, issue := range enhancements {
+			fmt.Printf("* #%d: %s\n", issue.number, issue.subject)
+		}
+		fmt.Println()
+	}
+	if len(other) > 0 {
+		fmt.Println(underline("Unclassified", "-"))
+		fmt.Println()
+		for _, issue := range other {
+			fmt.Printf("* #%d: %s\n", issue.number, issue.subject)
+		}
+		fmt.Println()
+	}
+}
+
+func underline(s, c string) string {
+	return fmt.Sprintf("%s\n%s", s, strings.Repeat(c, len(s)))
 }
 
 func runError(cmd string, args ...string) ([]byte, error) {
@@ -106,10 +176,10 @@ func runError(cmd string, args ...string) ([]byte, error) {
 	return bytes.TrimSpace(bs), nil
 }
 
-func githubIssueTitle(n int) (string, error) {
+func githubIssueTitleLabels(n int) (string, []string, error) {
 	req, err := http.NewRequest("GET", fmt.Sprintf("https://api.github.com/repos/syncthing/syncthing/issues/%d", n), nil)
 	if err != nil {
-		return "", err
+		return "", nil, err
 	}
 
 	user, token := os.Getenv("GITHUB_USERNAME"), os.Getenv("GITHUB_TOKEN")
@@ -119,22 +189,46 @@ func githubIssueTitle(n int) (string, error) {
 
 	resp, err := http.DefaultClient.Do(req)
 	if err != nil {
-		return "", err
+		return "", nil, err
 	}
 	defer resp.Body.Close()
 
 	bs, err := ioutil.ReadAll(resp.Body)
 	if err != nil {
-		return "", err
+		return "", nil, err
 	}
 
 	var res struct {
-		Title string
+		Title  string
+		Labels []struct {
+			Name string
+		}
+		PR struct {
+			URL string
+		} `json:"pull_request"`
 	}
 	err = json.Unmarshal(bs, &res)
 	if err != nil {
-		return "", err
+		return "", nil, err
+	}
+
+	if res.PR.URL != "" {
+		return "", nil, errors.New("pull request")
+	}
+
+	var labels []string
+	for _, l := range res.Labels {
+		labels = append(labels, l.Name)
 	}
 
-	return res.Title, nil
+	return res.Title, labels, nil
+}
+
+func contains(s string, ss []string) bool {
+	for _, x := range ss {
+		if s == x {
+			return true
+		}
+	}
+	return false
 }