mirror of
https://github.com/thegeeklab/git-sv.git
synced 2024-06-02 17:39:39 +02:00
feat: generate release-notes and commit-log from tag when flag -t is used
This commit is contained in:
parent
14270fd86f
commit
afa57122ba
|
@ -4,7 +4,9 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"sv4git/sv"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
@ -30,7 +32,7 @@ func nextVersionHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor) f
|
|||
return fmt.Errorf("error parsing version: %s from describe, message: %v", describe, err)
|
||||
}
|
||||
|
||||
commits, err := git.Log(describe)
|
||||
commits, err := git.Log(describe, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting git log, message: %v", err)
|
||||
}
|
||||
|
@ -43,9 +45,14 @@ func nextVersionHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor) f
|
|||
|
||||
func commitLogHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor) func(c *cli.Context) error {
|
||||
return func(c *cli.Context) error {
|
||||
describe := git.Describe()
|
||||
var commits []sv.GitCommitLog
|
||||
var err error
|
||||
|
||||
commits, err := git.Log(describe)
|
||||
if tag := c.String("t"); tag != "" {
|
||||
commits, err = getTagCommits(git, tag)
|
||||
} else {
|
||||
commits, err = git.Log(git.Describe(), "")
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting git log, message: %v", err)
|
||||
}
|
||||
|
@ -61,29 +68,99 @@ func commitLogHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor) fun
|
|||
}
|
||||
}
|
||||
|
||||
func getTagCommits(git sv.Git, tag string) ([]sv.GitCommitLog, error) {
|
||||
prev, _, err := getTags(git, tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return git.Log(prev, tag)
|
||||
}
|
||||
|
||||
func releaseNotesHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor, rnProcessor sv.ReleaseNoteProcessor) func(c *cli.Context) error {
|
||||
return func(c *cli.Context) error {
|
||||
var commits []sv.GitCommitLog
|
||||
var rnVersion semver.Version
|
||||
var date time.Time
|
||||
var err error
|
||||
|
||||
describe := git.Describe()
|
||||
|
||||
currentVer, err := sv.ToVersion(describe)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing version: %s from describe, message: %v", describe, err)
|
||||
if tag := c.String("t"); tag != "" {
|
||||
rnVersion, date, commits, err = getTagVersionInfo(git, semverProcessor, tag)
|
||||
} else {
|
||||
rnVersion, date, commits, err = getNextVersionInfo(git, semverProcessor)
|
||||
}
|
||||
|
||||
commits, err := git.Log(describe)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting git log, message: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
nextVer := semverProcessor.NextVersion(currentVer, commits)
|
||||
|
||||
releasenote := rnProcessor.Get(commits)
|
||||
fmt.Println(rnProcessor.Format(releasenote, nextVer))
|
||||
releasenote := rnProcessor.Get(date, commits)
|
||||
fmt.Println(rnProcessor.Format(releasenote, rnVersion))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getTagVersionInfo(git sv.Git, semverProcessor sv.SemVerCommitsProcessor, tag string) (semver.Version, time.Time, []sv.GitCommitLog, error) {
|
||||
tagVersion, err := sv.ToVersion(tag)
|
||||
if err != nil {
|
||||
return semver.Version{}, time.Time{}, nil, fmt.Errorf("error parsing version: %s from tag, message: %v", tag, err)
|
||||
}
|
||||
|
||||
previousTag, currentTag, err := getTags(git, tag)
|
||||
if err != nil {
|
||||
return semver.Version{}, time.Time{}, nil, fmt.Errorf("error listing tags, message: %v", err)
|
||||
}
|
||||
|
||||
commits, err := git.Log(previousTag, tag)
|
||||
if err != nil {
|
||||
return semver.Version{}, time.Time{}, nil, fmt.Errorf("error getting git log from tag: %s, message: %v", tag, err)
|
||||
}
|
||||
|
||||
return tagVersion, currentTag.Date, commits, nil
|
||||
}
|
||||
|
||||
func getTags(git sv.Git, tag string) (string, sv.GitTag, error) {
|
||||
tags, err := git.Tags()
|
||||
if err != nil {
|
||||
return "", sv.GitTag{}, err
|
||||
}
|
||||
|
||||
index := find(tag, tags)
|
||||
if index < 0 {
|
||||
return "", sv.GitTag{}, fmt.Errorf("tag: %s not found", tag)
|
||||
}
|
||||
|
||||
previousTag := ""
|
||||
if index > 0 {
|
||||
previousTag = tags[index-1].Name
|
||||
}
|
||||
return previousTag, tags[index], nil
|
||||
}
|
||||
|
||||
func find(tag string, tags []sv.GitTag) int {
|
||||
for i := 0; i < len(tags); i++ {
|
||||
if tag == tags[i].Name {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func getNextVersionInfo(git sv.Git, semverProcessor sv.SemVerCommitsProcessor) (semver.Version, time.Time, []sv.GitCommitLog, error) {
|
||||
describe := git.Describe()
|
||||
|
||||
currentVer, err := sv.ToVersion(describe)
|
||||
if err != nil {
|
||||
return semver.Version{}, time.Time{}, nil, fmt.Errorf("error parsing version: %s from describe, message: %v", describe, err)
|
||||
}
|
||||
|
||||
commits, err := git.Log(describe, "")
|
||||
if err != nil {
|
||||
return semver.Version{}, time.Time{}, nil, fmt.Errorf("error getting git log, message: %v", err)
|
||||
}
|
||||
|
||||
return semverProcessor.NextVersion(currentVer, commits), time.Now(), commits, nil
|
||||
}
|
||||
|
||||
func tagHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor, rnProcessor sv.ReleaseNoteProcessor) func(c *cli.Context) error {
|
||||
return func(c *cli.Context) error {
|
||||
describe := git.Describe()
|
||||
|
@ -93,7 +170,7 @@ func tagHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor, rnProcess
|
|||
return fmt.Errorf("error parsing version: %s from describe, message: %v", describe, err)
|
||||
}
|
||||
|
||||
commits, err := git.Log(describe)
|
||||
commits, err := git.Log(describe, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting git log, message: %v", err)
|
||||
}
|
||||
|
|
|
@ -40,12 +40,14 @@ func main() {
|
|||
Aliases: []string{"cl"},
|
||||
Usage: "list all commit logs since last version as jsons",
|
||||
Action: commitLogHandler(git, semverProcessor),
|
||||
Flags: []cli.Flag{cli.StringFlag{Name: "t", Usage: "get commit log from tag"}},
|
||||
},
|
||||
{
|
||||
Name: "release-notes",
|
||||
Aliases: []string{"rn"},
|
||||
Usage: "generate release notes",
|
||||
Action: releaseNotesHandler(git, semverProcessor, releasenotesProcessor),
|
||||
Flags: []cli.Flag{cli.StringFlag{Name: "t", Usage: "get release note from tag"}},
|
||||
},
|
||||
{
|
||||
Name: "tag",
|
||||
|
|
50
sv/git.go
50
sv/git.go
|
@ -7,6 +7,7 @@ import (
|
|||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
)
|
||||
|
@ -21,8 +22,9 @@ const (
|
|||
// Git commands
|
||||
type Git interface {
|
||||
Describe() string
|
||||
Log(lastTag string) ([]GitCommitLog, error)
|
||||
Log(initialTag, endTag string) ([]GitCommitLog, error)
|
||||
Tag(version semver.Version) error
|
||||
Tags() ([]GitTag, error)
|
||||
}
|
||||
|
||||
// GitCommitLog description of a single commit log
|
||||
|
@ -35,6 +37,12 @@ type GitCommitLog struct {
|
|||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// GitTag git tag info
|
||||
type GitTag struct {
|
||||
Name string
|
||||
Date time.Time
|
||||
}
|
||||
|
||||
// GitImpl git command implementation
|
||||
type GitImpl struct {
|
||||
messageMetadata map[string]string
|
||||
|
@ -57,11 +65,17 @@ func (GitImpl) Describe() string {
|
|||
}
|
||||
|
||||
// Log return git log
|
||||
func (g GitImpl) Log(lastTag string) ([]GitCommitLog, error) {
|
||||
func (g GitImpl) Log(initialTag, endTag string) ([]GitCommitLog, error) {
|
||||
format := "--pretty=format:\"%h" + logSeparator + "%s" + logSeparator + "%b" + endLine + "\""
|
||||
cmd := exec.Command("git", "log", format)
|
||||
if lastTag != "" {
|
||||
cmd = exec.Command("git", "log", lastTag+"..HEAD", format)
|
||||
var cmd *exec.Cmd
|
||||
if initialTag == "" && endTag == "" {
|
||||
cmd = exec.Command("git", "log", format)
|
||||
} else if endTag == "" {
|
||||
cmd = exec.Command("git", "log", initialTag+"..HEAD", format)
|
||||
} else if initialTag == "" {
|
||||
cmd = exec.Command("git", "log", endTag, format)
|
||||
} else {
|
||||
cmd = exec.Command("git", "log", initialTag+".."+endTag, format)
|
||||
}
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
|
@ -85,6 +99,32 @@ func (g GitImpl) Tag(version semver.Version) error {
|
|||
return pushCommand.Run()
|
||||
}
|
||||
|
||||
// Tags list repository tags
|
||||
func (g GitImpl) Tags() ([]GitTag, error) {
|
||||
cmd := exec.Command("git", "tag", "-l", "--format", "%(taggerdate:iso8601)#%(refname:short)")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseTagsOutput(string(out))
|
||||
}
|
||||
|
||||
func parseTagsOutput(input string) ([]GitTag, error) {
|
||||
scanner := bufio.NewScanner(strings.NewReader(input))
|
||||
var result []GitTag
|
||||
for scanner.Scan() {
|
||||
if line := strings.TrimSpace(scanner.Text()); line != "" {
|
||||
values := strings.Split(line, "#")
|
||||
date, err := time.Parse("2006-01-02 15:04:05 -0700", values[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse tag data, message: %v", err)
|
||||
}
|
||||
result = append(result, GitTag{Name: values[1], Date: date})
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func parseLogOutput(messageMetadata map[string]string, log string) []GitCommitLog {
|
||||
scanner := bufio.NewScanner(strings.NewReader(log))
|
||||
scanner.Split(splitAt([]byte(endLine)))
|
||||
|
|
|
@ -19,9 +19,9 @@ func commitlog(t string, metadata map[string]string) GitCommitLog {
|
|||
}
|
||||
}
|
||||
|
||||
func releaseNote(sections map[string]ReleaseNoteSection, breakingChanges []string) ReleaseNote {
|
||||
func releaseNote(date time.Time, sections map[string]ReleaseNoteSection, breakingChanges []string) ReleaseNote {
|
||||
return ReleaseNote{
|
||||
Date: time.Now().Truncate(time.Minute),
|
||||
Date: date.Truncate(time.Minute),
|
||||
Sections: sections,
|
||||
BreakingChanges: breakingChanges,
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ const markdownTemplate = `# v{{.Version}} ({{.Date}})
|
|||
|
||||
// ReleaseNoteProcessor release note processor interface.
|
||||
type ReleaseNoteProcessor interface {
|
||||
Get(commits []GitCommitLog) ReleaseNote
|
||||
Get(date time.Time, commits []GitCommitLog) ReleaseNote
|
||||
Format(releasenote ReleaseNote, version semver.Version) string
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ func NewReleaseNoteProcessor(tags map[string]string) *ReleaseNoteProcessorImpl {
|
|||
}
|
||||
|
||||
// Get generate a release note based on commits.
|
||||
func (p ReleaseNoteProcessorImpl) Get(commits []GitCommitLog) ReleaseNote {
|
||||
func (p ReleaseNoteProcessorImpl) Get(date time.Time, commits []GitCommitLog) ReleaseNote {
|
||||
sections := make(map[string]ReleaseNoteSection)
|
||||
var breakingChanges []string
|
||||
for _, commit := range commits {
|
||||
|
@ -67,7 +67,7 @@ func (p ReleaseNoteProcessorImpl) Get(commits []GitCommitLog) ReleaseNote {
|
|||
}
|
||||
}
|
||||
|
||||
return ReleaseNote{Date: time.Now().Truncate(time.Minute), Sections: sections, BreakingChanges: breakingChanges}
|
||||
return ReleaseNote{Date: date.Truncate(time.Minute), Sections: sections, BreakingChanges: breakingChanges}
|
||||
}
|
||||
|
||||
// Format format a release note.
|
||||
|
|
|
@ -3,34 +3,41 @@ package sv
|
|||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestReleaseNoteProcessorImpl_Get(t *testing.T) {
|
||||
date := time.Now()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
date time.Time
|
||||
commits []GitCommitLog
|
||||
want ReleaseNote
|
||||
}{
|
||||
{
|
||||
name: "mapped tag",
|
||||
date: date,
|
||||
commits: []GitCommitLog{commitlog("t1", map[string]string{})},
|
||||
want: releaseNote(map[string]ReleaseNoteSection{"t1": rnSection("Tag 1", []GitCommitLog{commitlog("t1", map[string]string{})})}, nil),
|
||||
want: releaseNote(date, map[string]ReleaseNoteSection{"t1": rnSection("Tag 1", []GitCommitLog{commitlog("t1", map[string]string{})})}, nil),
|
||||
},
|
||||
{
|
||||
name: "unmapped tag",
|
||||
date: date,
|
||||
commits: []GitCommitLog{commitlog("t1", map[string]string{}), commitlog("unmapped", map[string]string{})},
|
||||
want: releaseNote(map[string]ReleaseNoteSection{"t1": rnSection("Tag 1", []GitCommitLog{commitlog("t1", map[string]string{})})}, nil),
|
||||
want: releaseNote(date, map[string]ReleaseNoteSection{"t1": rnSection("Tag 1", []GitCommitLog{commitlog("t1", map[string]string{})})}, nil),
|
||||
},
|
||||
{
|
||||
name: "breaking changes tag",
|
||||
date: date,
|
||||
commits: []GitCommitLog{commitlog("t1", map[string]string{}), commitlog("unmapped", map[string]string{"breakingchange": "breaks"})},
|
||||
want: releaseNote(map[string]ReleaseNoteSection{"t1": rnSection("Tag 1", []GitCommitLog{commitlog("t1", map[string]string{})})}, []string{"breaks"}),
|
||||
want: releaseNote(date, map[string]ReleaseNoteSection{"t1": rnSection("Tag 1", []GitCommitLog{commitlog("t1", map[string]string{})})}, []string{"breaks"}),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p := NewReleaseNoteProcessor(map[string]string{"t1": "Tag 1", "t2": "Tag 2"})
|
||||
if got := p.Get(tt.commits); !reflect.DeepEqual(got, tt.want) {
|
||||
if got := p.Get(tt.date, tt.commits); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("ReleaseNoteProcessorImpl.Get() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue
Block a user