Skip to content

Commit

Permalink
Merge pull request #35 from chelnak/next_version_flag
Browse files Browse the repository at this point in the history
Implementation of next-version and unreleased
  • Loading branch information
chelnak authored May 7, 2022
2 parents 8520f27 + e0d29a3 commit 55cc726
Show file tree
Hide file tree
Showing 18 changed files with 239 additions and 28 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ jobs:
with:
version: latest

# - name: test
# run: go test -v ./...
- name: test
run: go test -v ./...

release:
name: release
Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ lint:
build:
@WORKINGDIR=$(pwd) goreleaser build --snapshot --rm-dist --single-target

.PHONY: mocks
mocks:
@mockery --all --keeptree
@mockery --all
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ gh extension upgrade chelnak/gh-changelog
gh changelog new
```

### Create a new changelog for an untagged version

```bash
gh changelog new --next-version v1.2.0
```

### View your changelog in the terminal

```bash
Expand Down Expand Up @@ -72,4 +78,8 @@ sections:
# When set to true, unlabelled entries will not be included in the changelog.
# By default they will be grouped in a section named "Other".
skip_entries_without_label: false
# Adds an unreleased section to the changelog. This will contain any qualifying entries
# that have been added since the last tag.
# Note: The unreleased section is not created when the --next-version flag is used.
show_unreleased: true
```
7 changes: 7 additions & 0 deletions cmd/new.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/spf13/viper"
)

var nextVersion string

// newCmd is the entry point for creating a new changelog
var newCmd = &cobra.Command{
Use: "new",
Expand All @@ -17,6 +19,7 @@ var newCmd = &cobra.Command{
RunE: func(command *cobra.Command, args []string) error {
builder := changelog.NewChangelogBuilder()
builder = builder.WithSpinner(true)
builder = builder.WithNextVersion(nextVersion)

changelog, err := builder.Build()
if err != nil {
Expand All @@ -32,3 +35,7 @@ var newCmd = &cobra.Command{
return writer.Write(f, changelog)
},
}

func init() {
newCmd.Flags().StringVar(&nextVersion, "next-version", "", "The next version to be released. The value passed does not have to be an existing tag.")
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/chelnak/gh-changelog
go 1.18

require (
github.com/Masterminds/semver/v3 v3.1.1
github.com/briandowns/spinner v1.18.1
github.com/charmbracelet/bubbles v0.10.3
github.com/charmbracelet/bubbletea v0.20.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
Expand Down
111 changes: 98 additions & 13 deletions internal/pkg/changelog/changelog.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,27 +51,31 @@ func (e *Entry) append(section string, entry string) error {
type Changelog interface {
GetRepoName() string
GetRepoOwner() string
GetUnreleased() []string
GetEntries() []Entry
}

type changelog struct {
repoName string
repoOwner string
entries []Entry
repoName string
repoOwner string
unreleased []string
entries []Entry
}

type ChangelogBuilder interface {
WithSpinner(enabled bool) ChangelogBuilder
WithGitClient(client gitclient.GitClient) ChangelogBuilder
WithGithubClient(client githubclient.GitHubClient) ChangelogBuilder
WithNextVersion(nextVersion string) ChangelogBuilder
Build() (Changelog, error)
}

type changelogBuilder struct {
spinner *spinner.Spinner
github githubclient.GitHubClient
git gitclient.GitClient
tags []githubclient.Tag
spinner *spinner.Spinner
github githubclient.GitHubClient
git gitclient.GitClient
tags []githubclient.Tag
nextVersion string
}

func (builder *changelogBuilder) WithSpinner(enabled bool) ChangelogBuilder {
Expand All @@ -93,6 +97,11 @@ func (builder *changelogBuilder) WithGithubClient(client githubclient.GitHubClie
return builder
}

func (builder *changelogBuilder) WithNextVersion(nextVersion string) ChangelogBuilder {
builder.nextVersion = nextVersion
return builder
}

func (builder *changelogBuilder) Build() (Changelog, error) {
if builder.spinner != nil {
builder.spinner.Start()
Expand All @@ -116,12 +125,17 @@ func (builder *changelogBuilder) Build() (Changelog, error) {
return nil, err
}

builder.tags = tags
err = builder.setNextVersion()
if err != nil {
return nil, err
}
builder.tags = append(builder.tags, tags...)

c := &changelog{
repoName: builder.github.GetRepoName(),
repoOwner: builder.github.GetRepoOwner(),
entries: []Entry{},
repoName: builder.github.GetRepoName(),
repoOwner: builder.github.GetRepoOwner(),
unreleased: []string{},
entries: []Entry{},
}

err = builder.buildChangeLog(c)
Expand All @@ -136,6 +150,27 @@ func (builder *changelogBuilder) Build() (Changelog, error) {
}

func (builder *changelogBuilder) buildChangeLog(changelog *changelog) error {
if viper.GetBool("show_unreleased") && builder.nextVersion == "" {
builder.spinner.Suffix = " Calculating unreleased entries"

nextTag := builder.tags[0]
pullRequests, err := builder.github.GetPullRequestsBetweenDates(nextTag.Date, time.Now())
if err != nil {
return err
}

unreleased, err := builder.populateUnreleasedEntry(
nextTag.Name,
nextTag.Sha,
pullRequests,
)
if err != nil {
return fmt.Errorf("could not process pull requests: %v", err)
}

changelog.unreleased = unreleased
}

for idx, currentTag := range builder.tags {
builder.spinner.Suffix = fmt.Sprintf(" Building changelog: 🏷️ %s", currentTag.Name)
var previousTag githubclient.Tag
Expand Down Expand Up @@ -165,7 +200,7 @@ func (builder *changelogBuilder) buildChangeLog(changelog *changelog) error {
return err
}

entry, err := builder.populateEntry(
entry, err := builder.populateReleasedEntry(
currentTag.Name,
previousTag.Name,

Expand All @@ -182,7 +217,30 @@ func (builder *changelogBuilder) buildChangeLog(changelog *changelog) error {
return nil
}

func (builder *changelogBuilder) populateEntry(currentTag string, previousTag string, date time.Time, pullRequests []githubclient.PullRequest) (*Entry, error) {
func (builder *changelogBuilder) populateUnreleasedEntry(nextTag string, headSha string, pullRequests []githubclient.PullRequest) ([]string, error) {
unreleased := []string{}
excludedLabels := viper.GetStringSlice("excluded_labels")
for _, pr := range pullRequests {
if !hasExcludedLabel(excludedLabels, pr) {
line := fmt.Sprintf(
"%s [#%d](https://github.com/%s/%s/pull/%d) ([%s](https://github.com/%s))\n",
pr.Title,
pr.Number,
builder.github.GetRepoOwner(),
builder.github.GetRepoName(),
pr.Number,
pr.User,
pr.User,
)

unreleased = append(unreleased, line)
}
}

return unreleased, nil
}

func (builder *changelogBuilder) populateReleasedEntry(currentTag string, previousTag string, date time.Time, pullRequests []githubclient.PullRequest) (*Entry, error) {
entry := &Entry{
CurrentTag: currentTag,
PreviousTag: previousTag,
Expand Down Expand Up @@ -223,6 +281,29 @@ func (builder *changelogBuilder) populateEntry(currentTag string, previousTag st
return entry, nil
}

func (builder *changelogBuilder) setNextVersion() error {
if builder.nextVersion != "" {
if !utils.IsValidSemanticVersion(builder.nextVersion) {
return fmt.Errorf("'%s' is not a valid semantic version", builder.nextVersion)
}

lastCommitSha, err := builder.git.GetLastCommit()
if err != nil {
return err
}

tag := githubclient.Tag{
Name: builder.nextVersion,
Sha: lastCommitSha,
Date: time.Now(),
}

builder.tags = append(builder.tags, tag)
}

return nil
}

func (c *changelog) GetRepoName() string {
return c.repoName
}
Expand All @@ -235,6 +316,10 @@ func (c *changelog) GetEntries() []Entry {
return c.entries
}

func (c *changelog) GetUnreleased() []string {
return c.unreleased
}

func NewChangelogBuilder() ChangelogBuilder {
return &changelogBuilder{}
}
Expand Down
5 changes: 4 additions & 1 deletion internal/pkg/configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ func InitConfig() error {
}
}

SetDefaults()

if err := viper.ReadInConfig(); err != nil {
SetDefaults()
err := viper.SafeWriteConfig()
if err != nil {
return fmt.Errorf("failed to write config: %s", err)
Expand All @@ -46,4 +47,6 @@ func SetDefaults() {
viper.SetDefault("sections", sections)

viper.SetDefault("skip_entries_without_label", false)

viper.SetDefault("show_unreleased", true)
}
14 changes: 8 additions & 6 deletions internal/pkg/gitclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,10 @@ import (

type GitClient interface {
GetFirstCommit() (string, error)
GetLastCommit() (string, error)
GetDateOfHash(hash string) (time.Time, error)
}

type Tag struct {
Name string
Sha string
Date time.Time
}

type execOptions struct {
args []string
}
Expand All @@ -42,6 +37,12 @@ func (g git) GetFirstCommit() (string, error) {
})
}

func (g git) GetLastCommit() (string, error) {
return g.exec(execOptions{
args: []string{"log", "-1", "--pretty=format:%H"},
})
}

func (g git) GetDateOfHash(hash string) (time.Time, error) {
date, err := g.exec(execOptions{
args: []string{"log", "-1", "--format=%cI", hash, "--date=local"},
Expand All @@ -50,6 +51,7 @@ func (g git) GetDateOfHash(hash string) (time.Time, error) {
if err != nil {
return time.Time{}, err
}

return time.ParseInLocation(time.RFC3339, date, time.Local)
}

Expand Down
2 changes: 0 additions & 2 deletions internal/pkg/githubclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,6 @@ func (client *githubClient) GetTags() ([]Tag, error) {
return nil, fmt.Errorf("error getting tags: %w", err)
}

fmt.Println(TagQuery)

for _, node := range TagQuery.Repository.Refs.Nodes {
switch node.Target.TypeName {
case "Tag":
Expand Down
3 changes: 0 additions & 3 deletions internal/pkg/githubclient/client_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package githubclient_test

import (
"fmt"
"testing"

"github.com/chelnak/gh-changelog/internal/pkg/githubclient"
Expand Down Expand Up @@ -56,8 +55,6 @@ func Test_GetTagsReturnsASliceOfTags(t *testing.T) {

tags, err := client.GetTags()

fmt.Println(tags)

assert.NoError(t, err)

assert.Equal(t, 2, len(tags))
Expand Down
9 changes: 9 additions & 0 deletions internal/pkg/utils/utils.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package utils

import (
"github.com/Masterminds/semver/v3"
)

func SliceContainsString(s []string, str string) bool {
for _, v := range s {
if v == str {
Expand All @@ -9,3 +13,8 @@ func SliceContainsString(s []string, str string) bool {

return false
}

func IsValidSemanticVersion(version string) bool {
_, err := semver.NewVersion(version)
return err == nil
}
27 changes: 27 additions & 0 deletions internal/pkg/utils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,30 @@ func TestSliceContainsString(t *testing.T) {
})
}
}

func TestIsValidSemanticVersion(t *testing.T) {
tests := []struct {
name string
value string
want bool
}{
{
name: "valid semantic version",
value: "1.0.0",
want: true,
},
{
name: "invalid semantic version",
value: "asdasdasd1",
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := utils.IsValidSemanticVersion(tt.value); got != tt.want {
t.Errorf("IsValidSemanticVersion() = %v, want %v", got, tt.want)
}
})
}
}
Loading

0 comments on commit 55cc726

Please sign in to comment.