diff --git a/README.md b/README.md index 4c31cc83..e156058a 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ USAGE: There are the following specification methods for . - 1. .. - Commit contained in tags from . + 1. .. - Commit contained in tags from . 2. .. - Commit from the to the latest tag. 3. .. - Commit from the oldest tag to . 4. - Commit contained in . @@ -163,6 +163,7 @@ OPTIONS: --init generate the git-chglog configuration file in interactive --config value, -c value specifies a different configuration file to pick up (default: ".chglog/config.yml") --output value, -o value output path and filename for the changelogs. If not specified, output to stdout + --next-tag value treat unreleased commits as specified tags (EXPERIMENTAL) --silent disable stdout output --no-color disable color output [$NO_COLOR] --no-emoji disable emoji output [$NO_EMOJI] @@ -486,6 +487,25 @@ See godoc [RenderData][doc-render-data] for available variables. By displaying it on the standard output, it makes it easy to change the contents. +
+ Can I commit CHANGELOG changes before creating tags? + + Yes, it can be solved by using the `--next-tag` flag. + + For example, let's say you want to upgrade your project to `2.0.0`. + You can create CHANGELOG containing `2.0.0` as follows. + + ```bash + $ git-chglog --next-tag 2.0.0 -o CHANGELOG.md + $ git commit -am "release 2.0.0" + $ git tag 2.0.0 + ``` + + The point to notice is that before actually creating a tag with git, it is conveying the next version with `--next-tag` :+1: + + This is a step that is necessary for project operation in many cases. +
+ diff --git a/chglog.go b/chglog.go index f62e63e5..1be130f8 100644 --- a/chglog.go +++ b/chglog.go @@ -16,6 +16,7 @@ import ( // Options is an option used to process commits type Options struct { Processor Processor + NextTag string // Treat unreleased commits as specified tags (EXPERIMENTAL) CommitFilters map[string][]string // Filter by using `Commit` properties and values. Filtering is not done by specifying an empty value CommitSortBy string // Property name to use for sorting `Commit` (e.g. `Scope`) CommitGroupBy string // Property name of `Commit` to be grouped into `CommitGroup` (e.g. `Type`) @@ -150,18 +151,30 @@ func (gen *Generator) Generate(w io.Writer, query string) error { } func (gen *Generator) readVersions(tags []*Tag, first string) ([]*Version, error) { + next := gen.config.Options.NextTag versions := []*Version{} for i, tag := range tags { - var rev string + var ( + isNext = next == tag.Name + rev string + ) - if i+1 < len(tags) { - rev = tags[i+1].Name + ".." + tag.Name - } else { - if first != "" { - rev = first + ".." + tag.Name + if isNext { + if tag.Previous != nil { + rev = tag.Previous.Name + "..HEAD" } else { - rev = tag.Name + rev = "HEAD" + } + } else { + if i+1 < len(tags) { + rev = tags[i+1].Name + ".." + tag.Name + } else { + if first != "" { + rev = first + ".." + tag.Name + } else { + rev = tag.Name + } } } @@ -180,12 +193,21 @@ func (gen *Generator) readVersions(tags []*Tag, first string) ([]*Version, error RevertCommits: revertCommits, NoteGroups: noteGroups, }) + + // Instead of `getTags()`, assign the date to the tag + if isNext { + tag.Date = commits[0].Author.Date + } } return versions, nil } func (gen *Generator) readUnreleased(tags []*Tag) (*Unreleased, error) { + if gen.config.Options.NextTag != "" { + return &Unreleased{}, nil + } + rev := "HEAD" if len(tags) > 0 { @@ -216,6 +238,33 @@ func (gen *Generator) getTags(query string) ([]*Tag, string, error) { return nil, "", err } + next := gen.config.Options.NextTag + if next != "" { + for _, tag := range tags { + if next == tag.Name { + return nil, "", fmt.Errorf("\"%s\" tag already exists", next) + } + } + + var previous *RelateTag + if len(tags) > 0 { + previous = &RelateTag{ + Name: tags[0].Name, + Subject: tags[0].Subject, + Date: tags[0].Date, + } + } + + // Assign the date with `readVersions()` + tags = append([]*Tag{ + &Tag{ + Name: next, + Subject: next, + Previous: previous, + }, + }, tags...) + } + if len(tags) == 0 { return nil, "", errors.New("git-tag does not exist") } diff --git a/chglog_test.go b/chglog_test.go index e603b27d..b233b45d 100644 --- a/chglog_test.go +++ b/chglog_test.go @@ -13,10 +13,14 @@ import ( ) var ( - cwd string - testRepoRoot = ".tmp" + cwd string + testRepoRoot = ".tmp" + internalTimeFormat = "2006-01-02 15:04:05" ) +type commitFunc = func(date, subject, body string) +type tagFunc = func(name string) + func TestMain(m *testing.M) { cwd, _ = os.Getwd() cleanup() @@ -25,9 +29,10 @@ func TestMain(m *testing.M) { os.Exit(code) } -func setup(dir string, setupRepo func(gitcmd.Client)) { +func setup(dir string, setupRepo func(commitFunc, tagFunc, gitcmd.Client)) { testDir := filepath.Join(cwd, testRepoRoot, dir) + os.RemoveAll(testDir) os.MkdirAll(testDir, os.ModePerm) os.Chdir(testDir) @@ -39,7 +44,21 @@ func setup(dir string, setupRepo func(gitcmd.Client)) { git.Exec("config", "user.name", "test_user") git.Exec("config", "user.email", "test@example.com") - setupRepo(git) + var commit = func(date, subject, body string) { + msg := subject + if body != "" { + msg += "\n\n" + body + } + t, _ := time.Parse(internalTimeFormat, date) + d := t.Format("Mon Jan 2 15:04:05 2006 +0000") + git.Exec("commit", "--allow-empty", "--date", d, "-m", msg) + } + + var tag = func(name string) { + git.Exec("tag", name) + } + + setupRepo(commit, tag, git) os.Chdir(cwd) } @@ -53,8 +72,8 @@ func TestGeneratorNotFoundTags(t *testing.T) { assert := assert.New(t) testName := "not_found" - setup(testName, func(git gitcmd.Client) { - git.Exec("commit", "--allow-empty", "--date", "Mon Jan 1 00:00:00 2018 +0000", "-m", "feat(*): New feature") + setup(testName, func(commit commitFunc, _ tagFunc, _ gitcmd.Client) { + commit("2018-01-01 00:00:00", "feat(*): New feature", "") }) gen := NewGenerator(&Config{ @@ -78,9 +97,9 @@ func TestGeneratorNotFoundCommits(t *testing.T) { assert := assert.New(t) testName := "not_found" - setup(testName, func(git gitcmd.Client) { - git.Exec("commit", "--allow-empty", "--date", "Mon Jan 1 00:00:00 2018 +0000", "-m", "feat(*): New feature") - git.Exec("tag", "1.0.0") + setup(testName, func(commit commitFunc, tag tagFunc, _ gitcmd.Client) { + commit("2018-01-01 00:00:00", "feat(*): New feature", "") + tag("1.0.0") }) gen := NewGenerator(&Config{ @@ -103,9 +122,9 @@ func TestGeneratorNotFoundCommitsOne(t *testing.T) { assert := assert.New(t) testName := "not_found" - setup(testName, func(git gitcmd.Client) { - git.Exec("commit", "--allow-empty", "--date", "Mon Jan 1 00:00:00 2018 +0000", "-m", "chore(*): First commit") - git.Exec("tag", "1.0.0") + setup(testName, func(commit commitFunc, tag tagFunc, _ gitcmd.Client) { + commit("2018-01-01 00:00:00", "chore(*): First commit", "") + tag("1.0.0") }) gen := NewGenerator(&Config{ @@ -158,24 +177,29 @@ func TestGeneratorWithTypeScopeSubject(t *testing.T) { assert := assert.New(t) testName := "type_scope_subject" - setup(testName, func(git gitcmd.Client) { - git.Exec("commit", "--allow-empty", "--date", "Mon Jan 1 00:00:00 2018 +0000", "-m", "chore(*): First commit") - git.Exec("commit", "--allow-empty", "--date", "Mon Jan 1 00:01:00 2018 +0000", "-m", "feat(core): Add foo bar") - git.Exec("commit", "--allow-empty", "--date", "Mon Jan 1 00:02:00 2018 +0000", "-m", "docs(readme): Update usage #123") + setup(testName, func(commit commitFunc, tag tagFunc, _ gitcmd.Client) { + commit("2018-01-01 00:00:00", "chore(*): First commit", "") + commit("2018-01-01 00:01:00", "feat(core): Add foo bar", "") + commit("2018-01-01 00:02:00", "docs(readme): Update usage #123", "") + tag("1.0.0") - git.Exec("tag", "1.0.0") - git.Exec("commit", "--allow-empty", "--date", "Tue Jan 2 00:00:00 2018 +0000", "-m", "feat(parser): New some super options #333") - git.Exec("commit", "--allow-empty", "--date", "Tue Jan 2 00:01:00 2018 +0000", "-m", "Merge pull request #999 from tsuyoshiwada/patch-1") - git.Exec("commit", "--allow-empty", "--date", "Tue Jan 2 00:02:00 2018 +0000", "-m", "Merge pull request #1000 from tsuyoshiwada/patch-1") - git.Exec("commit", "--allow-empty", "--date", "Tue Jan 2 00:03:00 2018 +0000", "-m", "Revert \"feat(core): Add foo bar @mention and issue #987\"") + commit("2018-01-02 00:00:00", "feat(parser): New some super options #333", "") + commit("2018-01-02 00:01:00", "Merge pull request #999 from tsuyoshiwada/patch-1", "") + commit("2018-01-02 00:02:00", "Merge pull request #1000 from tsuyoshiwada/patch-1", "") + commit("2018-01-02 00:03:00", "Revert \"feat(core): Add foo bar @mention and issue #987\"", "") + tag("1.1.0") - git.Exec("tag", "1.1.0") - git.Exec("commit", "--allow-empty", "--date", "Wed Jan 3 00:00:00 2018 +0000", "-m", "feat(context): Online breaking change\n\nBREAKING CHANGE: Online breaking change message.") - git.Exec("commit", "--allow-empty", "--date", "Wed Jan 3 00:01:00 2018 +0000", "-m", "feat(router): Muliple breaking change\n\nThis is body,\n\nBREAKING CHANGE:\nMultiple\nbreaking\nchange message.") + commit("2018-01-03 00:00:00", "feat(context): Online breaking change", "BREAKING CHANGE: Online breaking change message.") + commit("2018-01-03 00:01:00", "feat(router): Muliple breaking change", `This is body, - git.Exec("tag", "2.0.0-beta.0") - git.Exec("commit", "--allow-empty", "--date", "Thu Jan 4 00:00:00 2018 +0000", "-m", "refactor(context): gofmt") - git.Exec("commit", "--allow-empty", "--date", "Thu Jan 4 00:01:00 2018 +0000", "-m", "fix(core): Fix commit\n\nThis is body message.") +BREAKING CHANGE: +Multiple +breaking +change message.`) + tag("2.0.0-beta.0") + + commit("2018-01-04 00:00:00", "refactor(context): gofmt", "") + commit("2018-01-04 00:01:00", "fix(core): Fix commit\n\nThis is body message.", "") }) gen := NewGenerator(&Config{ @@ -230,13 +254,18 @@ func TestGeneratorWithTypeScopeSubject(t *testing.T) { err := gen.Generate(buf, "") assert.Nil(err) - assert.Equal(` -## 2.0.0-beta.0 (2018-01-03) + assert.Equal(` +## [Unreleased] +### Bug Fixes +- **core:** Fix commit + + + +## [2.0.0-beta.0] - 2018-01-03 ### Features - -* **context:** Online breaking change -* **router:** Muliple breaking change +- **context:** Online breaking change +- **router:** Muliple breaking change ### BREAKING CHANGE @@ -247,28 +276,118 @@ change message. Online breaking change message. - -## 1.1.0 (2018-01-02) - +## [1.1.0] - 2018-01-02 ### Features - -* **parser:** New some super options #333 +- **parser:** New some super options #333 ### Reverts - -* feat(core): Add foo bar @mention and issue #987 +- feat(core): Add foo bar @mention and issue #987 ### Pull Requests - -* Merge pull request #1000 from tsuyoshiwada/patch-1 -* Merge pull request #999 from tsuyoshiwada/patch-1 +- Merge pull request #1000 from tsuyoshiwada/patch-1 +- Merge pull request #999 from tsuyoshiwada/patch-1 -## 1.0.0 (2018-01-01) - +## 1.0.0 - 2018-01-01 ### Features +- **core:** Add foo bar -* **core:** Add foo bar`, strings.TrimSpace(buf.String())) + +[Unreleased]: https://github.com/git-chglog/git-chglog/compare/2.0.0-beta.0...HEAD +[2.0.0-beta.0]: https://github.com/git-chglog/git-chglog/compare/1.1.0...2.0.0-beta.0 +[1.1.0]: https://github.com/git-chglog/git-chglog/compare/1.0.0...1.1.0`, strings.TrimSpace(buf.String())) +} + +func TestGeneratorWithNextTag(t *testing.T) { + assert := assert.New(t) + testName := "type_scope_subject" + + setup(testName, func(commit commitFunc, tag tagFunc, _ gitcmd.Client) { + commit("2018-01-01 00:00:00", "feat(core): version 1.0.0", "") + tag("1.0.0") + + commit("2018-02-01 00:00:00", "feat(core): version 2.0.0", "") + tag("2.0.0") + + commit("2018-03-01 00:00:00", "feat(core): version 3.0.0", "") + }) + + gen := NewGenerator(&Config{ + Bin: "git", + WorkingDir: filepath.Join(testRepoRoot, testName), + Template: filepath.Join(cwd, "testdata", testName+".md"), + Info: &Info{ + Title: "CHANGELOG Example", + RepositoryURL: "https://github.com/git-chglog/git-chglog", + }, + Options: &Options{ + NextTag: "3.0.0", + CommitFilters: map[string][]string{ + "Type": []string{ + "feat", + }, + }, + CommitSortBy: "Scope", + CommitGroupBy: "Type", + CommitGroupSortBy: "Title", + CommitGroupTitleMaps: map[string]string{ + "feat": "Features", + }, + HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$", + HeaderPatternMaps: []string{ + "Type", + "Scope", + "Subject", + }, + }, + }) + + buf := &bytes.Buffer{} + err := gen.Generate(buf, "") + + assert.Nil(err) + assert.Equal(` +## [Unreleased] + + + +## [3.0.0] - 2018-03-01 +### Features +- **core:** version 3.0.0 + + + +## [2.0.0] - 2018-02-01 +### Features +- **core:** version 2.0.0 + + + +## 1.0.0 - 2018-01-01 +### Features +- **core:** version 1.0.0 + + +[Unreleased]: https://github.com/git-chglog/git-chglog/compare/3.0.0...HEAD +[3.0.0]: https://github.com/git-chglog/git-chglog/compare/2.0.0...3.0.0 +[2.0.0]: https://github.com/git-chglog/git-chglog/compare/1.0.0...2.0.0`, strings.TrimSpace(buf.String())) + + buf = &bytes.Buffer{} + err = gen.Generate(buf, "3.0.0") + + assert.Nil(err) + assert.Equal(` +## [Unreleased] + + + +## [3.0.0] - 2018-03-01 +### Features +- **core:** version 3.0.0 + + +[Unreleased]: https://github.com/git-chglog/git-chglog/compare/3.0.0...HEAD +[3.0.0]: https://github.com/git-chglog/git-chglog/compare/2.0.0...3.0.0`, strings.TrimSpace(buf.String())) } diff --git a/cmd/git-chglog/config.go b/cmd/git-chglog/config.go index a81340ee..cef822a2 100644 --- a/cmd/git-chglog/config.go +++ b/cmd/git-chglog/config.go @@ -257,6 +257,7 @@ func (config *Config) Convert(ctx *CLIContext) *chglog.Config { RepositoryURL: info.RepositoryURL, }, Options: &chglog.Options{ + NextTag: ctx.NextTag, CommitFilters: opts.Commits.Filters, CommitSortBy: opts.Commits.SortBy, CommitGroupBy: opts.CommitGroups.GroupBy, diff --git a/cmd/git-chglog/context.go b/cmd/git-chglog/context.go index c6a8740e..09c5cb59 100644 --- a/cmd/git-chglog/context.go +++ b/cmd/git-chglog/context.go @@ -15,6 +15,7 @@ type CLIContext struct { NoColor bool NoEmoji bool Query string + NextTag string } // InitContext ... diff --git a/cmd/git-chglog/main.go b/cmd/git-chglog/main.go index fe435507..ac26bf6d 100644 --- a/cmd/git-chglog/main.go +++ b/cmd/git-chglog/main.go @@ -53,7 +53,7 @@ func main() { $ {{.Name}} --config custom/dir/config.yml - The adove is a command that uses a configuration file placed other than ".chglog/config.yml". + The above is a command that uses a configuration file placed other than ".chglog/config.yml". `, ttl("USAGE:"), ttl("OPTIONS:"), @@ -89,6 +89,11 @@ func main() { Usage: "output path and filename for the changelogs. If not specified, output to stdout", }, + cli.StringFlag{ + Name: "next-tag", + Usage: "treat unreleased commits as specified tags (EXPERIMENTAL)", + }, + // silent cli.BoolFlag{ Name: "silent", @@ -155,6 +160,7 @@ func main() { NoColor: c.Bool("no-color"), NoEmoji: c.Bool("no-emoji"), Query: c.Args().First(), + NextTag: c.String("next-tag"), }, fs, NewConfigLoader(), diff --git a/fields.go b/fields.go index a60174dc..46cf8cc3 100644 --- a/fields.go +++ b/fields.go @@ -106,7 +106,6 @@ type Version struct { // Unreleased is unreleased commit dataset type Unreleased struct { - Tag *Tag CommitGroups []*CommitGroup Commits []*Commit MergeCommits []*Commit diff --git a/testdata/type_scope_subject.md b/testdata/type_scope_subject.md index 918ea9d9..33103070 100644 --- a/testdata/type_scope_subject.md +++ b/testdata/type_scope_subject.md @@ -1,22 +1,56 @@ -{{range .Versions}} - -## {{.Tag.Name}} ({{datetime "2006-01-02" .Tag.Date}}) -{{range .CommitGroups}} -### {{.Title}} -{{range .Commits}} -* {{if ne .Scope ""}}**{{.Scope}}:** {{end}}{{.Subject}}{{end}} -{{end}}{{if .RevertCommits}} +{{ if .Versions -}} + +## [Unreleased] + +{{ if .Unreleased.CommitGroups -}} +{{ range .Unreleased.CommitGroups -}} +### {{ .Title }} +{{ range .Commits -}} +- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} +{{ end }} +{{ end -}} +{{ end -}} +{{ end -}} + +{{ range .Versions }} + +## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }} +{{ range .CommitGroups -}} +### {{ .Title }} +{{ range .Commits -}} +- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} +{{ end }} +{{ end -}} + +{{- if .RevertCommits -}} ### Reverts -{{range .RevertCommits}} -* {{.Revert.Header}}{{end}} -{{end}}{{if .MergeCommits}} +{{ range .RevertCommits -}} +- {{ .Revert.Header }} +{{ end }} +{{ end -}} + +{{- if .MergeCommits -}} ### Pull Requests -{{range .MergeCommits}} -* {{.Header}}{{end}} -{{end}}{{range .NoteGroups}} -### {{.Title}} -{{range .Notes}} -{{.Body}} -{{end}} -{{end}} -{{end}} +{{ range .MergeCommits -}} +- {{ .Header }} +{{ end }} +{{ end -}} + +{{- if .NoteGroups -}} +{{ range .NoteGroups -}} +### {{ .Title }} +{{ range .Notes }} +{{ .Body }} +{{ end }} +{{ end -}} +{{ end -}} +{{ end -}} + +{{- if .Versions }} +[Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD +{{ range .Versions -}} +{{ if .Tag.Previous -}} +[{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }} +{{ end -}} +{{ end -}} +{{ end -}}