Repository: gembaadvantage/uplift (go)

Issue:
[Feature]: Support multiline commit messages within changelog
### Describe your feature

The default behaviour of `uplift` is only to include the first line of a commit message within the changelog. Important information within the commit could be omitted as a result. Allow the entire contents of the commit to be included.

### Your potential solution

Include a new configuration setting and flag that allows a changelog to include the entire setting.

### Any additional information?

_No response_

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct

Pull request:
feat: support multiline commit messages within changelog
closes #372 

Patch:
diff --git a/.gitignore b/.gitignore
index 33401c2..dc7a887 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
 *.dll
 *.so
 *.dylib
+/uplift
 
 # Test binary, built with `go test -c`
 *.test
diff --git a/cmd/uplift/changelog.go b/cmd/uplift/changelog.go
index 10bdd09..7af0977 100644
--- a/cmd/uplift/changelog.go
+++ b/cmd/uplift/changelog.go
@@ -77,15 +77,19 @@ uplift changelog --include "^.*\(scope\)"
 
 # Generate the next changelog entry but do not stage or push any changes
 # back to the git remote
-uplift changelog --no-stage`
+uplift changelog --no-stage
+
+# Generate a changelog with multiline commit messages
+uplift changelog --multiline`
 )
 
 type changelogOptions struct {
-	DiffOnly bool
-	Exclude  []string
-	Include  []string
-	All      bool
-	Sort     string
+	DiffOnly  bool
+	Exclude   []string
+	Include   []string
+	All       bool
+	Sort      string
+	Multiline bool
 	*globalOptions
 }
 
@@ -126,6 +130,7 @@ func newChangelogCmd(gopts *globalOptions, out io.Writer) *changelogCommand {
 	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")
+	f.BoolVar(&chglogCmd.Opts.Multiline, "multiline", false, "include multiline commit messages within changelog (skips truncation)")
 
 	chglogCmd.Cmd = cmd
 	return chglogCmd
@@ -185,6 +190,10 @@ func setupChangelogContext(opts changelogOptions, out io.Writer) (*context.Conte
 	ctx.NoStage = opts.NoStage
 	ctx.Changelog.DiffOnly = opts.DiffOnly
 	ctx.Changelog.All = opts.All
+	ctx.Changelog.Multiline = opts.Multiline
+	if !ctx.Changelog.Multiline && ctx.Config.Changelog != nil {
+		ctx.Changelog.Multiline = ctx.Config.Changelog.Multiline
+	}
 
 	// Sort order provided as a command-line flag takes precedence
 	ctx.Changelog.Sort = opts.Sort
diff --git a/cmd/uplift/release.go b/cmd/uplift/release.go
index fa29586..4a79e63 100644
--- a/cmd/uplift/release.go
+++ b/cmd/uplift/release.go
@@ -92,6 +92,7 @@ type releaseOptions struct {
 	Exclude       []string
 	Include       []string
 	Sort          string
+	Multiline     bool
 	*globalOptions
 }
 
@@ -133,6 +134,7 @@ func newReleaseCmd(gopts *globalOptions, out io.Writer) *releaseCommand {
 	f.StringSliceVar(&relCmd.Opts.Exclude, "exclude", []string{}, "a list of regexes for excluding conventional commits from the changelog")
 	f.StringSliceVar(&relCmd.Opts.Include, "include", []string{}, "a list of regexes to cherry-pick conventional commits for the changelog")
 	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)")
 
 	relCmd.Cmd = cmd
 	return relCmd
@@ -191,6 +193,10 @@ func setupReleaseContext(opts releaseOptions, out io.Writer) (*context.Context,
 	ctx.Changelog.PreTag = true
 
 	// Merge config and command line arguments together
+	ctx.Changelog.Multiline = opts.Multiline
+	if !ctx.Changelog.Multiline && ctx.Config.Changelog != nil {
+		ctx.Changelog.Multiline = ctx.Config.Changelog.Multiline
+	}
 	ctx.Changelog.Include = opts.Include
 	ctx.Changelog.Exclude = opts.Exclude
 	if ctx.Config.Changelog != nil {
diff --git a/docs/changelog.md b/docs/changelog.md
index 0a74c4d..e6e983d 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -40,10 +40,16 @@ uplift changelog --diff-only
 
 ## Migrate an Existing Repository
 
-:octicons-beaker-24: Experimental
-
 If your repository does not contain a `CHANGELOG.md` file, you can generate one that spans its entire history. A word of warning, this does require a tagging structure to be in place.
 
 ```sh
 uplift changelog --all
 ```
+
+## Supporting Multiline Commits
+
+You can configure `uplift` to include multiline commit messages within your changelog by changing its default behaviour to truncate them to a single line.
+
+```sh
+uplift changelog --multiline
+```
diff --git a/docs/reference/cli/changelog.md b/docs/reference/cli/changelog.md
index bcc981c..821651f 100644
--- a/docs/reference/cli/changelog.md
+++ b/docs/reference/cli/changelog.md
@@ -45,6 +45,9 @@ uplift changelog --include "^.*\(scope\)"
 # Generate the next changelog entry but do not stage or push any changes
 # back to the git remote
 uplift changelog --no-stage
+
+# Generate a changelog with multiline commit messages
+uplift changelog --multiline
 ```
 
 ## Flags
@@ -58,6 +61,8 @@ uplift changelog --no-stage
 -h, --help              help for changelog
     --include strings   a list of regexes to cherry-pick conventional commits
                         for the changelog
+    --multiline         include multiline commit messages within changelog
+                        (skips truncation)
     --sort string       the sort order of commits within each changelog entry
 ```
 
diff --git a/docs/reference/cli/release.md b/docs/reference/cli/release.md
index e31d10b..98a5d9c 100644
--- a/docs/reference/cli/release.md
+++ b/docs/reference/cli/release.md
@@ -50,6 +50,8 @@ uplift release --no-prefix
 -h, --help                help for release
     --include strings     a list of regexes to cherry-pick conventional commits
                           for the changelog
+    --multiline           include multiline commit messages within changelog
+                          (skips truncation)
     --no-prefix           strip the default 'v' prefix from the next calculated
                           semantic version
     --prerelease string   append a prerelease suffix to next calculated semantic
diff --git a/docs/reference/config.md b/docs/reference/config.md
index 520987f..93f5bf7 100644
--- a/docs/reference/config.md
+++ b/docs/reference/config.md
@@ -105,6 +105,10 @@ changelog:
   # commits that are to be included
   include:
     - '^.*\(scope\)'
+
+  # Include multiline commit messages within the changelog. Disables
+  # default behaviour of truncating a commit message to its first line
+  multiline: true
 ```
 
 ## commitAuthor
diff --git a/docs/static/schema.json b/docs/static/schema.json
index 0b19a70..17a19cd 100644
--- a/docs/static/schema.json
+++ b/docs/static/schema.json
@@ -33,7 +33,9 @@
       },
       "type": "object",
       "additionalProperties": false,
-      "required": ["file"],
+      "required": [
+        "file"
+      ],
       "not": {
         "properties": {
           "regex": {
@@ -67,7 +69,9 @@
       },
       "type": "object",
       "additionalProperties": false,
-      "required": ["pattern"]
+      "required": [
+        "pattern"
+      ]
     },
     "JSONBump": {
       "properties": {
@@ -85,7 +89,9 @@
       },
       "type": "object",
       "additionalProperties": false,
-      "required": ["path"]
+      "required": [
+        "path"
+      ]
     },
     "CommitAuthor": {
       "properties": {
@@ -106,10 +112,14 @@
       "additionalProperties": false,
       "anyOf": [
         {
-          "required": ["name"]
+          "required": [
+            "name"
+          ]
         },
         {
-          "required": ["email"]
+          "required": [
+            "email"
+          ]
         }
       ]
     },
@@ -119,7 +129,12 @@
           "$comment": "https://upliftci.dev/reference/config#changelog",
           "description": "Change the sort order of the commits within each changelog entry. Supported values are [asc, desc, ASC or DESC]. Defaults to desc (descending order) to mirror the default behaviour of 'git log'",
           "type": "string",
-          "enum": ["asc", "desc", "ASC", "DESC"]
+          "enum": [
+            "asc",
+            "desc",
+            "ASC",
+            "DESC"
+          ]
         },
         "exclude": {
           "$comment": "https://upliftci.dev/reference/config#changelog",
@@ -140,19 +155,35 @@
           },
           "type": "array",
           "minItems": 1
+        },
+        "multiline": {
+          "$comment": "https://upliftci.dev/reference/config#changelog",
+          "description": "Include multiline commit messages within the changelog. Disables default behaviour of truncating a commit message to its first line",
+          "type": "boolean"
         }
       },
       "type": "object",
       "additionalProperties": false,
       "anyOf": [
         {
-          "required": ["sort"]
+          "required": [
+            "sort"
+          ]
         },
         {
-          "required": ["exclude"]
+          "required": [
+            "exclude"
+          ]
         },
         {
-          "required": ["include"]
+          "required": [
+            "include"
+          ]
+        },
+        {
+          "required": [
+            "multiline"
+          ]
         }
       ]
     },
@@ -182,13 +213,19 @@
       "additionalProperties": false,
       "anyOf": [
         {
-          "required": ["ignoreDetached"]
+          "required": [
+            "ignoreDetached"
+          ]
         },
         {
-          "required": ["ignoreShallow"]
+          "required": [
+            "ignoreShallow"
+          ]
         },
         {
-          "required": ["pushOptions"]
+          "required": [
+            "pushOptions"
+          ]
         }
       ]
     },
@@ -220,7 +257,9 @@
           },
           "type": "object",
           "additionalProperties": false,
-          "required": ["option"]
+          "required": [
+            "option"
+          ]
         }
       ]
     },
@@ -235,7 +274,9 @@
       },
       "type": "object",
       "additionalProperties": false,
-      "required": ["url"]
+      "required": [
+        "url"
+      ]
     },
     "GitHub": {
       "properties": {
@@ -248,7 +289,9 @@
       },
       "type": "object",
       "additionalProperties": false,
-      "required": ["url"]
+      "required": [
+        "url"
+      ]
     },
     "GitLab": {
       "properties": {
@@ -261,7 +304,9 @@
       },
       "type": "object",
       "additionalProperties": false,
-      "required": ["url"]
+      "required": [
+        "url"
+      ]
     },
     "Hooks": {
       "properties": {
@@ -350,28 +395,44 @@
       "additionalProperties": false,
       "anyOf": [
         {
-          "required": ["before"]
+          "required": [
+            "before"
+          ]
         },
         {
-          "required": ["beforeBump"]
+          "required": [
+            "beforeBump"
+          ]
         },
         {
-          "required": ["beforeTag"]
+          "required": [
+            "beforeTag"
+          ]
         },
         {
-          "required": ["beforeChangelog"]
+          "required": [
+            "beforeChangelog"
+          ]
         },
         {
-          "required": ["after"]
+          "required": [
+            "after"
+          ]
         },
         {
-          "required": ["afterBump"]
+          "required": [
+            "afterBump"
+          ]
         },
         {
-          "required": ["afterTag"]
+          "required": [
+            "afterTag"
+          ]
         },
         {
-          "required": ["afterChangelog"]
+          "required": [
+            "afterChangelog"
+          ]
         }
       ]
     }
@@ -441,19 +502,29 @@
     {
       "oneOf": [
         {
-          "required": ["gitea"]
+          "required": [
+            "gitea"
+          ]
         },
         {
-          "required": ["github"]
+          "required": [
+            "github"
+          ]
         },
         {
-          "required": ["gitlab"]
+          "required": [
+            "gitlab"
+          ]
         }
       ]
     },
     {
       "not": {
-        "required": ["gitea", "github", "gitlab"]
+        "required": [
+          "gitea",
+          "github",
+          "gitlab"
+        ]
       }
     }
   ]
diff --git a/internal/config/uplift.go b/internal/config/uplift.go
index 11e1db4..22bd106 100644
--- a/internal/config/uplift.go
+++ b/internal/config/uplift.go
@@ -82,9 +82,10 @@ type CommitAuthor struct {
 // Changelog defines configuration for generating a changelog of the latest
 // semantic version based release
 type Changelog struct {
-	Sort    string   `yaml:"sort" validate:"required_without_all=Exclude Include,oneof=asc desc ASC DESC"`
-	Exclude []string `yaml:"exclude" validate:"required_without_all=Sort Include,dive,min=1"`
-	Include []string `yaml:"include" validate:"required_without_all=Sort Exclude,dive,min=1"`
+	Sort      string   `yaml:"sort" validate:"required_without_all=Exclude Include,oneof=asc desc ASC DESC"`
+	Exclude   []string `yaml:"exclude" validate:"required_without_all=Sort Include,dive,min=1"`
+	Include   []string `yaml:"include" validate:"required_without_all=Sort Exclude,dive,min=1"`
+	Multiline bool     `yaml:"multiline"`
 }
 
 // Git defines configuration for how uplift interacts with git
diff --git a/internal/context/context.go b/internal/context/context.go
index 9ac39fb..777f1b1 100644
--- a/internal/context/context.go
+++ b/internal/context/context.go
@@ -83,12 +83,13 @@ type SCM struct {
 // Changelog provides details about how the changelog should be managed
 // for the current repository
 type Changelog struct {
-	All      bool
-	DiffOnly bool
-	Exclude  []string
-	Include  []string
-	Sort     string
-	PreTag   bool
+	All       bool
+	DiffOnly  bool
+	Exclude   []string
+	Include   []string
+	Sort      string
+	PreTag    bool
+	Multiline bool
 }
 
 // New constructs a context that captures both runtime configuration and
diff --git a/internal/task/changelog/changelog.go b/internal/task/changelog/changelog.go
index 963f97f..1830d1a 100644
--- a/internal/task/changelog/changelog.go
+++ b/internal/task/changelog/changelog.go
@@ -140,6 +140,29 @@ func (t Task) Run(ctx *context.Context) error {
 		return nil
 	}
 
+	if ctx.Changelog.Multiline {
+		log.Info("formatting multiline messages for changelog")
+		for i := range rels {
+			for j := range rels[i].Changes {
+				msg := rels[i].Changes[j].Message
+				msg = strings.ReplaceAll(msg, "\n", "\n  ")
+				msg = strings.ReplaceAll(msg, "\n  \n", "\n\n")
+
+				rels[i].Changes[j].Message = msg
+			}
+		}
+	} else {
+		log.Info("trim all commit messages to a single line")
+		for i := range rels {
+			for j := range rels[i].Changes {
+				msg := rels[i].Changes[j].Message
+				if idx := strings.Index(msg, "\n"); idx > -1 {
+					rels[i].Changes[j].Message = strings.TrimSpace(msg[:idx])
+				}
+			}
+		}
+	}
+
 	if ctx.Changelog.DiffOnly {
 		diff, err := diffChangelog(rels)
 		if err != nil {


Test patch:
diff --git a/cmd/uplift/bump_test.go b/cmd/uplift/bump_test.go
index 1f42449..adbe8b4 100644
--- a/cmd/uplift/bump_test.go
+++ b/cmd/uplift/bump_test.go
@@ -49,17 +49,10 @@ docs: update docs
 feat: a new feature
 fix: a bug fix
 (tag: 0.1.0) feat: this was the last feature`
-	gittest.InitRepository(t, gittest.WithLog(log))
-
-	// TODO:
-	// gittest.StagedFile(t, "", "") > TempFile and StageFile under the covers
-	// gittest.WithCommittedFiles("", "", "", "") > file is automatically committed at the end
-
-	gittest.TempFile(t, "test.txt", bumpFile)
-	gittest.StageFile(t, "test.txt")
-	gittest.TempFile(t, ".uplift.yml", bumpConfig)
-	gittest.StageFile(t, ".uplift.yml")
-	gittest.Commit(t, "chore: added files")
+	gittest.InitRepository(t,
+		gittest.WithLog(log),
+		gittest.WithCommittedFiles("test.txt", ".uplift.yml"),
+		gittest.WithFileContent("test.txt", bumpFile, ".uplift.yml", bumpConfig))
 
 	bmpCmd := newBumpCmd(noChangesPushed(), os.Stdout)
 
@@ -77,12 +70,10 @@ func TestBump_PrereleaseFlag(t *testing.T) {
 fix: fix bug
 feat!: breaking change
 feat: this is a new feature`
-	gittest.InitRepository(t, gittest.WithLog(log))
-	gittest.TempFile(t, "test.txt", bumpFile)
-	gittest.StageFile(t, "test.txt")
-	gittest.TempFile(t, ".uplift.yml", bumpConfig)
-	gittest.StageFile(t, ".uplift.yml")
-	gittest.Commit(t, "chore: added files")
+	gittest.InitRepository(t,
+		gittest.WithLog(log),
+		gittest.WithCommittedFiles("test.txt", ".uplift.yml"),
+		gittest.WithFileContent("test.txt", bumpFile, ".uplift.yml", bumpConfig))
 
 	bmpCmd := newBumpCmd(&globalOptions{}, os.Stdout)
 	bmpCmd.Cmd.SetArgs([]string{"--prerelease", "-beta.1+12345"})
diff --git a/cmd/uplift/changelog_test.go b/cmd/uplift/changelog_test.go
index 47b818c..78e26fc 100644
--- a/cmd/uplift/changelog_test.go
+++ b/cmd/uplift/changelog_test.go
@@ -283,3 +283,31 @@ func TestChangelog_Hooks(t *testing.T) {
 	assert.FileExists(t, AfterChangelogFile)
 	assert.FileExists(t, AfterFile)
 }
+
+func TestChangelog_WithMultiline(t *testing.T) {
+	log := `> (tag: 2.0.0) feat: this is a multiline commit
+The entire contents of this commit should exist in the changelog.
+
+Multiline formatting should be correct for rendering in markdown
+> fix: this is a bug fix
+> docs: update documentation
+this now includes code examples`
+	gittest.InitRepository(t, gittest.WithLog(log))
+
+	chglogCmd := newChangelogCmd(noChangesPushed(), os.Stdout)
+	chglogCmd.Cmd.SetArgs([]string{"--multiline"})
+
+	err := chglogCmd.Cmd.Execute()
+	require.NoError(t, err)
+
+	assert.True(t, changelogExists(t))
+
+	cl := readChangelog(t)
+	assert.Contains(t, cl, `feat: this is a multiline commit
+  The entire contents of this commit should exist in the changelog.
+
+  Multiline formatting should be correct for rendering in markdown`)
+	assert.Contains(t, cl, "fix: this is a bug fix")
+	assert.Contains(t, cl, `docs: update documentation
+  this now includes code examples`)
+}
diff --git a/cmd/uplift/release_test.go b/cmd/uplift/release_test.go
index 3566a4f..37e1752 100644
--- a/cmd/uplift/release_test.go
+++ b/cmd/uplift/release_test.go
@@ -36,13 +36,10 @@ func TestRelease(t *testing.T) {
 fix: bug fix
 docs: update docs
 ci: update pipeline`
-	gittest.InitRepository(t, gittest.WithLog(log))
-
-	gittest.TempFile(t, "test.txt", bumpFile)
-	gittest.StageFile(t, "test.txt")
-	gittest.TempFile(t, ".uplift.yml", bumpConfig)
-	gittest.StageFile(t, ".uplift.yml")
-	gittest.Commit(t, "chore: added files")
+	gittest.InitRepository(t,
+		gittest.WithLog(log),
+		gittest.WithCommittedFiles("test.txt", ".uplift.yml"),
+		gittest.WithFileContent("test.txt", bumpFile, ".uplift.yml", bumpConfig))
 
 	relCmd := newReleaseCmd(noChangesPushed(), os.Stdout)
 
@@ -120,13 +117,10 @@ func TestRelease_PrereleaseFlag(t *testing.T) {
 	log := `refactor: make changes
 feat: new feature
 docs: update docs`
-	gittest.InitRepository(t, gittest.WithLog(log))
-
-	gittest.TempFile(t, "test.txt", bumpFile)
-	gittest.StageFile(t, "test.txt")
-	gittest.TempFile(t, ".uplift.yml", bumpConfig)
-	gittest.StageFile(t, ".uplift.yml")
-	gittest.Commit(t, "chore: added files")
+	gittest.InitRepository(t,
+		gittest.WithLog(log),
+		gittest.WithCommittedFiles("test.txt", ".uplift.yml"),
+		gittest.WithFileContent("test.txt", bumpFile, ".uplift.yml", bumpConfig))
 
 	relCmd := newReleaseCmd(noChangesPushed(), os.Stdout)
 	relCmd.Cmd.SetArgs([]string{"--prerelease", "-beta.1+12345"})
@@ -166,13 +160,10 @@ func TestRelease_SkipBumps(t *testing.T) {
 fix: bug fix
 ci: updated workflow
 (tag: 1.0.0) feat: first feature`
-	gittest.InitRepository(t, gittest.WithLog(log))
-
-	gittest.TempFile(t, "test.txt", bumpFile)
-	gittest.StageFile(t, "test.txt")
-	gittest.TempFile(t, ".uplift.yml", bumpConfig)
-	gittest.StageFile(t, ".uplift.yml")
-	gittest.Commit(t, "chore: added files")
+	gittest.InitRepository(t,
+		gittest.WithLog(log),
+		gittest.WithCommittedFiles("test.txt", ".uplift.yml"),
+		gittest.WithFileContent("test.txt", bumpFile, ".uplift.yml", bumpConfig))
 
 	relCmd := newReleaseCmd(noChangesPushed(), os.Stdout)
 	relCmd.Cmd.SetArgs([]string{"--skip-bumps"})
@@ -270,3 +261,31 @@ feat: a new feat`
 	assert.NotContains(t, cl, "ci: a ci task")
 	assert.NotContains(t, cl, "docs: some new docs")
 }
+
+func TestRelease_WithMultiline(t *testing.T) {
+	log := `> feat: this is a multiline commit
+The entire contents of this commit should exist in the changelog.
+
+Multiline formatting should be correct for rendering in markdown
+> fix: this is a bug fix
+> docs: update documentation
+this now includes code examples`
+	gittest.InitRepository(t, gittest.WithLog(log))
+
+	relCmd := newReleaseCmd(noChangesPushed(), os.Stdout)
+	relCmd.Cmd.SetArgs([]string{"--multiline"})
+
+	err := relCmd.Cmd.Execute()
+	require.NoError(t, err)
+
+	assert.True(t, changelogExists(t))
+
+	cl := readChangelog(t)
+	assert.Contains(t, cl, `feat: this is a multiline commit
+  The entire contents of this commit should exist in the changelog.
+
+  Multiline formatting should be correct for rendering in markdown`)
+	assert.Contains(t, cl, "fix: this is a bug fix")
+	assert.Contains(t, cl, `docs: update documentation
+  this now includes code examples`)
+}
diff --git a/internal/task/changelog/changelog_test.go b/internal/task/changelog/changelog_test.go
index 2858f5e..4940eae 100644
--- a/internal/task/changelog/changelog_test.go
+++ b/internal/task/changelog/changelog_test.go
@@ -723,3 +723,50 @@ ci: tweak
 	assert.NotContains(t, actual, "ci: tweak")
 	assert.NotContains(t, actual, "fix(scope1): a fix")
 }
+
+func TestRun_MultilineMessages(t *testing.T) {
+	log := `> (tag: 1.1.0) feat: this is a multiline commmit
+
+That should be displayed across multiple lines within the changelog.
+It should be formatted as expected.
+
+With the correct indentation for rendering in markdown
+> feat: this is a single line commit that remains unchanged
+> (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{
+			DiffOnly:  true,
+			Multiline: 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 multiline commmit
+
+  That should be displayed across multiple lines within the changelog.
+  It should be formatted as expected.
+
+  With the correct indentation for rendering in markdown
+- %s feat: this is a single line commit that remains unchanged
+`, changelogDate(t), fmt.Sprintf("`%s`", glog[0].AbbrevHash), fmt.Sprintf("`%s`", glog[1].AbbrevHash))
+
+	assert.Equal(t, expected, buf.String())
+}