diff --git a/internal/client/git.go b/internal/client/git.go index f3270101..993325c1 100644 --- a/internal/client/git.go +++ b/internal/client/git.go @@ -1,6 +1,7 @@ package client import ( + "fmt" "io" "path" "regexp" @@ -200,66 +201,107 @@ func (g *GitClient) Logs(from string, to string) ([]*object.Commit, error) { return commits, errors.WithStack(err) } -func (g *GitClient) LogsAuto() ([]*object.Commit, error) { - var ( - toHash string - ) +// get commit hash from a string +// str: HEAD +// str: HASH +// str: tag +func (g *GitClient) getCommitHash(str string) (string, error) { + if str == "HEAD" { + commit, err := g.repository.Head() - head, err := g.repository.Head() + if err != nil { + return "", errors.WithStack(err) + } + + return commit.Hash().String(), nil + } else { + version := versionRegexp.ReplaceAllString(str, "") + _, err := semver.New(version) + + if err != nil { + return "", errors.WithStack(err) + } + + tag, err := g.TagName(str) + + if err != nil { + return "", errors.WithStack(err) + } + + if tag == nil { + return "", errors.New(fmt.Sprintf(`tag '%s' not exist`, str)) + } + + return tag.Commit.Hash.String(), nil + } +} + +func (g *GitClient) GetTagRangesByTagName(start string, end string) ([]*Tag, error) { + tags, err := g.Tags() if err != nil { return nil, errors.WithStack(err) } - // get latest tag - tag, err := g.TagN(0) + startIndex := 0 + endIndex := 0 + + for index, tag := range tags { + if tag.Name == start { + startIndex = index + } else if tag.Name == end { + endIndex = index + } + } + + result := tags[startIndex : endIndex+1] + + return result, nil +} + +func (g *GitClient) GetTagRangesByCommitHash(startHash string, endHash string) ([]*Tag, error) { + start, err := g.repository.CommitObject(plumbing.NewHash(startHash)) if err != nil { return nil, errors.WithStack(err) } - // if tag not exist - if tag != nil { - toHash = tag.Commit.Hash.String() + end, err := g.repository.CommitObject(plumbing.NewHash(endHash)) - // if head is latest tag's commit - if head.Hash().String() == toHash { - // get next tag - nextTag, err := g.TagN(1) + if err != nil { + return nil, errors.WithStack(err) + } - if err != nil { - return nil, errors.WithStack(err) - } + tags, err := g.Tags() - if nextTag != nil { - toHash = nextTag.Commit.Hash.String() - } else { - toHash = "" - } + if err != nil { + return nil, errors.WithStack(err) + } + + result := make([]*Tag, 0) + + for _, tag := range tags { + tagTime := tag.Commit.Committer.When + if tagTime.After(start.Author.When) && tagTime.After(end.Author.When) { + result = append(result, tag) } } - cIter, err := g.repository.Log(&git.LogOptions{From: head.Hash()}) + return result, nil +} + +func (g *GitClient) LogsRange(start string, end string) ([]*object.Commit, error) { + startHash, err := g.getCommitHash(start) if err != nil { return nil, errors.WithStack(err) } - commits := make([]*object.Commit, 0) + endHash, err := g.getCommitHash(end) - for { - if commit, er := cIter.Next(); er == io.EOF { - break - } else if er != nil { - return nil, er - } else if commit == nil { - break - } else if commit.Hash.String() == toHash { - break - } else { - commits = append(commits, commit) - } + if err != nil { + return nil, errors.WithStack(err) } - return commits, errors.WithStack(err) + return g.Logs(startHash, endHash) } diff --git a/internal/printer/render.go b/internal/printer/render.go new file mode 100644 index 00000000..343f644a --- /dev/null +++ b/internal/printer/render.go @@ -0,0 +1,29 @@ +package printer + +import ( + "bytes" + "html/template" + "strings" + + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/pkg/errors" +) + +func Bytes(version string, commits []*object.Commit) ([]byte, error) { + ctx := transform(version, commits) + + t := template.New("bytes") + + t.Funcs(template.FuncMap{"StringsJoin": strings.Join}) + + if t, err := t.Parse(defaultTemplate); err != nil { + return nil, errors.WithStack(err) + } else { + b := bytes.NewBuffer([]byte{}) + if err := t.Execute(b, ctx); err != nil { + return nil, errors.WithStack(err) + } + + return b.Bytes(), nil + } +} diff --git a/internal/printer/stdout.go b/internal/printer/stdout.go index b87dd258..bf78be30 100644 --- a/internal/printer/stdout.go +++ b/internal/printer/stdout.go @@ -16,9 +16,7 @@ func Stdout(version string, commits []*object.Commit) error { t := template.New("stdout") - t.Funcs(template.FuncMap{ - "StringsJoin": strings.Join, - }) + t.Funcs(template.FuncMap{"StringsJoin": strings.Join}) if t, err := t.Parse(defaultTemplate); err != nil { return errors.WithStack(err) diff --git a/internal/printer/template_default.go b/internal/printer/template_default.go index 18f8f385..47bdbde9 100644 --- a/internal/printer/template_default.go +++ b/internal/printer/template_default.go @@ -1,6 +1,7 @@ package printer const defaultTemplate = `# {{ .Version }} + {{- define "body" -}} {{range . -}} - {{if .Field.Header.Scope }}**{{ .Field.Header.Scope }}**: {{ end }}{{ .Field.Header.Subject }}({{.Short}}) (thanks @{{ .Author.Name }}){{if .Field.Footer }} {{if .Field.Footer.Closes }}, Closes: {{ StringsJoin .Field.Footer.Closes "," }} {{- end }} {{- end}} @@ -10,33 +11,34 @@ const defaultTemplate = `# {{ .Version }} ### New feature: {{ template "body" .Feat }} {{- end -}} -{{if .Fix -}} +{{if .Fix}} ### Bugs fixed: {{ template "body" .Fix }} {{- end -}} -{{if .Refactor -}} +{{if .Refactor}} ### Code Refactoring: {{ template "body" .Refactor }} {{- end -}} -{{if .Test -}} +{{if .Test}} ### Testing: {{ template "body" .Test }} {{- end -}} -{{if .Perf -}} +{{if .Perf}} ### Performance improves: {{ template "body" .Perf }} {{- end -}} -{{if .Docs -}} +{{if .Docs}} ### Documentation: {{ template "body" .Docs }} {{- end -}} -{{- if .BreakingChanges -}} +{{if .BreakingChanges}} ### BREAKING CHANGES: {{ range .BreakingChanges -}} -- {{ .Field.Footer.BreakingChange.Title }} {{ .Field.Title }} +- {{if .Field.Footer.BreakingChange.Title}}{{ .Field.Footer.BreakingChange.Title }}{{ else }}{{ .Field.Title }}{{ end }} {{ .Field.Footer.BreakingChange.Content }} {{- end -}} -{{- end -}} +{{- end}} + ### Commits({{ len .Commits }}): {{range .Commits -}} - **{{ .Short }}** {{ .Field.Title }} diff --git a/main.go b/main.go index c3f898fc..a0e74a15 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,12 @@ package main import ( + "bytes" "flag" "fmt" + "io" "os" + "strings" "github.com/axetroy/changelog/internal/client" "github.com/axetroy/changelog/internal/printer" @@ -173,38 +176,91 @@ func run() error { } } } else { - // Only output one version of the changelog - from, err := client.TagName(version) + ranges := strings.Split(version, "~") + length := len(ranges) - if err != nil { - return errors.WithStack(err) - } + // handle version range + // v2.0.0~v1.0.0 + // HEAD~ + if length == 2 { + tags, err := client.GetTagRangesByTagName(ranges[0], ranges[1]) - if from == nil { - return errors.New(fmt.Sprintf("can not found tag %s", version)) - } + if err != nil { + return errors.WithStack(err) + } - to, err := client.NextTag(from) + var output []byte - if err != nil { - return errors.WithStack(err) - } + for index, tag := range tags { + fromHash := tag.Commit.Hash.String() + toHash := "" + // if not last element + if index != len(tags)-1 { + toHash = tags[index+1].Commit.Hash.String() + } else { + nextTag, err := client.NextTag(tag) + + if err != nil { + return errors.WithStack(err) + } + + if nextTag != nil { + toHash = nextTag.Commit.Hash.String() + } + } - toHash := "" + commits, err := client.Logs(fromHash, toHash) - // if don't have next tag - if to != nil { - toHash = to.Commit.Hash.String() - } + if err != nil { + return errors.WithStack(err) + } - commits, err := client.Logs(from.Commit.Hash.String(), toHash) + if b, err := printer.Bytes(tag.Name, commits); err != nil { + return errors.WithStack(err) + } else { + output = append(output, b...) + } + } - if err != nil { - return errors.WithStack(err) - } + _, err = io.Copy(os.Stdout, bytes.NewBuffer(output)) - if err := printer.Stdout(version, commits); err != nil { - return errors.WithStack(err) + if err != nil { + return errors.WithStack(err) + } + } else { + // only output one version of the changelog + from, err := client.TagName(version) + + if err != nil { + return errors.WithStack(err) + } + + if from == nil { + return errors.New(fmt.Sprintf("can not found tag %s", version)) + } + + to, err := client.NextTag(from) + + if err != nil { + return errors.WithStack(err) + } + + toHash := "" + + // if don't have next tag + if to != nil { + toHash = to.Commit.Hash.String() + } + + commits, err := client.Logs(from.Commit.Hash.String(), toHash) + + if err != nil { + return errors.WithStack(err) + } + + if err := printer.Stdout(version, commits); err != nil { + return errors.WithStack(err) + } } }