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: support trimming any text lines preceding the line containing the conventional commit type #416

Merged
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
10 changes: 10 additions & 0 deletions cmd/uplift/changelog.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ uplift changelog --no-stage
# Generate a changelog with multiline commit messages
uplift changelog --multiline

# Generate a changelog trimming any lines preceding the conventional commit type
uplift changelog --trim-header

# Generate a changelog with prerelease tags being skipped
uplift changelog --skip-prerelease`
)
Expand All @@ -94,6 +97,7 @@ type changelogOptions struct {
Sort string
Multiline bool
SkipPrerelease bool
TrimHeader bool
*globalOptions
}

Expand Down Expand Up @@ -136,6 +140,7 @@ func newChangelogCmd(gopts *globalOptions, out io.Writer) *changelogCommand {
f.StringVar(&chglogCmd.Opts.Sort, "sort", "", "the sort order of commits within each changelog entry")
f.BoolVar(&chglogCmd.Opts.Multiline, "multiline", false, "include multiline commit messages within changelog (skips truncation)")
f.BoolVar(&chglogCmd.Opts.SkipPrerelease, "skip-prerelease", false, "skips the creation of a changelog entry for a prerelease")
f.BoolVar(&chglogCmd.Opts.TrimHeader, "trim-header", false, "strip any lines preceding the conventional commit type in the commit message")

chglogCmd.Cmd = cmd
return chglogCmd
Expand Down Expand Up @@ -204,6 +209,11 @@ func setupChangelogContext(opts changelogOptions, out io.Writer) (*context.Conte
ctx.Changelog.SkipPrerelease = ctx.Config.Changelog.SkipPrerelease
}

ctx.Changelog.TrimHeader = opts.TrimHeader
if !ctx.Changelog.TrimHeader && ctx.Config.Changelog != nil {
ctx.Changelog.TrimHeader = ctx.Config.Changelog.TrimHeader
}

// Sort order provided as a command-line flag takes precedence
ctx.Changelog.Sort = opts.Sort
if ctx.Changelog.Sort == "" && cfg.Changelog != nil {
Expand Down
22 changes: 22 additions & 0 deletions cmd/uplift/changelog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,3 +332,25 @@ fix: 1
assert.NotContains(t, cl, "## 0.1.0-pre.2")
assert.NotContains(t, cl, "## 0.1.0-pre.1")
}

func TestChangelog_TrimHeader(t *testing.T) {
log := `>(tag: 0.1.0) feat: this is a commit
>this line that should be ignored
this line that should also be ignored
feat: second commit`
gittest.InitRepository(t, gittest.WithLog(log))

chglogCmd := newChangelogCmd(noChangesPushed(), os.Stdout)
chglogCmd.Cmd.SetArgs([]string{"--trim-header"})

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

assert.True(t, changelogExists(t))

cl := readChangelog(t)
assert.Contains(t, cl, `feat: this is a commit`)
assert.Contains(t, cl, "feat: second commit")
assert.NotContains(t, cl, "this line that should be ignored")
assert.NotContains(t, cl, "this line that should also be ignored")
}
6 changes: 6 additions & 0 deletions cmd/uplift/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ type releaseOptions struct {
Sort string
Multiline bool
SkipPrerelease bool
TrimHeader bool
*globalOptions
}

Expand Down Expand Up @@ -137,6 +138,7 @@ func newReleaseCmd(gopts *globalOptions, out io.Writer) *releaseCommand {
f.StringVar(&relCmd.Opts.Sort, "sort", "", "the sort order of commits within each changelog entry")
f.BoolVar(&relCmd.Opts.Multiline, "multiline", false, "include multiline commit messages within changelog (skips truncation)")
f.BoolVar(&relCmd.Opts.SkipPrerelease, "skip-changelog-prerelease", false, "skips the creation of a changelog entry for a prerelease")
f.BoolVar(&relCmd.Opts.TrimHeader, "trim-header", false, "strip any lines preceding the conventional commit type in the commit message")

relCmd.Cmd = cmd
return relCmd
Expand Down Expand Up @@ -209,6 +211,10 @@ func setupReleaseContext(opts releaseOptions, out io.Writer) (*context.Context,
if !ctx.Changelog.SkipPrerelease && ctx.Config.Changelog != nil {
ctx.Changelog.SkipPrerelease = ctx.Config.Changelog.SkipPrerelease
}
ctx.Changelog.TrimHeader = opts.TrimHeader
if !ctx.Changelog.TrimHeader && ctx.Config.Changelog != nil {
ctx.Changelog.TrimHeader = ctx.Config.Changelog.TrimHeader
}

// By default ensure the ci(uplift): commits are excluded also
ctx.Changelog.Exclude = append(ctx.Changelog.Exclude, "ci(uplift):")
Expand Down
22 changes: 22 additions & 0 deletions cmd/uplift/release_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,25 @@ func TestRelease_SkipChangelogPrerelease(t *testing.T) {
assert.NotContains(t, cl, "## 0.1.0-pre.2")
assert.NotContains(t, cl, "## 0.1.0-pre.1")
}

func TestRelease_TrimHeader(t *testing.T) {
log := `> feat: this is a commit
>this line that should be ignored
this line that should also be ignored
feat: second commit`
gittest.InitRepository(t, gittest.WithLog(log))

relCmd := newReleaseCmd(noChangesPushed(), os.Stdout)
relCmd.Cmd.SetArgs([]string{"--trim-header"})

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

assert.True(t, changelogExists(t))

cl := readChangelog(t)
assert.Contains(t, cl, `feat: this is a commit`)
assert.Contains(t, cl, "feat: second commit")
assert.NotContains(t, cl, "this line that should be ignored")
assert.NotContains(t, cl, "this line that should also be ignored")
}
8 changes: 8 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,11 @@ Prevent any prerelease from being included in your changelog. Upon your next rel
```sh
uplift changelog --skip-prerelease
```

## Trim Header

Trims any lines preceding the conventional commit type in the commit message

```sh
uplift changelog --skip-prerelease
```
2 changes: 2 additions & 0 deletions docs/reference/cli/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ uplift release --no-prefix
commits for the changelog
--multiline include multiline commit messages within
changelog (skips truncation)
--trim-header trims any lines preceding the conventional commit type
in the commit message
--no-prefix strip the default 'v' prefix from the next
calculated semantic version
--prerelease string append a prerelease suffix to next calculated
Expand Down
12 changes: 8 additions & 4 deletions docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ changelog:
# default behaviour of truncating a commit message to its first line
multiline: true

# Trims any lines preceding the conventional commit type in the
# commit message
trimHeader: true

# Skips generating a changelog for any prerelease. All commits from
# a prerelease will be appended to the changelog entry for the next
# release
Expand Down Expand Up @@ -243,10 +247,10 @@ git:
skipTag: true
skipBranch: false

# A list of case sensitive files that will be committed to the repo when the
# release is created. Typically these files are managed as part of the
# release process e.g. generated by `hooks`. This allows additional
# information to be included in a release such as a
# A list of case sensitive files that will be committed to the repo when the
# release is created. Typically these files are managed as part of the
# release process e.g. generated by `hooks`. This allows additional
# information to be included in a release such as a
# Software Bill Of Materials (SBOM) that is created by an external tool.
includeArtifacts:
- file.txt
Expand Down
12 changes: 11 additions & 1 deletion docs/static/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@
"description": "Include multiline commit messages within the changelog. Disables default behaviour of truncating a commit message to its first line",
"type": "boolean"
},
"trimHeader": {
"$comment": "https://upliftci.dev/reference/config#changelog",
"description": "Trims any lines preceding the conventional commit type in the commit message",
"type": "boolean"
},
"skipPrerelease": {
"$comment": "https://upliftci.dev/reference/config#changelog",
"description": "Skips generating a changelog for any prerelease. All commits from a prerelease will be appended to the changelog entry for the next release",
Expand Down Expand Up @@ -190,9 +195,14 @@
"multiline"
]
},
{
"required": [
"trimHeader"
]
},
{
"skipPrerelease": [
"multiline"
"skipPrerelease"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change is not related to this PR goal but it looked like a "typo". please confirm this is the case.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great spot. This should be:

{
  "required": [
     "skipPrerelease"
  ]
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll fix this shortly. The change looks great, and I don't want this to hold up merging this PR.

]
}
]
Expand Down
1 change: 1 addition & 0 deletions internal/config/uplift.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ type Changelog struct {
Include []string `yaml:"include" validate:"required_without_all=Sort Exclude,dive,min=1"`
Multiline bool `yaml:"multiline"`
SkipPrerelease bool `yaml:"skipPrerelease"`
TrimHeader bool `yaml:"trimHeader"`
}

// 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 @@ -92,6 +92,7 @@ type Changelog struct {
PreTag bool
Multiline bool
SkipPrerelease bool
TrimHeader bool
}

// New constructs a context that captures both runtime configuration and
Expand Down
37 changes: 33 additions & 4 deletions internal/semver/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ import (
// against a semantic version
type Increment string

type ParseOptions struct {
TrimHeader bool
}

const (
// NoIncrement represents no increment change to a semantic version
NoIncrement Increment = "None"
Expand All @@ -56,16 +60,26 @@ const (
// log against the conventional commit standards defined, @see:
// https://www.conventionalcommits.org/en/v1.0.0/
func ParseLog(log []git.LogEntry) Increment {
return ParseLogWithOptions(log, ParseOptions{TrimHeader: false})
}

func ParseLogWithOptions(log []git.LogEntry, options ParseOptions) Increment {
mode := NoIncrement
for _, entry := range log {
// Check for the existence of a conventional commit type
idx := strings.Index(entry.Message, colonSpace)
if idx == -1 {
colonSpaceIdx := strings.Index(entry.Message, colonSpace)
if colonSpaceIdx == -1 {
continue
}

leadingType := strings.ToUpper(entry.Message[:idx])
if leadingType[idx-1] == breakingBang || multilineBreaking(entry.Message) {
startIdx := 0
// Commit messages may have leading lines before the conventional commit type
if options.TrimHeader {
startIdx = FindStartIdx(entry.Message)
}

leadingType := strings.ToUpper(entry.Message[startIdx:colonSpaceIdx])
if leadingType[len(leadingType)-1] == breakingBang || multilineBreaking(entry.Message) {
return MajorIncrement
}

Expand Down Expand Up @@ -122,3 +136,18 @@ func multilineBreaking(msg string) bool {
return strings.HasPrefix(footer, "BREAKING CHANGE: ") ||
strings.HasPrefix(footer, "BREAKING-CHANGE: ")
}

func FindStartIdx(msg string) int {
colonIdx := strings.Index(msg, colonSpace)
if colonIdx == -1 {
return 0
}

trimmedMsg := msg[:colonIdx]
leadingLineBreakIdx := strings.LastIndex(trimmedMsg, "\n")
if leadingLineBreakIdx == -1 {
return 0
}

return leadingLineBreakIdx + 1
}
13 changes: 13 additions & 0 deletions internal/semver/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,16 @@ refactor: tidy up some bits of the code`,
inc := ParseLog(log)
assert.Equal(t, PatchIncrement, inc)
}

func TestParseLog_TrimHeader(t *testing.T) {
log := []git.LogEntry{
{
Message: `this line that should be ignored
this line that should also be ignored
feat: shiny new feature has been added`,
},
}

inc := ParseLogWithOptions(log, ParseOptions{TrimHeader: true})
assert.Equal(t, MinorIncrement, inc)
}
11 changes: 10 additions & 1 deletion internal/task/changelog/changelog.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ func (t Task) Run(ctx *context.Context) error {
for i := range rels {
for j := range rels[i].Changes {
msg := rels[i].Changes[j].Message
if ctx.Changelog.TrimHeader {
startIdx := semver.FindStartIdx(msg)
msg = msg[startIdx:]
}
msg = strings.ReplaceAll(msg, "\n", "\n ")
msg = strings.ReplaceAll(msg, "\n \n", "\n\n")

Expand All @@ -165,8 +169,13 @@ func (t Task) Run(ctx *context.Context) error {
for i := range rels {
for j := range rels[i].Changes {
msg := rels[i].Changes[j].Message
if ctx.Changelog.TrimHeader {
startIdx := semver.FindStartIdx(msg)
msg = msg[startIdx:]
rels[i].Changes[j].Message = msg
}
if idx := strings.Index(msg, "\n"); idx > -1 {
rels[i].Changes[j].Message = strings.TrimSpace(msg[:idx])
rels[i].Changes[j].Message = strings.TrimSpace(msg)
}
}
}
Expand Down
43 changes: 41 additions & 2 deletions internal/task/changelog/changelog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,7 @@ ci: tweak
}

func TestRun_MultilineMessages(t *testing.T) {
log := `> (tag: 1.1.0) feat: this is a multiline commmit
log := `> (tag: 1.1.0) feat: this is a multiline commit

That should be displayed across multiple lines within the changelog.
It should be formatted as expected.
Expand Down Expand Up @@ -770,7 +770,7 @@ With the correct indentation for rendering in markdown

expected := fmt.Sprintf(`## 1.1.0 - %s

- %s feat: this is a multiline commmit
- %s feat: this is a multiline commit

That should be displayed across multiple lines within the changelog.
It should be formatted as expected.
Expand Down Expand Up @@ -946,3 +946,42 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

assert.Equal(t, expected, readChangelog(t))
}

func TestRun_TrimHeader(t *testing.T) {
log := `> (tag: 1.1.0) feat: this is a commit
>this line that should be ignored
this line that should also be ignored
feat: second commit
> (tag: 1.0.0) not included in changelog`
gittest.InitRepository(t, gittest.WithLog(log))
glog := gittest.Log(t)

var buf bytes.Buffer
ctx := &context.Context{
Out: &buf,
Changelog: context.Changelog{
TrimHeader: true,
DiffOnly: true,
},
CurrentVersion: semver.Version{
Raw: "1.0.0",
},
NextVersion: semver.Version{
Raw: "1.1.0",
},
SCM: context.SCM{
Provider: context.Unrecognised,
},
}

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

expected := fmt.Sprintf(`## 1.1.0 - %s

- %s feat: this is a commit
- %s feat: second commit
`, changelogDate(t), fmt.Sprintf("`%s`", glog[0].AbbrevHash), fmt.Sprintf("`%s`", glog[1].AbbrevHash))

assert.Equal(t, expected, buf.String())
}
2 changes: 1 addition & 1 deletion internal/task/nextsemver/nextsemver.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (t Task) Run(ctx *context.Context) error {
}

// Identify any commit that will trigger the largest semantic version bump
inc := semver.ParseLog(glog.Commits)
inc := semver.ParseLogWithOptions(glog.Commits, semver.ParseOptions{TrimHeader: ctx.Changelog.TrimHeader})
if inc == semver.NoIncrement {
ctx.NoVersionChanged = true

Expand Down