Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: cherry-pick commits to include within the changelog #301

Merged
merged 3 commits into from
Jan 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion cmd/uplift/changelog.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ release tags`
type changelogOptions struct {
DiffOnly bool
Exclude []string
Include []string
All bool
Sort string
*globalOptions
Expand Down Expand Up @@ -93,7 +94,8 @@ func newChangelogCmd(gopts *globalOptions, out io.Writer) *changelogCommand {
f := cmd.Flags()
f.BoolVar(&chglogCmd.Opts.DiffOnly, "diff-only", false, "output the changelog diff only")
f.BoolVar(&chglogCmd.Opts.All, "all", false, "generate a changelog from the entire history of this repository")
f.StringSliceVar(&chglogCmd.Opts.Exclude, "exclude", []string{}, "a list of conventional commit prefixes to exclude")
f.StringSliceVar(&chglogCmd.Opts.Exclude, "exclude", []string{}, "a list of regexes for excluding conventional commits from the changelog")
f.StringSliceVar(&chglogCmd.Opts.Include, "include", []string{}, "a list of regexes to cherry-pick conventional commits for the changelog")
f.StringVar(&chglogCmd.Opts.Sort, "sort", "", "the sort order of commits within each changelog entry")

chglogCmd.Cmd = cmd
Expand Down Expand Up @@ -173,7 +175,11 @@ func setupChangelogContext(opts changelogOptions, out io.Writer) (*context.Conte
ctx.Changelog.Sort = strings.ToLower(cfg.Changelog.Sort)
}

// TODO: can this just be done with a direct append, rather than multiple statements

// Merge config and command line arguments together
ctx.Changelog.Include = opts.Include
ctx.Changelog.Include = append(ctx.Changelog.Include, ctx.Config.Changelog.Include...)
ctx.Changelog.Exclude = opts.Exclude
ctx.Changelog.Exclude = append(ctx.Changelog.Exclude, ctx.Config.Changelog.Exclude...)

Expand Down
20 changes: 19 additions & 1 deletion cmd/uplift/changelog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func TestChangelog_WithExclude(t *testing.T) {
taggedRepo(t, "2.0.0", "feat: a new feat", "fix: a new fix", "ci: a ci task", "docs: some new docs")

chglogCmd := newChangelogCmd(noChangesPushed(), os.Stdout)
chglogCmd.Cmd.SetArgs([]string{"--exclude", "ci,docs"})
chglogCmd.Cmd.SetArgs([]string{"--exclude", "^ci,^docs"})

err := chglogCmd.Cmd.Execute()
require.NoError(t, err)
Expand All @@ -102,6 +102,24 @@ func TestChangelog_WithExclude(t *testing.T) {
assert.NotContains(t, cl, "docs: some new docs")
}

func TestChangelog_WithInclude(t *testing.T) {
taggedRepo(t, "2.0.0", "feat(scope): a new feat", "fix(scope): a new fix", "ci: a ci task", "docs: some new docs")

chglogCmd := newChangelogCmd(noChangesPushed(), os.Stdout)
chglogCmd.Cmd.SetArgs([]string{"--include", "^.*\\(scope\\)"})

err := chglogCmd.Cmd.Execute()
require.NoError(t, err)

assert.True(t, changelogExists(t))

cl := readChangelog(t)
assert.Contains(t, cl, "feat(scope): a new feat")
assert.Contains(t, cl, "fix(scope): a new fix")
assert.NotContains(t, cl, "ci: a ci task")
assert.NotContains(t, cl, "docs: some new docs")
}

func TestChangelog_All(t *testing.T) {
tagRepoWith(t, []string{"0.1.0", "0.2.0", "0.3.0", "0.4.0", "0.5.0"})

Expand Down
1 change: 1 addition & 0 deletions internal/config/uplift.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ type CommitAuthor struct {
type Changelog struct {
Sort string `yaml:"sort"`
Exclude []string `yaml:"exclude"`
Include []string `yaml:"include"`
}

// Git defines configuration for how uplift interacts with git
Expand Down
1 change: 1 addition & 0 deletions internal/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ type Changelog struct {
All bool
DiffOnly bool
Exclude []string
Include []string
Sort string
PreTag bool
}
Expand Down
61 changes: 46 additions & 15 deletions internal/task/changelog/changelog.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ type release struct {
Changes []git.LogEntry
}

// Define functions for filtering commits
type commitFilter func([]git.LogEntry, *regexp.Regexp) []git.LogEntry

// Task that generates a changelog for the current repository
type Task struct{}

Expand Down Expand Up @@ -164,9 +167,17 @@ func changelogRelease(ctx *context.Context) ([]release, error) {
return []release{}, err
}

if len(ctx.Changelog.Include) > 0 {
log.Info("cherry-picking commits based on include list")
ents, err = filterCommits(ents, ctx.Changelog.Include, includeCommits)
if err != nil {
return []release{}, err
}
}

if len(ctx.Changelog.Exclude) > 0 {
log.Info("removing commits based on exclude list")
ents, err = excludeCommits(ents, ctx.Changelog.Exclude)
ents, err = filterCommits(ents, ctx.Changelog.Exclude, excludeCommits)
if err != nil {
return []release{}, err
}
Expand Down Expand Up @@ -228,9 +239,17 @@ func changelogReleases(ctx *context.Context) ([]release, error) {
return []release{}, err
}

if len(ctx.Changelog.Include) > 0 {
log.Info("cherry-picking commits based on include list")
ents, err = filterCommits(ents, ctx.Changelog.Include, includeCommits)
if err != nil {
return []release{}, err
}
}

if len(ctx.Changelog.Exclude) > 0 {
log.Info("removing commits based on exclude list")
ents, err = excludeCommits(ents, ctx.Changelog.Exclude)
ents, err = filterCommits(ents, ctx.Changelog.Exclude, excludeCommits)
if err != nil {
return []release{}, err
}
Expand Down Expand Up @@ -339,23 +358,35 @@ func reverse(ents []git.LogEntry) {
}
}

func excludeCommits(commits []git.LogEntry, excludes []string) ([]git.LogEntry, error) {
filtered := commits
for _, exclude := range excludes {
excludeRgx, err := regexp.Compile(exclude)
func filterCommits(commits []git.LogEntry, regexes []string, filter commitFilter) ([]git.LogEntry, error) {
filteredCommits := commits
for _, regex := range regexes {
rgx, err := regexp.Compile(regex)
if err != nil {
return filtered, err
return filteredCommits, err
}
filteredCommits = filter(filteredCommits, rgx)
}

// Carry out a filtering pass for each defined
filterPass := []git.LogEntry{}
for _, commit := range filtered {
if !excludeRgx.MatchString(commit.Message) {
filterPass = append(filterPass, commit)
}
return filteredCommits, nil
}

func includeCommits(commits []git.LogEntry, rgx *regexp.Regexp) []git.LogEntry {
filteredCommits := []git.LogEntry{}
for _, commit := range commits {
if rgx.MatchString(commit.Message) {
filteredCommits = append(filteredCommits, commit)
}
filtered = filterPass
}
return filteredCommits
}

return filtered, nil
func excludeCommits(commits []git.LogEntry, rgx *regexp.Regexp) []git.LogEntry {
filteredCommits := []git.LogEntry{}
for _, commit := range commits {
if !rgx.MatchString(commit.Message) {
filteredCommits = append(filteredCommits, commit)
}
}
return filteredCommits
}
147 changes: 121 additions & 26 deletions internal/task/changelog/changelog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,32 +367,6 @@ func TestRun_WithExcludes(t *testing.T) {
assert.Equal(t, expected, buf.String())
}

func TestRun_ExcludeAllEntries(t *testing.T) {
git.InitRepo(t)
git.Tag("1.0.0")
git.EmptyCommitsAndTag(t, "1.1.0", "prefix: first commit", "prefix: second commit", "prefix: third commit", "prefix: forth commit")

var buf bytes.Buffer
ctx := &context.Context{
Out: &buf,
Changelog: context.Changelog{
Exclude: []string{"prefix"},
},
CurrentVersion: semver.Version{
Raw: "1.0.0",
},
NextVersion: semver.Version{
Raw: "1.1.0",
},
}

err := Task{}.Run(ctx)
require.NoError(t, err)

assert.False(t, changelogExists(t))
assert.Equal(t, "", buf.String())
}

func TestRun_AllTags(t *testing.T) {
ih := git.InitRepo(t)
th := git.TimeBasedTagSeries(t, []string{"0.1.0", "0.2.0", "0.3.0"})
Expand Down Expand Up @@ -473,6 +447,31 @@ func TestRun_AllTagsDiffOnly(t *testing.T) {
assert.Equal(t, expected, buf.String())
}

func TestRun_ExcludeAllEntries(t *testing.T) {
git.InitRepo(t)
git.Tag("1.0.0")
git.EmptyCommitsAndTag(t, "1.1.0", "prefix: first commit", "prefix: second commit", "prefix: third commit", "prefix: forth commit")

var buf bytes.Buffer
ctx := &context.Context{
Out: &buf,
Changelog: context.Changelog{
Exclude: []string{"prefix"},
},
CurrentVersion: semver.Version{
Raw: "1.0.0",
},
NextVersion: semver.Version{
Raw: "1.1.0",
},
}

err := Task{}.Run(ctx)
require.NoError(t, err)

assert.Equal(t, "", buf.String())
}

func TestRun_AllWithExcludes(t *testing.T) {
ih := git.InitRepo(t)
th := git.TimeBasedTagSeries(t, []string{"0.1.0", "0.2.0"})
Expand Down Expand Up @@ -590,3 +589,99 @@ func TestRun_IdentifiedSCM(t *testing.T) {

assert.Contains(t, buf.String(), expected)
}

func TestRun_WithIncludes(t *testing.T) {
git.InitRepo(t)
git.Tag("1.0.0")
git.EmptyCommitsAndTag(t, "1.1.0", "ci: tweak", "fix(scope1): a fix", "feat(scope1): a feature", "fix(scope2): another fix")

var buf bytes.Buffer
ctx := &context.Context{
Out: &buf,
Changelog: context.Changelog{
DiffOnly: true,
Include: []string{`^.*\(scope1\)`},
},
CurrentVersion: semver.Version{
Raw: "1.0.0",
},
NextVersion: semver.Version{
Raw: "1.1.0",
},
SCM: context.SCM{
Provider: git.Unrecognised,
},
}

err := Task{}.Run(ctx)
require.NoError(t, err)

actual := buf.String()
assert.Contains(t, actual, "fix(scope1): a fix")
assert.Contains(t, actual, "feat(scope1): a feature")
assert.NotContains(t, actual, "ci: tweak")
assert.NotContains(t, actual, "fix(scope2): another fix")
}

func TestRun_AllWithIncludes(t *testing.T) {
git.InitRepo(t)
git.TimeBasedTagSeries(t, []string{"0.1.0", "0.2.0"})
git.EmptyCommitsAndTag(t, "0.3.0", "feat: another feature", "ci: tweak", "docs: update docs")

var buf bytes.Buffer
ctx := &context.Context{
Out: &buf,
Changelog: context.Changelog{
All: true,
DiffOnly: true,
Include: []string{"^feat:"},
},
SCM: context.SCM{
Provider: git.Unrecognised,
},
}

err := Task{}.Run(ctx)
require.NoError(t, err)

actual := buf.String()
assert.Contains(t, actual, "feat: another feature")
assert.Contains(t, actual, "feat: 1")
assert.Contains(t, actual, "feat: 2")
assert.NotContains(t, actual, "ci: tweak")
assert.NotContains(t, actual, "docs: update docs")
}

func TestRun_CombinedIncludeAndExclude(t *testing.T) {
git.InitRepo(t)
git.Tag("1.0.0")
git.EmptyCommitsAndTag(t, "1.1.0", "ci: tweak", "fix(scope1): a fix", "feat(scope1): a feature", "fix(scope2): another fix")

var buf bytes.Buffer
ctx := &context.Context{
Out: &buf,
Changelog: context.Changelog{
DiffOnly: true,
Include: []string{`^.*\(scope1\)`},
Exclude: []string{`^fix`},
},
CurrentVersion: semver.Version{
Raw: "1.0.0",
},
NextVersion: semver.Version{
Raw: "1.1.0",
},
SCM: context.SCM{
Provider: git.Unrecognised,
},
}

err := Task{}.Run(ctx)
require.NoError(t, err)

actual := buf.String()
assert.Contains(t, actual, "feat(scope1): a feature")
assert.NotContains(t, actual, "ci: tweak")
assert.NotContains(t, actual, "fix(scope1): a fix")
assert.NotContains(t, actual, "fix(scope2): another fix")
}