Skip to content

Commit

Permalink
Merge pull request #43 from bvieira/#40
Browse files Browse the repository at this point in the history
Feature: support templates for release-notes and changelog
  • Loading branch information
bvieira authored Mar 1, 2022
2 parents 6b4948b + 893f893 commit cf43b2a
Show file tree
Hide file tree
Showing 20 changed files with 566 additions and 155 deletions.
2 changes: 1 addition & 1 deletion .sv4git.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: "1.0"
version: "1.1"

versioning:
update-major: []
Expand Down
126 changes: 116 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<p align="center">
<h1 align="center">sv4git</h1>
<p align="center">semantic version for git</p>
<p align="center">A command line tool (CLI) to validate commit messages, bump version, create tags and generate changelogs!</p>
<p align="center">
<a href="https://github.com/bvieira/sv4git/releases/latest"><img alt="Release" src="https://img.shields.io/github/release/bvieira/sv4git.svg?style=for-the-badge"></a>
<a href="https://pkg.go.dev/github.com/bvieira/sv4git/v2"><img alt="Go Reference" src="https://img.shields.io/badge/-Reference-blue?style=for-the-badge&logo=go&labelColor=gray"></a>
Expand All @@ -26,11 +26,17 @@
If you want to install from source using `go install`, just run:

```bash
# keep in mind that with this, it will compile from source and won't show the version on cli -h.
go install github.com/bvieira/sv4git/v2/cmd/git-sv@latest

# if you want to add the version on the binary, run this command instead.
SV4GIT_VERSION=$(go list -f '{{ .Version }}' -m github.com/bvieira/sv4git/v2@latest | sed 's/v//') && go install --ldflags "-X main.Version=$SV4GIT_VERSION" github.com/bvieira/sv4git/v2/cmd/git-sv@v$SV4GIT_VERSION
```

### Config

#### YAML

There are 3 config levels when using sv4git: [default](#default), [user](#user), [repository](#repository). All of them are merged considering the follow priority: **repository > user > default**.

To see the current config, run:
Expand All @@ -39,17 +45,17 @@ To see the current config, run:
git sv cfg show
```

#### Configuration Types
##### Configuration Types

##### Default
###### Default

To check the default configuration, run:

```bash
git sv cfg default
```

##### User
###### User

For user config, it is necessary to define the `SV4GIT_HOME` environment variable, eg.:

Expand All @@ -64,14 +70,14 @@ And create a `config.yml` file inside it, eg.:
└── config.yml
```

##### Repository
###### Repository

Create a `.sv4git.yml` file on the root of your repository, eg.: [.sv4git.yml](.sv4git.yml).

#### Configuration format
##### Configuration format

```yml
version: "1.0" #config version
version: "1.1" #config version

versioning: # versioning bump
update-major: [] # Commit types used to bump major.
Expand All @@ -85,13 +91,24 @@ tag:
pattern: '%d.%d.%d' # Pattern used to create git tag.

release-notes:
# Headers names for release notes markdown. To disable a section just remove the header line.
# It's possible to add other commit types, the release note will be created respecting the following order:
# feat, fix, refactor, perf, test, build, ci, chore, docs, style, breaking-change
# Deprecated!!! please use 'sections' instead!
# Headers names for release notes markdown. To disable a section just remove the header
# line. It's possible to add other commit types, the release note will be created
# respecting the following order: feat, fix, refactor, perf, test, build, ci, chore, docs, style, breaking-change.
headers:
breaking-change: Breaking Changes
feat: Features
fix: Bug Fixes

sections: # Array with each section of release note. Check template section for more information.
- name: Features # Name used on section.
section-type: commits # Type of the section, supported types: commits, breaking-changes.
commit-types: [feat] # Commit types for commit section-type, one commit type cannot be in more than one section.
- name: Bug Fixes
section-type: commits
commit-types: [fix]
- name: Breaking Changes
section-type: breaking-changes

branches: # Git branches config.
prefix: ([a-z]+\/)? # Prefix used on branch name, it should be a regex group.
Expand All @@ -116,6 +133,95 @@ commit-message:
regex: '[A-Z]+-[0-9]+' # Regex for issue id.
```
#### Templates
**sv4git** uses *go templates* to format the output for `release-notes` and `changelog`, to see how the default template is configured check [template directory](cmd/git-sv/resources/templates). On v2.7.0+, its possible to overwrite the default configuration by adding `.sv4git/templates` on your repository. The cli expects that at least 2 files exists on your directory: `changelog-md.tpl` and `releasenotes-md.tpl`.

```bash
.sv4git
└── templates
├── changelog-md.tpl
└── releasenotes-md.tpl
```

Everything inside `.sv4git/templates` will be loaded, so it's possible to add more files to be used as needed.

##### Variables

To execute the template the `releasenotes-md.tpl` will receive a single **ReleaseNote** and `changelog-md.tpl` will receive a list of **ReleaseNote** as variables.

Each **ReleaseNoteSection** will be configured according with `release-notes.section` from config file. The order for each section will be maintained and the **SectionType** is defined according with `section-type` attribute as described on the table below.

| section-type | ReleaseNoteSection |
| -- | -- |
| commits | ReleaseNoteCommitsSection |
| breaking-changes | ReleaseNoteBreakingChangeSection |

> :warning: currently only `commits` and `breaking-changes` are supported as `section-types`, using a different value for this field will make the section to be removed from the template variables.

Check below the variables available:

```go
ReleaseNote
Release string // 'v' followed by version if present, if not tag will be used instead.
Tag string // Current tag, if available.
Version *Version // Version from tag or next version according with semver.
Date time.Time
Sections []ReleaseNoteSection // ReleaseNoteCommitsSection or ReleaseNoteBreakingChangeSection
AuthorNames []string // Author names recovered from commit message (user.name from git)
Version
Major int
Minor int
Patch int
Prerelease string
Metadata string
Original string
ReleaseNoteCommitsSection // SectionType == commits
SectionType string
SectionName string
Types []string
Items []GitCommitLog
HasMultipleTypes bool
ReleaseNoteBreakingChangeSection // SectionType == breaking-changes
SectionType string
SectionName string
Messages []string
GitCommitLog
Date string
Timestamp int
AuthorName string
Hash string
Message CommitMessage
CommitMessage
Type string
Scope string
Description string
Body string
IsBreakingChange bool
Metadata map[string]string
```

##### Functions

Beside the [go template functions](https://pkg.go.dev/text/template#hdr-Functions), the folowing functions are availiable to use in the templates. Check [formatter_functions.go](sv/formatter_functions.go) to see the functions implementation.

###### timefmt

**Usage:** timefmt time "2006-01-02"

Receive a time.Time and a layout string and returns a textual representation of the time according with the layout provided. Check <https://pkg.go.dev/time#Time.Format> for more information.

###### getsection

**Usage:** getsection sections "Features"

Receive a list of ReleaseNoteSection and a Section name and returns a section with the provided name. If no section is found, it will return `nil`.

### Running

Run `git-sv` to get the list of available parameters:
Expand Down
44 changes: 41 additions & 3 deletions cmd/git-sv/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,21 @@ func readConfig(filepath string) (Config, error) {
func defaultConfig() Config {
skipDetached := false
return Config{
Version: "1.0",
Version: "1.1",
Versioning: sv.VersioningConfig{
UpdateMajor: []string{},
UpdateMinor: []string{"feat"},
UpdatePatch: []string{"build", "ci", "chore", "docs", "fix", "perf", "refactor", "style", "test"},
IgnoreUnknown: false,
},
Tag: sv.TagConfig{Pattern: "%d.%d.%d"},
ReleaseNotes: sv.ReleaseNotesConfig{Headers: map[string]string{"fix": "Bug Fixes", "feat": "Features", "breaking-change": "Breaking Changes"}},
Tag: sv.TagConfig{Pattern: "%d.%d.%d"},
ReleaseNotes: sv.ReleaseNotesConfig{
Sections: []sv.ReleaseNotesSectionConfig{
{Name: "Features", SectionType: sv.ReleaseNotesSectionTypeCommits, CommitTypes: []string{"feat"}},
{Name: "Bug Fixes", SectionType: sv.ReleaseNotesSectionTypeCommits, CommitTypes: []string{"fix"}},
{Name: "Breaking Changes", SectionType: sv.ReleaseNotesSectionTypeBreakingChanges},
},
},
Branches: sv.BranchesConfig{
Prefix: "([a-z]+\\/)?",
Suffix: "(-.*)?",
Expand Down Expand Up @@ -129,3 +135,35 @@ func (t *mergeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.V
}
return nil
}

func migrateConfig(cfg Config, filename string) Config {
if cfg.ReleaseNotes.Headers == nil {
return cfg
}
warnf("config 'release-notes.headers' on %s is deprecated, please use 'sections' instead!", filename)

return Config{
Version: cfg.Version,
Versioning: cfg.Versioning,
Tag: cfg.Tag,
ReleaseNotes: sv.ReleaseNotesConfig{
Sections: migrateReleaseNotesConfig(cfg.ReleaseNotes.Headers),
},
Branches: cfg.Branches,
CommitMessage: cfg.CommitMessage,
}
}

func migrateReleaseNotesConfig(headers map[string]string) []sv.ReleaseNotesSectionConfig {
order := []string{"feat", "fix", "refactor", "perf", "test", "build", "ci", "chore", "docs", "style"}
var sections []sv.ReleaseNotesSectionConfig
for _, key := range order {
if name, exists := headers[key]; exists {
sections = append(sections, sv.ReleaseNotesSectionConfig{Name: name, SectionType: sv.ReleaseNotesSectionTypeCommits, CommitTypes: []string{key}})
}
}
if name, exists := headers["breaking-change"]; exists {
sections = append(sections, sv.ReleaseNotesSectionConfig{Name: name, SectionType: sv.ReleaseNotesSectionTypeBreakingChanges})
}
return sections
}
7 changes: 5 additions & 2 deletions cmd/git-sv/log.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package main

import "fmt"
import (
"fmt"
"os"
)

func warnf(format string, values ...interface{}) {
fmt.Printf("WARN: "+format+"\n", values...)
fmt.Fprintf(os.Stderr, "WARN: "+format+"\n", values...)
}
45 changes: 31 additions & 14 deletions cmd/git-sv/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package main

import (
"embed"
"io/fs"
"log"
"os"
"path/filepath"
Expand All @@ -15,17 +17,36 @@ var Version = "source"
const (
configFilename = "config.yml"
repoConfigFilename = ".sv4git.yml"
configDir = ".sv4git"
)

var (
//go:embed resources/templates/*.tpl
defaultTemplatesFS embed.FS
)

func templateFS(filepath string) fs.FS {
if _, err := os.Stat(filepath); err != nil {
defaultTemplatesFS, _ := fs.Sub(defaultTemplatesFS, "resources/templates")
return defaultTemplatesFS
}
return os.DirFS(filepath)
}

func main() {
log.SetFlags(0)

cfg := loadCfg()
repoPath, rerr := getRepoPath()
if rerr != nil {
log.Fatal("failed to discovery repository top level, error: ", rerr)
}

cfg := loadCfg(repoPath)
messageProcessor := sv.NewMessageProcessor(cfg.CommitMessage, cfg.Branches)
git := sv.NewGit(messageProcessor, cfg.Tag)
semverProcessor := sv.NewSemVerCommitsProcessor(cfg.Versioning, cfg.CommitMessage)
releasenotesProcessor := sv.NewReleaseNoteProcessor(cfg.ReleaseNotes)
outputFormatter := sv.NewOutputFormatter()
outputFormatter := sv.NewOutputFormatter(templateFS(filepath.Join(repoPath, configDir, "templates")))

app := cli.NewApp()
app.Name = "sv"
Expand Down Expand Up @@ -145,26 +166,22 @@ func main() {
}
}

func loadCfg() Config {
envCfg := loadEnvConfig()

func loadCfg(repoPath string) Config {
cfg := defaultConfig()

envCfg := loadEnvConfig()
if envCfg.Home != "" {
if homeCfg, err := readConfig(filepath.Join(envCfg.Home, configFilename)); err == nil {
if merr := merge(&cfg, homeCfg); merr != nil {
homeCfgFilepath := filepath.Join(envCfg.Home, configFilename)
if homeCfg, err := readConfig(homeCfgFilepath); err == nil {
if merr := merge(&cfg, migrateConfig(homeCfg, homeCfgFilepath)); merr != nil {
log.Fatal("failed to merge user config, error: ", merr)
}
}
}

repoPath, rerr := getRepoPath()
if rerr != nil {
log.Fatal("failed to get repository path, error: ", rerr)
}

if repoCfg, err := readConfig(filepath.Join(repoPath, repoConfigFilename)); err == nil {
if merr := merge(&cfg, repoCfg); merr != nil {
repoCfgFilepath := filepath.Join(repoPath, repoConfigFilename)
if repoCfg, err := readConfig(repoCfgFilepath); err == nil {
if merr := merge(&cfg, migrateConfig(repoCfg, repoCfgFilepath)); merr != nil {
log.Fatal("failed to merge repo config, error: ", merr)
}
if len(repoCfg.ReleaseNotes.Headers) > 0 { // mergo is merging maps, headers will be overwritten
Expand Down
6 changes: 6 additions & 0 deletions cmd/git-sv/resources/templates/changelog-md.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Changelog
{{- range .}}

{{template "releasenotes-md.tpl" .}}
---
{{- end}}
8 changes: 8 additions & 0 deletions cmd/git-sv/resources/templates/releasenotes-md.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## {{if .Release}}{{.Release}}{{end}}{{if and (not .Date.IsZero) .Release}} ({{end}}{{timefmt .Date "2006-01-02"}}{{if and (not .Date.IsZero) .Release}}){{end}}
{{- range $section := .Sections }}
{{- if (eq $section.SectionType "commits") }}
{{- template "rn-md-section-commits.tpl" $section }}
{{- else if (eq $section.SectionType "breaking-changes")}}
{{- template "rn-md-section-breaking-changes.tpl" $section }}
{{- end}}
{{- end}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{{- if ne .Name ""}}

### {{.Name}}
{{range $k,$v := .Messages}}
- {{$v}}
{{- end}}
{{- end}}
7 changes: 7 additions & 0 deletions cmd/git-sv/resources/templates/rn-md-section-commits.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{{- if .}}{{- if ne .SectionName ""}}

### {{.SectionName}}
{{range $k,$v := .Items}}
- {{if $v.Message.Scope}}**{{$v.Message.Scope}}:** {{end}}{{$v.Message.Description}} ({{$v.Hash}}){{if $v.Message.Metadata.issue}} ({{$v.Message.Metadata.issue}}){{end}}
{{- end}}
{{- end}}{{- end}}
Loading

0 comments on commit cf43b2a

Please sign in to comment.