mirror of
https://github.com/apricote/releaser-pleaser.git
synced 2026-01-13 21:21:03 +00:00
refactor: move changelog file to updater interface (#15)
This commit is contained in:
parent
47de2f97bc
commit
f0eed8cc56
5 changed files with 79 additions and 151 deletions
62
changelog.go
62
changelog.go
|
|
@ -3,14 +3,8 @@ package rp
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -20,8 +14,6 @@ const (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
changelogTemplate *template.Template
|
changelogTemplate *template.Template
|
||||||
|
|
||||||
headerRegex = regexp.MustCompile(`^# Changelog\n`)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed changelog.md.tpl
|
//go:embed changelog.md.tpl
|
||||||
|
|
@ -35,60 +27,6 @@ func init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateChangelogFile(wt *git.Worktree, newEntry string) error {
|
|
||||||
file, err := wt.Filesystem.OpenFile(ChangelogFile, os.O_RDWR|os.O_CREATE, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
content, err := io.ReadAll(file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
headerIndex := headerRegex.FindIndex(content)
|
|
||||||
if headerIndex == nil && len(content) != 0 {
|
|
||||||
return fmt.Errorf("unexpected format of CHANGELOG.md, header does not match")
|
|
||||||
}
|
|
||||||
if headerIndex != nil {
|
|
||||||
// Remove the header from the content
|
|
||||||
content = content[headerIndex[1]:]
|
|
||||||
}
|
|
||||||
|
|
||||||
err = file.Truncate(0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = file.Seek(0, io.SeekStart)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = file.Write([]byte(ChangelogHeader + "\n\n" + newEntry))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = file.Write(content)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close file to make sure it is written to disk.
|
|
||||||
err = file.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = wt.Add(ChangelogFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewChangelogEntry(commits []AnalyzedCommit, version, link, prefix, suffix string) (string, error) {
|
func NewChangelogEntry(commits []AnalyzedCommit, version, link, prefix, suffix string) (string, error) {
|
||||||
features := make([]AnalyzedCommit, 0)
|
features := make([]AnalyzedCommit, 0)
|
||||||
fixes := make([]AnalyzedCommit, 0)
|
fixes := make([]AnalyzedCommit, 0)
|
||||||
|
|
|
||||||
|
|
@ -1,99 +1,15 @@
|
||||||
package rp
|
package rp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/apricote/releaser-pleaser/internal/testutils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func ptr[T any](input T) *T {
|
func ptr[T any](input T) *T {
|
||||||
return &input
|
return &input
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateChangelogFile(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
repoFn testutils.Repo
|
|
||||||
entry string
|
|
||||||
expectedContent string
|
|
||||||
wantErr assert.ErrorAssertionFunc
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "empty repo",
|
|
||||||
repoFn: testutils.WithTestRepo(),
|
|
||||||
entry: "## v1.0.0\n",
|
|
||||||
expectedContent: "# Changelog\n\n## v1.0.0\n",
|
|
||||||
wantErr: assert.NoError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "repo with well-formatted changelog",
|
|
||||||
repoFn: testutils.WithTestRepo(testutils.WithCommit("feat: add changelog", testutils.WithFile(ChangelogFile, `# Changelog
|
|
||||||
|
|
||||||
## v0.0.1
|
|
||||||
|
|
||||||
- Bazzle
|
|
||||||
|
|
||||||
## v0.1.0
|
|
||||||
|
|
||||||
### Bazuuum
|
|
||||||
`))),
|
|
||||||
entry: "## v1.0.0\n\n- Version 1, juhu.\n",
|
|
||||||
expectedContent: `# Changelog
|
|
||||||
|
|
||||||
## v1.0.0
|
|
||||||
|
|
||||||
- Version 1, juhu.
|
|
||||||
|
|
||||||
## v0.0.1
|
|
||||||
|
|
||||||
- Bazzle
|
|
||||||
|
|
||||||
## v0.1.0
|
|
||||||
|
|
||||||
### Bazuuum
|
|
||||||
`,
|
|
||||||
wantErr: assert.NoError,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
repo := tt.repoFn(t)
|
|
||||||
wt, err := repo.Worktree()
|
|
||||||
require.NoError(t, err, "failed to get worktree")
|
|
||||||
|
|
||||||
err = UpdateChangelogFile(wt, tt.entry)
|
|
||||||
if !tt.wantErr(t, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wtStatus, err := wt.Status()
|
|
||||||
require.NoError(t, err, "failed to get worktree status")
|
|
||||||
|
|
||||||
assert.Len(t, wtStatus, 1, "worktree status does not have the expected entry number")
|
|
||||||
|
|
||||||
changelogFileStatus := wtStatus.File(ChangelogFile)
|
|
||||||
|
|
||||||
assert.Equal(t, git.Unmodified, changelogFileStatus.Worktree, "unexpected file status in worktree")
|
|
||||||
assert.Equal(t, git.Added, changelogFileStatus.Staging, "unexpected file status in staging")
|
|
||||||
|
|
||||||
changelogFile, err := wt.Filesystem.Open(ChangelogFile)
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer changelogFile.Close()
|
|
||||||
|
|
||||||
changelogFileContent, err := io.ReadAll(changelogFile)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, tt.expectedContent, string(changelogFileContent))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_NewChangelogEntry(t *testing.T) {
|
func Test_NewChangelogEntry(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
analyzedCommits []AnalyzedCommit
|
analyzedCommits []AnalyzedCommit
|
||||||
|
|
|
||||||
|
|
@ -250,11 +250,6 @@ func (rp *ReleaserPleaser) runReconcileReleasePR(ctx context.Context) error {
|
||||||
// Info for updaters
|
// Info for updaters
|
||||||
info := ReleaseInfo{Version: nextVersion, ChangelogEntry: changelogEntry}
|
info := ReleaseInfo{Version: nextVersion, ChangelogEntry: changelogEntry}
|
||||||
|
|
||||||
err = UpdateChangelogFile(worktree, changelogEntry)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to update changelog file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
updateFile := func(path string, updaters []Updater) error {
|
updateFile := func(path string, updaters []Updater) error {
|
||||||
file, err := worktree.Filesystem.OpenFile(path, os.O_RDWR, 0)
|
file, err := worktree.Filesystem.OpenFile(path, os.O_RDWR, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -297,6 +292,11 @@ func (rp *ReleaserPleaser) runReconcileReleasePR(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = updateFile(ChangelogFile, []Updater{&ChangelogUpdater{}})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update changelog file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
for _, path := range rp.extraFiles {
|
for _, path := range rp.extraFiles {
|
||||||
_, err = worktree.Filesystem.Stat(path)
|
_, err = worktree.Filesystem.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
19
updater.go
19
updater.go
|
|
@ -1,12 +1,14 @@
|
||||||
package rp
|
package rp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
GenericUpdaterSemVerRegex = regexp.MustCompile(`\d+\.\d+\.\d+(-[\w.]+)?(.*x-releaser-pleaser-version)`)
|
GenericUpdaterSemVerRegex = regexp.MustCompile(`\d+\.\d+\.\d+(-[\w.]+)?(.*x-releaser-pleaser-version)`)
|
||||||
|
ChangelogUpdaterHeaderRegex = regexp.MustCompile(`^# Changelog\n`)
|
||||||
)
|
)
|
||||||
|
|
||||||
type ReleaseInfo struct {
|
type ReleaseInfo struct {
|
||||||
|
|
@ -26,3 +28,20 @@ func (u *GenericUpdater) UpdateContent(content string, info ReleaseInfo) (string
|
||||||
|
|
||||||
return GenericUpdaterSemVerRegex.ReplaceAllString(content, version+"${2}"), nil
|
return GenericUpdaterSemVerRegex.ReplaceAllString(content, version+"${2}"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChangelogUpdater struct{}
|
||||||
|
|
||||||
|
func (u *ChangelogUpdater) UpdateContent(content string, info ReleaseInfo) (string, error) {
|
||||||
|
headerIndex := ChangelogUpdaterHeaderRegex.FindStringIndex(content)
|
||||||
|
if headerIndex == nil && len(content) != 0 {
|
||||||
|
return "", fmt.Errorf("unexpected format of CHANGELOG.md, header does not match")
|
||||||
|
}
|
||||||
|
if headerIndex != nil {
|
||||||
|
// Remove the header from the content
|
||||||
|
content = content[headerIndex[1]:]
|
||||||
|
}
|
||||||
|
|
||||||
|
content = ChangelogHeader + "\n\n" + info.ChangelogEntry + content
|
||||||
|
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,3 +72,58 @@ func TestGenericUpdater_UpdateContent(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestChangelogUpdater_UpdateContent(t *testing.T) {
|
||||||
|
updater := &ChangelogUpdater{}
|
||||||
|
|
||||||
|
tests := []updaterTestCase{
|
||||||
|
{
|
||||||
|
name: "empty file",
|
||||||
|
content: "",
|
||||||
|
info: ReleaseInfo{ChangelogEntry: "## v1.0.0\n"},
|
||||||
|
want: "# Changelog\n\n## v1.0.0\n",
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "well-formatted changelog",
|
||||||
|
content: `# Changelog
|
||||||
|
|
||||||
|
## v0.0.1
|
||||||
|
|
||||||
|
- Bazzle
|
||||||
|
|
||||||
|
## v0.1.0
|
||||||
|
|
||||||
|
### Bazuuum
|
||||||
|
`,
|
||||||
|
info: ReleaseInfo{ChangelogEntry: "## v1.0.0\n\n- Version 1, juhu.\n"},
|
||||||
|
want: `# Changelog
|
||||||
|
|
||||||
|
## v1.0.0
|
||||||
|
|
||||||
|
- Version 1, juhu.
|
||||||
|
|
||||||
|
## v0.0.1
|
||||||
|
|
||||||
|
- Bazzle
|
||||||
|
|
||||||
|
## v0.1.0
|
||||||
|
|
||||||
|
### Bazuuum
|
||||||
|
`,
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error on invalid header",
|
||||||
|
content: "What even is this file?",
|
||||||
|
info: ReleaseInfo{ChangelogEntry: "## v1.0.0\n\n- Version 1, juhu.\n"},
|
||||||
|
want: "",
|
||||||
|
wantErr: assert.Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
runUpdaterTest(t, updater, tt)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue