mirror of
https://github.com/apricote/releaser-pleaser.git
synced 2026-02-11 12:17:04 +00:00
Compare commits
17 commits
4eb0fd7253
...
6810ace9b8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6810ace9b8 | ||
| d5fd606577 | |||
| f0eed8cc56 | |||
| 47de2f97bc | |||
| 589fdde401 | |||
| 6f616f9923 | |||
| ffcc3b60cb | |||
| 08e7b31eff | |||
| 69796325c7 | |||
| 0c93645b21 | |||
| fbd47fac62 | |||
| 5882a6bf2c | |||
| f2a982d7a0 | |||
| 32734d9aa1 | |||
| d8daad7623 | |||
| 8f106e4028 | |||
|
|
d5bc18b904 |
25 changed files with 1104 additions and 1006 deletions
8
.github/workflows/ci.yaml
vendored
8
.github/workflows/ci.yaml
vendored
|
|
@ -21,7 +21,7 @@ jobs:
|
||||||
- name: Run golangci-lint
|
- name: Run golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
version: v1.59.1 # renovate: datasource=github-releases depName=golangci/golangci-lint
|
version: v1.60.1 # renovate: datasource=github-releases depName=golangci/golangci-lint
|
||||||
args: --timeout 5m
|
args: --timeout 5m
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -37,8 +37,12 @@ jobs:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: go test -v -race -coverpkg=./... ./...
|
run: go test -v -race -coverpkg=./... -coverprofile=coverage.txt ./...
|
||||||
|
|
||||||
|
- name: Upload results to Codecov
|
||||||
|
uses: codecov/codecov-action@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
go-mod-tidy:
|
go-mod-tidy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
|
||||||
11
.github/workflows/releaser-pleaser.yaml
vendored
11
.github/workflows/releaser-pleaser.yaml
vendored
|
|
@ -9,10 +9,7 @@ on:
|
||||||
- labeled
|
- labeled
|
||||||
- unlabeled
|
- unlabeled
|
||||||
|
|
||||||
permissions:
|
permissions: {}
|
||||||
contents: write
|
|
||||||
issues: write
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
releaser-pleaser:
|
releaser-pleaser:
|
||||||
|
|
@ -21,5 +18,7 @@ jobs:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- run: env
|
- name: releaser-pleaser
|
||||||
- uses: ./
|
uses: ./
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.RELEASER_PLEASER_TOKEN }}
|
||||||
|
|
|
||||||
27
.golangci.yaml
Normal file
27
.golangci.yaml
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
linters:
|
||||||
|
presets:
|
||||||
|
- bugs
|
||||||
|
- error
|
||||||
|
- import
|
||||||
|
- metalinter
|
||||||
|
- module
|
||||||
|
- unused
|
||||||
|
|
||||||
|
enable:
|
||||||
|
- testifylint
|
||||||
|
|
||||||
|
disable:
|
||||||
|
# preset error
|
||||||
|
# These should probably be cleaned up at some point if we want to publish part of this as a library.
|
||||||
|
- err113 # Very annoying to define static errors everywhere
|
||||||
|
- wrapcheck # Very annoying to wrap errors everywhere
|
||||||
|
# preset import
|
||||||
|
- depguard
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
gci:
|
||||||
|
sections:
|
||||||
|
- standard
|
||||||
|
- default
|
||||||
|
- localmodule
|
||||||
|
|
||||||
14
CHANGELOG.md
Normal file
14
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [v0.2.0](https://github.com/apricote/releaser-pleaser/releases/tag/v0.2.0)
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- update version references in any files (#14)
|
||||||
|
|
||||||
|
## [v0.1.0](https://github.com/apricote/releaser-pleaser/releases/tag/v0.1.0)
|
||||||
|
### This is the first release ever, so it also includes a lot of other functionality.
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- add github action (#1)
|
||||||
|
|
@ -12,14 +12,19 @@ inputs:
|
||||||
description: 'GitHub token for creating and grooming release PRs, defaults to using secrets.GITHUB_TOKEN'
|
description: 'GitHub token for creating and grooming release PRs, defaults to using secrets.GITHUB_TOKEN'
|
||||||
required: false
|
required: false
|
||||||
default: ${{ github.token }}
|
default: ${{ github.token }}
|
||||||
|
extra-files:
|
||||||
|
description: 'List of files that are scanned for version references.'
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
outputs: {}
|
outputs: {}
|
||||||
runs:
|
runs:
|
||||||
using: 'docker'
|
using: 'docker'
|
||||||
image: ghcr.io/apricote/releaser-pleaser:v0.1.0
|
image: ghcr.io/apricote/releaser-pleaser:v0.2.0 # x-releaser-pleaser-version
|
||||||
args:
|
args:
|
||||||
- run
|
- run
|
||||||
- --forge=github
|
- --forge=github
|
||||||
- --branch=${{ inputs.branch }}
|
- --branch=${{ inputs.branch }}
|
||||||
|
- --extra-files="${{ inputs.extra-files }}"
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ inputs.token }}
|
GITHUB_TOKEN: ${{ inputs.token }}
|
||||||
GITHUB_USER: "oauth2"
|
GITHUB_USER: "oauth2"
|
||||||
|
|
|
||||||
83
changelog.go
83
changelog.go
|
|
@ -3,26 +3,17 @@ 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 (
|
||||||
ChangelogFile = "CHANGELOG.md"
|
ChangelogFile = "CHANGELOG.md"
|
||||||
ChangelogFileBuffer = "CHANGELOG.md.tmp"
|
ChangelogHeader = "# Changelog"
|
||||||
ChangelogHeader = "# Changelog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
changelogTemplate *template.Template
|
changelogTemplate *template.Template
|
||||||
|
|
||||||
headerRegex = regexp.MustCompile(`^# Changelog\n`)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed changelog.md.tpl
|
//go:embed changelog.md.tpl
|
||||||
|
|
@ -36,72 +27,16 @@ func init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateChangelogFile(wt *git.Worktree, newEntry string) error {
|
func NewChangelogEntry(commits []AnalyzedCommit, version, link, prefix, suffix string) (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(changesets []Changeset, version, link, prefix, suffix string) (string, error) {
|
|
||||||
features := make([]AnalyzedCommit, 0)
|
features := make([]AnalyzedCommit, 0)
|
||||||
fixes := make([]AnalyzedCommit, 0)
|
fixes := make([]AnalyzedCommit, 0)
|
||||||
|
|
||||||
for _, changeset := range changesets {
|
for _, commit := range commits {
|
||||||
for _, commit := range changeset.ChangelogEntries {
|
switch commit.Type {
|
||||||
switch commit.Type {
|
case "feat":
|
||||||
case "feat":
|
features = append(features, commit)
|
||||||
features = append(features, commit)
|
case "fix":
|
||||||
case "fix":
|
fixes = append(fixes, commit)
|
||||||
fixes = append(fixes, commit)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,106 +1,22 @@
|
||||||
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 {
|
||||||
changesets []Changeset
|
analyzedCommits []AnalyzedCommit
|
||||||
version string
|
version string
|
||||||
link string
|
link string
|
||||||
prefix string
|
prefix string
|
||||||
suffix string
|
suffix string
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
@ -111,9 +27,9 @@ func Test_NewChangelogEntry(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "empty",
|
name: "empty",
|
||||||
args: args{
|
args: args{
|
||||||
changesets: []Changeset{{ChangelogEntries: []AnalyzedCommit{}}},
|
analyzedCommits: []AnalyzedCommit{},
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
link: "https://example.com/1.0.0",
|
link: "https://example.com/1.0.0",
|
||||||
},
|
},
|
||||||
want: "## [1.0.0](https://example.com/1.0.0)",
|
want: "## [1.0.0](https://example.com/1.0.0)",
|
||||||
wantErr: assert.NoError,
|
wantErr: assert.NoError,
|
||||||
|
|
@ -121,13 +37,13 @@ func Test_NewChangelogEntry(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "single feature",
|
name: "single feature",
|
||||||
args: args{
|
args: args{
|
||||||
changesets: []Changeset{{ChangelogEntries: []AnalyzedCommit{
|
analyzedCommits: []AnalyzedCommit{
|
||||||
{
|
{
|
||||||
Commit: Commit{},
|
Commit: Commit{},
|
||||||
Type: "feat",
|
Type: "feat",
|
||||||
Description: "Foobar!",
|
Description: "Foobar!",
|
||||||
},
|
},
|
||||||
}}},
|
},
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
link: "https://example.com/1.0.0",
|
link: "https://example.com/1.0.0",
|
||||||
},
|
},
|
||||||
|
|
@ -137,13 +53,13 @@ func Test_NewChangelogEntry(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "single fix",
|
name: "single fix",
|
||||||
args: args{
|
args: args{
|
||||||
changesets: []Changeset{{ChangelogEntries: []AnalyzedCommit{
|
analyzedCommits: []AnalyzedCommit{
|
||||||
{
|
{
|
||||||
Commit: Commit{},
|
Commit: Commit{},
|
||||||
Type: "fix",
|
Type: "fix",
|
||||||
Description: "Foobar!",
|
Description: "Foobar!",
|
||||||
},
|
},
|
||||||
}}},
|
},
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
link: "https://example.com/1.0.0",
|
link: "https://example.com/1.0.0",
|
||||||
},
|
},
|
||||||
|
|
@ -153,7 +69,7 @@ func Test_NewChangelogEntry(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "multiple commits with scopes",
|
name: "multiple commits with scopes",
|
||||||
args: args{
|
args: args{
|
||||||
changesets: []Changeset{{ChangelogEntries: []AnalyzedCommit{
|
analyzedCommits: []AnalyzedCommit{
|
||||||
{
|
{
|
||||||
Commit: Commit{},
|
Commit: Commit{},
|
||||||
Type: "feat",
|
Type: "feat",
|
||||||
|
|
@ -176,7 +92,7 @@ func Test_NewChangelogEntry(t *testing.T) {
|
||||||
Description: "So sad!",
|
Description: "So sad!",
|
||||||
Scope: ptr("sad"),
|
Scope: ptr("sad"),
|
||||||
},
|
},
|
||||||
}}},
|
},
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
link: "https://example.com/1.0.0",
|
link: "https://example.com/1.0.0",
|
||||||
},
|
},
|
||||||
|
|
@ -196,13 +112,13 @@ func Test_NewChangelogEntry(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "prefix",
|
name: "prefix",
|
||||||
args: args{
|
args: args{
|
||||||
changesets: []Changeset{{ChangelogEntries: []AnalyzedCommit{
|
analyzedCommits: []AnalyzedCommit{
|
||||||
{
|
{
|
||||||
Commit: Commit{},
|
Commit: Commit{},
|
||||||
Type: "fix",
|
Type: "fix",
|
||||||
Description: "Foobar!",
|
Description: "Foobar!",
|
||||||
},
|
},
|
||||||
}}},
|
},
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
link: "https://example.com/1.0.0",
|
link: "https://example.com/1.0.0",
|
||||||
prefix: "### Breaking Changes",
|
prefix: "### Breaking Changes",
|
||||||
|
|
@ -219,13 +135,13 @@ func Test_NewChangelogEntry(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "suffix",
|
name: "suffix",
|
||||||
args: args{
|
args: args{
|
||||||
changesets: []Changeset{{ChangelogEntries: []AnalyzedCommit{
|
analyzedCommits: []AnalyzedCommit{
|
||||||
{
|
{
|
||||||
Commit: Commit{},
|
Commit: Commit{},
|
||||||
Type: "fix",
|
Type: "fix",
|
||||||
Description: "Foobar!",
|
Description: "Foobar!",
|
||||||
},
|
},
|
||||||
}}},
|
},
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
link: "https://example.com/1.0.0",
|
link: "https://example.com/1.0.0",
|
||||||
suffix: "### Compatibility\n\nThis version is compatible with flux-compensator v2.2 - v2.9.",
|
suffix: "### Compatibility\n\nThis version is compatible with flux-compensator v2.2 - v2.9.",
|
||||||
|
|
@ -245,7 +161,7 @@ This version is compatible with flux-compensator v2.2 - v2.9.
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := NewChangelogEntry(tt.args.changesets, tt.args.version, tt.args.link, tt.args.prefix, tt.args.suffix)
|
got, err := NewChangelogEntry(tt.args.analyzedCommits, tt.args.version, tt.args.link, tt.args.prefix, tt.args.suffix)
|
||||||
if !tt.wantErr(t, err) {
|
if !tt.wantErr(t, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,13 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"strings"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5"
|
|
||||||
"github.com/go-git/go-git/v5/config"
|
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
rp "github.com/apricote/releaser-pleaser"
|
rp "github.com/apricote/releaser-pleaser"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
RELEASER_PLEASER_BRANCH = "releaser-pleaser--branches--%s"
|
|
||||||
)
|
|
||||||
|
|
||||||
// runCmd represents the run command
|
// runCmd represents the run command
|
||||||
var runCmd = &cobra.Command{
|
var runCmd = &cobra.Command{
|
||||||
Use: "run",
|
Use: "run",
|
||||||
|
|
@ -23,10 +15,11 @@ var runCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
flagForge string
|
flagForge string
|
||||||
flagBranch string
|
flagBranch string
|
||||||
flagOwner string
|
flagOwner string
|
||||||
flagRepo string
|
flagRepo string
|
||||||
|
flagExtraFiles string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -38,6 +31,7 @@ func init() {
|
||||||
runCmd.PersistentFlags().StringVar(&flagBranch, "branch", "main", "")
|
runCmd.PersistentFlags().StringVar(&flagBranch, "branch", "main", "")
|
||||||
runCmd.PersistentFlags().StringVar(&flagOwner, "owner", "", "")
|
runCmd.PersistentFlags().StringVar(&flagOwner, "owner", "", "")
|
||||||
runCmd.PersistentFlags().StringVar(&flagRepo, "repo", "", "")
|
runCmd.PersistentFlags().StringVar(&flagRepo, "repo", "", "")
|
||||||
|
runCmd.PersistentFlags().StringVar(&flagExtraFiles, "extra-files", "", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(cmd *cobra.Command, _ []string) error {
|
func run(cmd *cobra.Command, _ []string) error {
|
||||||
|
|
@ -50,319 +44,50 @@ func run(cmd *cobra.Command, _ []string) error {
|
||||||
"repo", flagRepo,
|
"repo", flagRepo,
|
||||||
)
|
)
|
||||||
|
|
||||||
var f rp.Forge
|
var forge rp.Forge
|
||||||
|
|
||||||
forgeOptions := rp.ForgeOptions{
|
forgeOptions := rp.ForgeOptions{
|
||||||
Repository: flagRepo,
|
Repository: flagRepo,
|
||||||
BaseBranch: flagBranch,
|
BaseBranch: flagBranch,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch flagForge {
|
switch flagForge { // nolint:gocritic // Will become a proper switch once gitlab is added
|
||||||
//case "gitlab":
|
// case "gitlab":
|
||||||
//f = rp.NewGitLab(forgeOptions)
|
// f = rp.NewGitLab(forgeOptions)
|
||||||
case "github":
|
case "github":
|
||||||
logger.DebugContext(ctx, "using forge GitHub")
|
logger.DebugContext(ctx, "using forge GitHub")
|
||||||
f = rp.NewGitHub(logger, &rp.GitHubOptions{
|
forge = rp.NewGitHub(logger, &rp.GitHubOptions{
|
||||||
ForgeOptions: forgeOptions,
|
ForgeOptions: forgeOptions,
|
||||||
Owner: flagOwner,
|
Owner: flagOwner,
|
||||||
Repo: flagRepo,
|
Repo: flagRepo,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
err := ensureLabels(ctx, f)
|
extraFiles := parseExtraFiles(flagExtraFiles)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to ensure all labels exist: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = createPendingReleases(ctx, f)
|
releaserPleaser := rp.New(
|
||||||
if err != nil {
|
forge,
|
||||||
return fmt.Errorf("failed to create pending releases: %w", err)
|
logger,
|
||||||
}
|
flagBranch,
|
||||||
|
rp.NewConventionalCommitsParser(),
|
||||||
|
rp.SemVerNextVersion,
|
||||||
|
extraFiles,
|
||||||
|
[]rp.Updater{&rp.GenericUpdater{}},
|
||||||
|
)
|
||||||
|
|
||||||
changesets, releases, err := getChangesetsFromForge(ctx, f)
|
return releaserPleaser.Run(ctx)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get changesets: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = reconcileReleasePR(ctx, f, changesets, releases)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to reconcile release pr: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureLabels(ctx context.Context, forge rp.Forge) error {
|
func parseExtraFiles(input string) []string {
|
||||||
return forge.EnsureLabelsExist(ctx, rp.Labels)
|
lines := strings.Split(input, "\n")
|
||||||
}
|
|
||||||
|
extraFiles := make([]string, 0, len(lines))
|
||||||
func createPendingReleases(ctx context.Context, forge rp.Forge) error {
|
for _, line := range lines {
|
||||||
logger.InfoContext(ctx, "checking for pending releases")
|
line = strings.TrimSpace(line)
|
||||||
prs, err := forge.PendingReleases(ctx)
|
if len(line) > 0 {
|
||||||
if err != nil {
|
extraFiles = append(extraFiles, line)
|
||||||
return err
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(prs) == 0 {
|
return extraFiles
|
||||||
logger.InfoContext(ctx, "No pending releases found")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.InfoContext(ctx, "Found pending releases", "length", len(prs))
|
|
||||||
|
|
||||||
for _, pr := range prs {
|
|
||||||
err = createPendingRelease(ctx, forge, pr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createPendingRelease(ctx context.Context, forge rp.Forge, pr *rp.ReleasePullRequest) error {
|
|
||||||
logger := logger.With("pr.id", pr.ID, "pr.title", pr.Title)
|
|
||||||
|
|
||||||
if pr.ReleaseCommit == nil {
|
|
||||||
return fmt.Errorf("pull request is missing the merge commit")
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("Creating release", "commit.hash", pr.ReleaseCommit.Hash)
|
|
||||||
|
|
||||||
version, err := pr.Version()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
changelog, err := pr.ChangelogText()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: pre-release & latest
|
|
||||||
|
|
||||||
logger.DebugContext(ctx, "Creating release on forge")
|
|
||||||
err = forge.CreateRelease(ctx, *pr.ReleaseCommit, version, changelog, false, true)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create release on forge: %w", err)
|
|
||||||
}
|
|
||||||
logger.DebugContext(ctx, "created release", "release.title", version, "release.url", forge.ReleaseURL(version))
|
|
||||||
|
|
||||||
logger.DebugContext(ctx, "updating pr labels")
|
|
||||||
err = forge.SetPullRequestLabels(ctx, pr, []string{rp.LabelReleasePending}, []string{rp.LabelReleaseTagged})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.DebugContext(ctx, "updated pr labels")
|
|
||||||
|
|
||||||
logger.InfoContext(ctx, "Created release", "release.title", version, "release.url", forge.ReleaseURL(version))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getChangesetsFromForge(ctx context.Context, forge rp.Forge) ([]rp.Changeset, rp.Releases, error) {
|
|
||||||
releases, err := forge.LatestTags(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, rp.Releases{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if releases.Latest != nil {
|
|
||||||
logger.InfoContext(ctx, "found latest tag", "tag.hash", releases.Latest.Hash, "tag.name", releases.Latest.Name)
|
|
||||||
if releases.Stable != nil && releases.Latest.Hash != releases.Stable.Hash {
|
|
||||||
logger.InfoContext(ctx, "found stable tag", "tag.hash", releases.Stable.Hash, "tag.name", releases.Stable.Name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.InfoContext(ctx, "no latest tag found")
|
|
||||||
}
|
|
||||||
|
|
||||||
releasableCommits, err := forge.CommitsSince(ctx, releases.Stable)
|
|
||||||
if err != nil {
|
|
||||||
return nil, rp.Releases{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.InfoContext(ctx, "Found releasable commits", "length", len(releasableCommits))
|
|
||||||
|
|
||||||
changesets, err := forge.Changesets(ctx, releasableCommits)
|
|
||||||
if err != nil {
|
|
||||||
return nil, rp.Releases{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.InfoContext(ctx, "Found changesets", "length", len(changesets))
|
|
||||||
|
|
||||||
return changesets, releases, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func reconcileReleasePR(ctx context.Context, forge rp.Forge, changesets []rp.Changeset, releases rp.Releases) error {
|
|
||||||
rpBranch := fmt.Sprintf(RELEASER_PLEASER_BRANCH, flagBranch)
|
|
||||||
rpBranchRef := plumbing.NewBranchReferenceName(rpBranch)
|
|
||||||
// Check Forge for open PR
|
|
||||||
// Get any modifications from open PR
|
|
||||||
// Clone Repo
|
|
||||||
// Run Updaters + Changelog
|
|
||||||
// Upsert PR
|
|
||||||
pr, err := forge.PullRequestForBranch(ctx, fmt.Sprintf(RELEASER_PLEASER_BRANCH, flagBranch))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if pr != nil {
|
|
||||||
logger.InfoContext(ctx, "found existing release pull request", "pr.id", pr.ID, "pr.title", pr.Title)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(changesets) == 0 {
|
|
||||||
if pr != nil {
|
|
||||||
logger.InfoContext(ctx, "closing existing pull requests, no changesets available", "pr.id", pr.ID, "pr.title", pr.Title)
|
|
||||||
err = forge.ClosePullRequest(ctx, pr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.InfoContext(ctx, "No changesets available for release")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var releaseOverrides rp.ReleaseOverrides
|
|
||||||
if pr != nil {
|
|
||||||
releaseOverrides, err = pr.GetOverrides()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
versionBump := rp.VersionBumpFromChangesets(changesets)
|
|
||||||
nextVersion, err := releases.NextVersion(versionBump, releaseOverrides.NextVersionType)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.InfoContext(ctx, "next version", "version", nextVersion)
|
|
||||||
|
|
||||||
logger.DebugContext(ctx, "cloning repository", "clone.url", forge.CloneURL())
|
|
||||||
repo, err := rp.CloneRepo(ctx, forge.CloneURL(), flagBranch, forge.GitAuth())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to clone repository: %w", err)
|
|
||||||
}
|
|
||||||
worktree, err := repo.Worktree()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if branch, _ := repo.Branch(rpBranch); branch != nil {
|
|
||||||
logger.DebugContext(ctx, "deleting previous releaser-pleaser branch locally", "branch.name", rpBranch)
|
|
||||||
if err = repo.DeleteBranch(rpBranch); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = worktree.Checkout(&git.CheckoutOptions{
|
|
||||||
Branch: rpBranchRef,
|
|
||||||
Create: true,
|
|
||||||
}); err != nil {
|
|
||||||
return fmt.Errorf("failed to check out branch: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = rp.RunUpdater(ctx, nextVersion, worktree)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to update files with new version: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
changelogEntry, err := rp.NewChangelogEntry(changesets, nextVersion, forge.ReleaseURL(nextVersion), releaseOverrides.Prefix, releaseOverrides.Suffix)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to build changelog entry: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = rp.UpdateChangelogFile(worktree, changelogEntry)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to update changelog file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
releaseCommitMessage := fmt.Sprintf("chore(%s): release %s", flagBranch, nextVersion)
|
|
||||||
releaseCommitHash, err := worktree.Commit(releaseCommitMessage, &git.CommitOptions{
|
|
||||||
Author: rp.GitSignature(),
|
|
||||||
Committer: rp.GitSignature(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to commit changes: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.InfoContext(ctx, "created release commit", "commit.hash", releaseCommitHash.String(), "commit.message", releaseCommitMessage)
|
|
||||||
|
|
||||||
newReleasePRChanges := true
|
|
||||||
|
|
||||||
// Check if anything changed in comparison to the remote branch (if exists)
|
|
||||||
if remoteRef, err := repo.Reference(plumbing.NewRemoteReferenceName(rp.GitRemoteName, rpBranch), false); err != nil {
|
|
||||||
if err.Error() != "reference not found" {
|
|
||||||
// "reference not found" is expected and we should always push
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
remoteCommit, err := repo.CommitObject(remoteRef.Hash())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
localCommit, err := repo.CommitObject(releaseCommitHash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
diff, err := localCommit.PatchContext(ctx, remoteCommit)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
newReleasePRChanges = len(diff.FilePatches()) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if newReleasePRChanges {
|
|
||||||
pushRefSpec := config.RefSpec(fmt.Sprintf(
|
|
||||||
"+%s:%s",
|
|
||||||
rpBranchRef,
|
|
||||||
// This needs to be the local branch name, not the remotes/origin ref
|
|
||||||
// See https://stackoverflow.com/a/75727620
|
|
||||||
rpBranchRef,
|
|
||||||
))
|
|
||||||
logger.DebugContext(ctx, "pushing branch", "commit.hash", releaseCommitHash.String(), "branch.name", rpBranch, "refspec", pushRefSpec.String())
|
|
||||||
if err = repo.PushContext(ctx, &git.PushOptions{
|
|
||||||
RemoteName: rp.GitRemoteName,
|
|
||||||
RefSpecs: []config.RefSpec{pushRefSpec},
|
|
||||||
Force: true,
|
|
||||||
Auth: forge.GitAuth(),
|
|
||||||
}); err != nil {
|
|
||||||
return fmt.Errorf("failed to push branch: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.InfoContext(ctx, "pushed branch", "commit.hash", releaseCommitHash.String(), "branch.name", rpBranch, "refspec", pushRefSpec.String())
|
|
||||||
} else {
|
|
||||||
logger.InfoContext(ctx, "file content is already up-to-date in remote branch, skipping push")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open/Update PR
|
|
||||||
if pr == nil {
|
|
||||||
pr, err = rp.NewReleasePullRequest(rpBranch, flagBranch, nextVersion, changelogEntry)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = forge.CreatePullRequest(ctx, pr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.InfoContext(ctx, "opened pull request", "pr.title", pr.Title, "pr.id", pr.ID)
|
|
||||||
} else {
|
|
||||||
pr.SetTitle(flagBranch, nextVersion)
|
|
||||||
err = pr.SetDescription(changelogEntry)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = forge.UpdatePullRequest(ctx, pr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.InfoContext(ctx, "updated pull request", "pr.title", pr.Title, "pr.id", pr.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
43
commits.go
43
commits.go
|
|
@ -7,6 +7,19 @@ import (
|
||||||
"github.com/leodido/go-conventionalcommits/parser"
|
"github.com/leodido/go-conventionalcommits/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Commit struct {
|
||||||
|
Hash string
|
||||||
|
Message string
|
||||||
|
|
||||||
|
PullRequest *PullRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
type PullRequest struct {
|
||||||
|
ID int
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
type AnalyzedCommit struct {
|
type AnalyzedCommit struct {
|
||||||
Commit
|
Commit
|
||||||
Type string
|
Type string
|
||||||
|
|
@ -15,24 +28,36 @@ type AnalyzedCommit struct {
|
||||||
BreakingChange bool
|
BreakingChange bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func AnalyzeCommits(commits []Commit) ([]AnalyzedCommit, conventionalcommits.VersionBump, error) {
|
type CommitParser interface {
|
||||||
|
Analyze(commits []Commit) ([]AnalyzedCommit, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConventionalCommitsParser struct {
|
||||||
|
machine conventionalcommits.Machine
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConventionalCommitsParser() *ConventionalCommitsParser {
|
||||||
parserMachine := parser.NewMachine(
|
parserMachine := parser.NewMachine(
|
||||||
parser.WithBestEffort(),
|
parser.WithBestEffort(),
|
||||||
parser.WithTypes(conventionalcommits.TypesConventional),
|
parser.WithTypes(conventionalcommits.TypesConventional),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return &ConventionalCommitsParser{
|
||||||
|
machine: parserMachine,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConventionalCommitsParser) Analyze(commits []Commit) ([]AnalyzedCommit, error) {
|
||||||
analyzedCommits := make([]AnalyzedCommit, 0, len(commits))
|
analyzedCommits := make([]AnalyzedCommit, 0, len(commits))
|
||||||
|
|
||||||
highestVersionBump := conventionalcommits.UnknownVersion
|
|
||||||
|
|
||||||
for _, commit := range commits {
|
for _, commit := range commits {
|
||||||
msg, err := parserMachine.Parse([]byte(commit.Message))
|
msg, err := c.machine.Parse([]byte(commit.Message))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, conventionalcommits.UnknownVersion, fmt.Errorf("failed to parse message of commit %q: %w", commit.Hash, err)
|
return nil, fmt.Errorf("failed to parse message of commit %q: %w", commit.Hash, err)
|
||||||
}
|
}
|
||||||
conventionalCommit, ok := msg.(*conventionalcommits.ConventionalCommit)
|
conventionalCommit, ok := msg.(*conventionalcommits.ConventionalCommit)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, conventionalcommits.UnknownVersion, fmt.Errorf("unable to get ConventionalCommit from parser result: %T", msg)
|
return nil, fmt.Errorf("unable to get ConventionalCommit from parser result: %T", msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
commitVersionBump := conventionalCommit.VersionBump(conventionalcommits.DefaultStrategy)
|
commitVersionBump := conventionalCommit.VersionBump(conventionalcommits.DefaultStrategy)
|
||||||
|
|
@ -47,11 +72,7 @@ func AnalyzeCommits(commits []Commit) ([]AnalyzedCommit, conventionalcommits.Ver
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if commitVersionBump > highestVersionBump {
|
|
||||||
// Get max version bump from all releasable commits
|
|
||||||
highestVersionBump = commitVersionBump
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return analyzedCommits, highestVersionBump, nil
|
return analyzedCommits, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package rp
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/leodido/go-conventionalcommits"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -12,14 +11,12 @@ func TestAnalyzeCommits(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
commits []Commit
|
commits []Commit
|
||||||
expectedCommits []AnalyzedCommit
|
expectedCommits []AnalyzedCommit
|
||||||
expectedBump conventionalcommits.VersionBump
|
|
||||||
wantErr assert.ErrorAssertionFunc
|
wantErr assert.ErrorAssertionFunc
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "empty commits",
|
name: "empty commits",
|
||||||
commits: []Commit{},
|
commits: []Commit{},
|
||||||
expectedCommits: []AnalyzedCommit{},
|
expectedCommits: []AnalyzedCommit{},
|
||||||
expectedBump: conventionalcommits.UnknownVersion,
|
|
||||||
wantErr: assert.NoError,
|
wantErr: assert.NoError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -30,7 +27,6 @@ func TestAnalyzeCommits(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedCommits: nil,
|
expectedCommits: nil,
|
||||||
expectedBump: conventionalcommits.UnknownVersion,
|
|
||||||
wantErr: assert.Error,
|
wantErr: assert.Error,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -41,7 +37,6 @@ func TestAnalyzeCommits(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedCommits: []AnalyzedCommit{},
|
expectedCommits: []AnalyzedCommit{},
|
||||||
expectedBump: conventionalcommits.UnknownVersion,
|
|
||||||
wantErr: assert.NoError,
|
wantErr: assert.NoError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -61,8 +56,7 @@ func TestAnalyzeCommits(t *testing.T) {
|
||||||
Description: "blabla",
|
Description: "blabla",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedBump: conventionalcommits.PatchVersion,
|
wantErr: assert.NoError,
|
||||||
wantErr: assert.NoError,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "highest bump (minor)",
|
name: "highest bump (minor)",
|
||||||
|
|
@ -86,8 +80,7 @@ func TestAnalyzeCommits(t *testing.T) {
|
||||||
Description: "foobar",
|
Description: "foobar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedBump: conventionalcommits.MinorVersion,
|
wantErr: assert.NoError,
|
||||||
wantErr: assert.NoError,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
@ -113,19 +106,17 @@ func TestAnalyzeCommits(t *testing.T) {
|
||||||
BreakingChange: true,
|
BreakingChange: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedBump: conventionalcommits.MajorVersion,
|
wantErr: assert.NoError,
|
||||||
wantErr: assert.NoError,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
analyzedCommits, versionBump, err := AnalyzeCommits(tt.commits)
|
analyzedCommits, err := NewConventionalCommitsParser().Analyze(tt.commits)
|
||||||
if !tt.wantErr(t, err) {
|
if !tt.wantErr(t, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, tt.expectedCommits, analyzedCommits)
|
assert.Equal(t, tt.expectedCommits, analyzedCommits)
|
||||||
assert.Equal(t, tt.expectedBump, versionBump)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
172
forge.go
172
forge.go
|
|
@ -19,18 +19,12 @@ const (
|
||||||
GitHubPerPageMax = 100
|
GitHubPerPageMax = 100
|
||||||
GitHubPRStateOpen = "open"
|
GitHubPRStateOpen = "open"
|
||||||
GitHubPRStateClosed = "closed"
|
GitHubPRStateClosed = "closed"
|
||||||
GitHubEnvAPIToken = "GITHUB_TOKEN"
|
GitHubEnvAPIToken = "GITHUB_TOKEN" // nolint:gosec // Not actually a hardcoded credential
|
||||||
GitHubEnvUsername = "GITHUB_USER"
|
GitHubEnvUsername = "GITHUB_USER"
|
||||||
GitHubEnvRepository = "GITHUB_REPOSITORY"
|
GitHubEnvRepository = "GITHUB_REPOSITORY"
|
||||||
GitHubLabelColor = "dedede"
|
GitHubLabelColor = "dedede"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Changeset struct {
|
|
||||||
URL string
|
|
||||||
Identifier string
|
|
||||||
ChangelogEntries []AnalyzedCommit
|
|
||||||
}
|
|
||||||
|
|
||||||
type Forge interface {
|
type Forge interface {
|
||||||
RepoURL() string
|
RepoURL() string
|
||||||
CloneURL() string
|
CloneURL() string
|
||||||
|
|
@ -46,23 +40,35 @@ type Forge interface {
|
||||||
// function should return all commits.
|
// function should return all commits.
|
||||||
CommitsSince(context.Context, *Tag) ([]Commit, error)
|
CommitsSince(context.Context, *Tag) ([]Commit, error)
|
||||||
|
|
||||||
// Changesets looks up the Pull/Merge Requests for each commit, returning its parsed metadata.
|
// EnsureLabelsExist verifies that all desired labels are available on the repository. If labels are missing, they
|
||||||
Changesets(context.Context, []Commit) ([]Changeset, error)
|
// are created them.
|
||||||
|
EnsureLabelsExist(context.Context, []Label) error
|
||||||
EnsureLabelsExist(context.Context, []string) error
|
|
||||||
|
|
||||||
// PullRequestForBranch returns the open pull request between the branch and ForgeOptions.BaseBranch. If no open PR
|
// PullRequestForBranch returns the open pull request between the branch and ForgeOptions.BaseBranch. If no open PR
|
||||||
// exists, it returns nil.
|
// exists, it returns nil.
|
||||||
PullRequestForBranch(context.Context, string) (*ReleasePullRequest, error)
|
PullRequestForBranch(context.Context, string) (*ReleasePullRequest, error)
|
||||||
|
|
||||||
|
// CreatePullRequest opens a new pull/merge request for the ReleasePullRequest.
|
||||||
CreatePullRequest(context.Context, *ReleasePullRequest) error
|
CreatePullRequest(context.Context, *ReleasePullRequest) error
|
||||||
|
|
||||||
|
// UpdatePullRequest updates the pull/merge request identified through the ID of
|
||||||
|
// the ReleasePullRequest to the current description and title.
|
||||||
UpdatePullRequest(context.Context, *ReleasePullRequest) error
|
UpdatePullRequest(context.Context, *ReleasePullRequest) error
|
||||||
SetPullRequestLabels(ctx context.Context, pr *ReleasePullRequest, remove, add []string) error
|
|
||||||
|
// SetPullRequestLabels updates the pull/merge request identified through the ID of
|
||||||
|
// the ReleasePullRequest to the current labels.
|
||||||
|
SetPullRequestLabels(ctx context.Context, pr *ReleasePullRequest, remove, add []Label) error
|
||||||
|
|
||||||
|
// ClosePullRequest closes the pull/merge request identified through the ID of
|
||||||
|
// the ReleasePullRequest, as it is no longer required.
|
||||||
ClosePullRequest(context.Context, *ReleasePullRequest) error
|
ClosePullRequest(context.Context, *ReleasePullRequest) error
|
||||||
|
|
||||||
PendingReleases(context.Context) ([]*ReleasePullRequest, error)
|
// PendingReleases returns a list of ReleasePullRequest. The list should contain all pull/merge requests that are
|
||||||
|
// merged and have the matching label.
|
||||||
|
PendingReleases(context.Context, Label) ([]*ReleasePullRequest, error)
|
||||||
|
|
||||||
CreateRelease(ctx context.Context, commit Commit, title, changelog string, prelease, latest bool) error
|
// CreateRelease creates a release on the Forge, pointing at the commit with the passed in details.
|
||||||
|
CreateRelease(ctx context.Context, commit Commit, title, changelog string, prerelease, latest bool) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type ForgeOptions struct {
|
type ForgeOptions struct {
|
||||||
|
|
@ -169,10 +175,16 @@ func (g *GitHub) CommitsSince(ctx context.Context, tag *Tag) ([]Commit, error) {
|
||||||
|
|
||||||
var commits = make([]Commit, 0, len(repositoryCommits))
|
var commits = make([]Commit, 0, len(repositoryCommits))
|
||||||
for _, ghCommit := range repositoryCommits {
|
for _, ghCommit := range repositoryCommits {
|
||||||
commits = append(commits, Commit{
|
commit := Commit{
|
||||||
Hash: ghCommit.GetSHA(),
|
Hash: ghCommit.GetSHA(),
|
||||||
Message: ghCommit.GetCommit().GetMessage(),
|
Message: ghCommit.GetCommit().GetMessage(),
|
||||||
})
|
}
|
||||||
|
commit.PullRequest, err = g.prForCommit(ctx, commit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to check for commit pull request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
commits = append(commits, commit)
|
||||||
}
|
}
|
||||||
|
|
||||||
return commits, nil
|
return commits, nil
|
||||||
|
|
@ -257,76 +269,52 @@ func (g *GitHub) commitsSinceInit(ctx context.Context) ([]*github.RepositoryComm
|
||||||
return repositoryCommits, nil
|
return repositoryCommits, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GitHub) Changesets(ctx context.Context, commits []Commit) ([]Changeset, error) {
|
func (g *GitHub) prForCommit(ctx context.Context, commit Commit) (*PullRequest, error) {
|
||||||
// We naively look up the associated PR for each commit through the "List pull requests associated with a commit"
|
// We naively look up the associated PR for each commit through the "List pull requests associated with a commit"
|
||||||
// endpoint. This requires len(commits) requests.
|
// endpoint. This requires len(commits) requests.
|
||||||
// Using the "List pull requests" endpoint might be faster, as it allows us to fetch 100 arbitrary PRs per request,
|
// Using the "List pull requests" endpoint might be faster, as it allows us to fetch 100 arbitrary PRs per request,
|
||||||
// but worst case we need to look up all PRs made in the repository ever.
|
// but worst case we need to look up all PRs made in the repository ever.
|
||||||
|
|
||||||
changesets := make([]Changeset, 0, len(commits))
|
log := g.log.With("commit.hash", commit.Hash)
|
||||||
|
page := 1
|
||||||
|
var associatedPRs []*github.PullRequest
|
||||||
|
|
||||||
for _, commit := range commits {
|
for {
|
||||||
log := g.log.With("commit.hash", commit.Hash)
|
log.Debug("fetching pull requests associated with commit", "page", page)
|
||||||
page := 1
|
prs, resp, err := g.client.PullRequests.ListPullRequestsWithCommit(
|
||||||
var associatedPRs []*github.PullRequest
|
ctx, g.options.Owner, g.options.Repo,
|
||||||
|
commit.Hash, &github.ListOptions{
|
||||||
for {
|
Page: page,
|
||||||
log.Debug("fetching pull requests associated with commit", "page", page)
|
PerPage: GitHubPerPageMax,
|
||||||
prs, resp, err := g.client.PullRequests.ListPullRequestsWithCommit(
|
|
||||||
ctx, g.options.Owner, g.options.Repo,
|
|
||||||
commit.Hash, &github.ListOptions{
|
|
||||||
Page: page,
|
|
||||||
PerPage: GitHubPerPageMax,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
associatedPRs = append(associatedPRs, prs...)
|
|
||||||
|
|
||||||
if page == resp.LastPage || resp.LastPage == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
page = resp.NextPage
|
|
||||||
}
|
|
||||||
|
|
||||||
var pullrequest *github.PullRequest
|
|
||||||
for _, pr := range associatedPRs {
|
|
||||||
// We only look for the PR that has this commit set as the "merge commit" => The result of squashing this branch onto main
|
|
||||||
if pr.GetMergeCommitSHA() == commit.Hash {
|
|
||||||
pullrequest = pr
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pullrequest == nil {
|
|
||||||
log.Warn("did not find associated pull request, not considering it for changesets")
|
|
||||||
// No pull request was found for this commit, nothing to do here
|
|
||||||
// TODO: We could also return the minimal changeset for this commit, so at least it would come up in the changelog.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log = log.With("pullrequest.id", pullrequest.GetID())
|
|
||||||
|
|
||||||
// TODO: Parse PR description for overrides
|
|
||||||
changelogEntries, _, err := AnalyzeCommits([]Commit{commit})
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("unable to parse changelog entries", "error", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(changelogEntries) > 0 {
|
|
||||||
changesets = append(changesets, Changeset{
|
|
||||||
URL: pullrequest.GetHTMLURL(),
|
|
||||||
Identifier: fmt.Sprintf("#%d", pullrequest.GetNumber()),
|
|
||||||
ChangelogEntries: changelogEntries,
|
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
associatedPRs = append(associatedPRs, prs...)
|
||||||
|
|
||||||
|
if page == resp.LastPage || resp.LastPage == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
page = resp.NextPage
|
||||||
}
|
}
|
||||||
|
|
||||||
return changesets, nil
|
var pullrequest *github.PullRequest
|
||||||
|
for _, pr := range associatedPRs {
|
||||||
|
// We only look for the PR that has this commit set as the "merge commit" => The result of squashing this branch onto main
|
||||||
|
if pr.GetMergeCommitSHA() == commit.Hash {
|
||||||
|
pullrequest = pr
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pullrequest == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return gitHubPRToPullRequest(pullrequest), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GitHub) EnsureLabelsExist(ctx context.Context, labels []string) error {
|
func (g *GitHub) EnsureLabelsExist(ctx context.Context, labels []Label) error {
|
||||||
existingLabels := make([]string, 0, len(labels))
|
existingLabels := make([]string, 0, len(labels))
|
||||||
|
|
||||||
page := 1
|
page := 1
|
||||||
|
|
@ -354,12 +342,12 @@ func (g *GitHub) EnsureLabelsExist(ctx context.Context, labels []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, label := range labels {
|
for _, label := range labels {
|
||||||
if !slices.Contains(existingLabels, label) {
|
if !slices.Contains(existingLabels, string(label)) {
|
||||||
g.log.Info("creating label in repository", "label.name", label)
|
g.log.Info("creating label in repository", "label.name", label)
|
||||||
_, _, err := g.client.Issues.CreateLabel(
|
_, _, err := g.client.Issues.CreateLabel(
|
||||||
ctx, g.options.Owner, g.options.Repo,
|
ctx, g.options.Owner, g.options.Repo,
|
||||||
&github.Label{
|
&github.Label{
|
||||||
Name: &label,
|
Name: Pointer(string(label)),
|
||||||
Color: Pointer(GitHubLabelColor),
|
Color: Pointer(GitHubLabelColor),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -422,7 +410,7 @@ func (g *GitHub) CreatePullRequest(ctx context.Context, pr *ReleasePullRequest)
|
||||||
// TODO: String ID?
|
// TODO: String ID?
|
||||||
pr.ID = ghPR.GetNumber()
|
pr.ID = ghPR.GetNumber()
|
||||||
|
|
||||||
err = g.SetPullRequestLabels(ctx, pr, []string{}, pr.Labels)
|
err = g.SetPullRequestLabels(ctx, pr, []Label{}, pr.Labels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -445,20 +433,25 @@ func (g *GitHub) UpdatePullRequest(ctx context.Context, pr *ReleasePullRequest)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GitHub) SetPullRequestLabels(ctx context.Context, pr *ReleasePullRequest, remove, add []string) error {
|
func (g *GitHub) SetPullRequestLabels(ctx context.Context, pr *ReleasePullRequest, remove, add []Label) error {
|
||||||
for _, label := range remove {
|
for _, label := range remove {
|
||||||
_, err := g.client.Issues.RemoveLabelForIssue(
|
_, err := g.client.Issues.RemoveLabelForIssue(
|
||||||
ctx, g.options.Owner, g.options.Repo,
|
ctx, g.options.Owner, g.options.Repo,
|
||||||
pr.ID, label,
|
pr.ID, string(label),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addString := make([]string, 0, len(add))
|
||||||
|
for _, label := range add {
|
||||||
|
addString = append(addString, string(label))
|
||||||
|
}
|
||||||
|
|
||||||
_, _, err := g.client.Issues.AddLabelsToIssue(
|
_, _, err := g.client.Issues.AddLabelsToIssue(
|
||||||
ctx, g.options.Owner, g.options.Repo,
|
ctx, g.options.Owner, g.options.Repo,
|
||||||
pr.ID, add,
|
pr.ID, addString,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -481,7 +474,7 @@ func (g *GitHub) ClosePullRequest(ctx context.Context, pr *ReleasePullRequest) e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GitHub) PendingReleases(ctx context.Context) ([]*ReleasePullRequest, error) {
|
func (g *GitHub) PendingReleases(ctx context.Context, pendingLabel Label) ([]*ReleasePullRequest, error) {
|
||||||
page := 1
|
page := 1
|
||||||
|
|
||||||
var prs []*ReleasePullRequest
|
var prs []*ReleasePullRequest
|
||||||
|
|
@ -509,7 +502,7 @@ func (g *GitHub) PendingReleases(ctx context.Context) ([]*ReleasePullRequest, er
|
||||||
|
|
||||||
for _, pr := range ghPRs {
|
for _, pr := range ghPRs {
|
||||||
pending := slices.ContainsFunc(pr.Labels, func(l *github.Label) bool {
|
pending := slices.ContainsFunc(pr.Labels, func(l *github.Label) bool {
|
||||||
return l.GetName() == LabelReleasePending
|
return l.GetName() == string(pendingLabel)
|
||||||
})
|
})
|
||||||
if !pending {
|
if !pending {
|
||||||
continue
|
continue
|
||||||
|
|
@ -558,10 +551,21 @@ func (g *GitHub) CreateRelease(ctx context.Context, commit Commit, title, change
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func gitHubPRToPullRequest(pr *github.PullRequest) *PullRequest {
|
||||||
|
return &PullRequest{
|
||||||
|
ID: pr.GetNumber(),
|
||||||
|
Title: pr.GetTitle(),
|
||||||
|
Description: pr.GetBody(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func gitHubPRToReleasePullRequest(pr *github.PullRequest) *ReleasePullRequest {
|
func gitHubPRToReleasePullRequest(pr *github.PullRequest) *ReleasePullRequest {
|
||||||
labels := make([]string, 0, len(pr.Labels))
|
labels := make([]Label, 0, len(pr.Labels))
|
||||||
for _, label := range pr.Labels {
|
for _, label := range pr.Labels {
|
||||||
labels = append(labels, label.GetName())
|
labelName := Label(label.GetName())
|
||||||
|
if slices.Contains(KnownLabels, Label(label.GetName())) {
|
||||||
|
labels = append(labels, labelName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var releaseCommit *Commit
|
var releaseCommit *Commit
|
||||||
|
|
|
||||||
8
git.go
8
git.go
|
|
@ -13,15 +13,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
CommitSearchDepth = 50 // TODO: Increase
|
GitRemoteName = "origin"
|
||||||
GitRemoteName = "origin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Commit struct {
|
|
||||||
Hash string
|
|
||||||
Message string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
Hash string
|
Hash string
|
||||||
Name string
|
Name string
|
||||||
|
|
|
||||||
20
go.mod
20
go.mod
|
|
@ -1,6 +1,6 @@
|
||||||
module github.com/apricote/releaser-pleaser
|
module github.com/apricote/releaser-pleaser
|
||||||
|
|
||||||
go 1.22.4
|
go 1.23.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/blang/semver/v4 v4.0.0
|
github.com/blang/semver/v4 v4.0.0
|
||||||
|
|
@ -14,11 +14,11 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
dario.cat/mergo v1.0.0 // indirect
|
dario.cat/mergo v1.0.1 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||||
github.com/cloudflare/circl v1.3.7 // indirect
|
github.com/cloudflare/circl v1.3.9 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
github.com/cyphar/filepath-securejoin v0.3.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
|
|
@ -31,14 +31,12 @@ require (
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/skeema/knownhosts v1.2.2 // indirect
|
github.com/skeema/knownhosts v1.3.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
golang.org/x/crypto v0.23.0 // indirect
|
golang.org/x/crypto v0.26.0 // indirect
|
||||||
golang.org/x/mod v0.12.0 // indirect
|
golang.org/x/net v0.28.0 // indirect
|
||||||
golang.org/x/net v0.22.0 // indirect
|
golang.org/x/sys v0.24.0 // indirect
|
||||||
golang.org/x/sys v0.20.0 // indirect
|
|
||||||
golang.org/x/tools v0.13.0 // indirect
|
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
46
go.sum
46
go.sum
|
|
@ -1,8 +1,8 @@
|
||||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
||||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
|
|
@ -13,11 +13,11 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM
|
||||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE=
|
||||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
github.com/cyphar/filepath-securejoin v0.3.1 h1:1V7cHiaW+C+39wEfpH6XlLBQo3j/PciWFrgfCLS8XrE=
|
||||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
github.com/cyphar/filepath-securejoin v0.3.1/go.mod h1:F7i41x/9cBF7lzCrVsYs9fuzwRZm4NQsGTBdpp6mETc=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
|
@ -75,8 +75,8 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
|
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
|
||||||
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
|
||||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
|
@ -97,12 +97,10 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
|
||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
|
@ -110,13 +108,11 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
@ -130,15 +126,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
|
@ -146,14 +142,12 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,10 @@ import (
|
||||||
"github.com/apricote/releaser-pleaser/internal/markdown/extensions/ast"
|
"github.com/apricote/releaser-pleaser/internal/markdown/extensions/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
var sectionStartRegex = regexp.MustCompile(`^<!-- section-start (.+) -->`)
|
var (
|
||||||
var sectionEndRegex = regexp.MustCompile(`^<!-- section-end (.+) -->`)
|
sectionStartRegex = regexp.MustCompile(`^<!-- section-start (.+) -->`)
|
||||||
|
sectionEndRegex = regexp.MustCompile(`^<!-- section-end (.+) -->`)
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
sectionTrigger = "<!--"
|
sectionTrigger = "<!--"
|
||||||
|
|
@ -21,8 +23,7 @@ const (
|
||||||
SectionEndFormat = "<!-- section-end %s -->"
|
SectionEndFormat = "<!-- section-end %s -->"
|
||||||
)
|
)
|
||||||
|
|
||||||
type sectionParser struct {
|
type sectionParser struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (s *sectionParser) Open(_ gast.Node, reader text.Reader, _ parser.Context) (gast.Node, parser.State) {
|
func (s *sectionParser) Open(_ gast.Node, reader text.Reader, _ parser.Context) (gast.Node, parser.State) {
|
||||||
line, _ := reader.PeekLine()
|
line, _ := reader.PeekLine()
|
||||||
|
|
@ -75,8 +76,7 @@ func (s *sectionParser) Trigger() []byte {
|
||||||
return []byte(sectionTrigger)
|
return []byte(sectionTrigger)
|
||||||
}
|
}
|
||||||
|
|
||||||
type section struct {
|
type section struct{}
|
||||||
}
|
|
||||||
|
|
||||||
// Section is an extension that allow you to use group content under a shared parent ast node.
|
// Section is an extension that allow you to use group content under a shared parent ast node.
|
||||||
var Section = §ion{}
|
var Section = §ion{}
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ func (r *Renderer) writeByte(w io.Writer, c byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteString writes a string to an io.Writer, ensuring that appropriate indentation and prefices are added at the
|
// WriteString writes a string to an io.Writer, ensuring that appropriate indentation and prefixes are added at the
|
||||||
// beginning of each line.
|
// beginning of each line.
|
||||||
func (r *Renderer) writeString(w io.Writer, s string) (int, error) {
|
func (r *Renderer) writeString(w io.Writer, s string) (int, error) {
|
||||||
n, err := r.write(w, []byte(s))
|
n, err := r.write(w, []byte(s))
|
||||||
|
|
@ -178,7 +178,7 @@ func (r *Renderer) popPrefix() {
|
||||||
|
|
||||||
// OpenBlock ensures that each block begins on a new line, and that blank lines are inserted before blocks as
|
// OpenBlock ensures that each block begins on a new line, and that blank lines are inserted before blocks as
|
||||||
// indicated by node.HasPreviousBlankLines.
|
// indicated by node.HasPreviousBlankLines.
|
||||||
func (r *Renderer) openBlock(w util.BufWriter, source []byte, node ast.Node) error {
|
func (r *Renderer) openBlock(w util.BufWriter, _ []byte, node ast.Node) error {
|
||||||
r.openBlocks = append(r.openBlocks, blockState{
|
r.openBlocks = append(r.openBlocks, blockState{
|
||||||
node: node,
|
node: node,
|
||||||
fresh: true,
|
fresh: true,
|
||||||
|
|
@ -222,7 +222,7 @@ func (r *Renderer) closeBlock(w io.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderDocument renders an *ast.Document node to the given BufWriter.
|
// RenderDocument renders an *ast.Document node to the given BufWriter.
|
||||||
func (r *Renderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, enter bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderDocument(_ util.BufWriter, _ []byte, _ ast.Node, _ bool) (ast.WalkStatus, error) {
|
||||||
r.listStack, r.prefixStack, r.prefix, r.atNewline = nil, nil, nil, false
|
r.listStack, r.prefixStack, r.prefix, r.atNewline = nil, nil, nil, false
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
@ -331,7 +331,7 @@ func (r *Renderer) renderFencedCodeBlock(w util.BufWriter, source []byte, node a
|
||||||
return ast.WalkStop, fmt.Errorf(": %w", err)
|
return ast.WalkStop, fmt.Errorf(": %w", err)
|
||||||
}
|
}
|
||||||
if err := r.writeByte(w, '\n'); err != nil {
|
if err := r.writeByte(w, '\n'); err != nil {
|
||||||
return ast.WalkStop, nil
|
return ast.WalkStop, fmt.Errorf(": %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the contents of the fenced code block.
|
// Write the contents of the fenced code block.
|
||||||
|
|
@ -594,7 +594,7 @@ func (r *Renderer) renderCodeSpan(w util.BufWriter, source []byte, node ast.Node
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderEmphasis renders an *ast.Emphasis node to the given BufWriter.
|
// RenderEmphasis renders an *ast.Emphasis node to the given BufWriter.
|
||||||
func (r *Renderer) renderEmphasis(w util.BufWriter, source []byte, node ast.Node, enter bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderEmphasis(w util.BufWriter, _ []byte, node ast.Node, _ bool) (ast.WalkStatus, error) {
|
||||||
em := node.(*ast.Emphasis)
|
em := node.(*ast.Emphasis)
|
||||||
if _, err := r.writeString(w, strings.Repeat("*", em.Level)); err != nil {
|
if _, err := r.writeString(w, strings.Repeat("*", em.Level)); err != nil {
|
||||||
return ast.WalkStop, fmt.Errorf(": %w", err)
|
return ast.WalkStop, fmt.Errorf(": %w", err)
|
||||||
|
|
@ -663,7 +663,7 @@ func (r *Renderer) renderLinkOrImage(w util.BufWriter, open string, dest, title
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderImage renders an *ast.Image node to the given BufWriter.
|
// RenderImage renders an *ast.Image node to the given BufWriter.
|
||||||
func (r *Renderer) renderImage(w util.BufWriter, source []byte, node ast.Node, enter bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderImage(w util.BufWriter, _ []byte, node ast.Node, enter bool) (ast.WalkStatus, error) {
|
||||||
img := node.(*ast.Image)
|
img := node.(*ast.Image)
|
||||||
if err := r.renderLinkOrImage(w, "![", img.Destination, img.Title, enter); err != nil {
|
if err := r.renderLinkOrImage(w, "![", img.Destination, img.Title, enter); err != nil {
|
||||||
return ast.WalkStop, fmt.Errorf(": %w", err)
|
return ast.WalkStop, fmt.Errorf(": %w", err)
|
||||||
|
|
@ -672,7 +672,7 @@ func (r *Renderer) renderImage(w util.BufWriter, source []byte, node ast.Node, e
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderLink renders an *ast.Link node to the given BufWriter.
|
// RenderLink renders an *ast.Link node to the given BufWriter.
|
||||||
func (r *Renderer) renderLink(w util.BufWriter, source []byte, node ast.Node, enter bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderLink(w util.BufWriter, _ []byte, node ast.Node, enter bool) (ast.WalkStatus, error) {
|
||||||
link := node.(*ast.Link)
|
link := node.(*ast.Link)
|
||||||
if err := r.renderLinkOrImage(w, "[", link.Destination, link.Title, enter); err != nil {
|
if err := r.renderLinkOrImage(w, "[", link.Destination, link.Title, enter); err != nil {
|
||||||
return ast.WalkStop, fmt.Errorf(": %w", err)
|
return ast.WalkStop, fmt.Errorf(": %w", err)
|
||||||
|
|
@ -724,7 +724,7 @@ func (r *Renderer) renderText(w util.BufWriter, source []byte, node ast.Node, en
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderString renders an *ast.String node to the given BufWriter.
|
// RenderString renders an *ast.String node to the given BufWriter.
|
||||||
func (r *Renderer) renderString(w util.BufWriter, source []byte, node ast.Node, enter bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderString(w util.BufWriter, _ []byte, node ast.Node, enter bool) (ast.WalkStatus, error) {
|
||||||
if !enter {
|
if !enter {
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
@ -801,7 +801,7 @@ func (r *Renderer) renderTableRow(w util.BufWriter, source []byte, node ast.Node
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) renderTableCell(w util.BufWriter, source []byte, node ast.Node, enter bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderTableCell(w util.BufWriter, _ []byte, node ast.Node, enter bool) (ast.WalkStatus, error) {
|
||||||
if !enter {
|
if !enter {
|
||||||
if node.NextSibling() != nil {
|
if node.NextSibling() != nil {
|
||||||
if _, err := r.writeString(w, " | "); err != nil {
|
if _, err := r.writeString(w, " | "); err != nil {
|
||||||
|
|
@ -813,14 +813,14 @@ func (r *Renderer) renderTableCell(w util.BufWriter, source []byte, node ast.Nod
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) renderStrikethrough(w util.BufWriter, source []byte, node ast.Node, enter bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderStrikethrough(w util.BufWriter, _ []byte, _ ast.Node, _ bool) (ast.WalkStatus, error) {
|
||||||
if _, err := r.writeString(w, "~~"); err != nil {
|
if _, err := r.writeString(w, "~~"); err != nil {
|
||||||
return ast.WalkStop, fmt.Errorf(": %w", err)
|
return ast.WalkStop, fmt.Errorf(": %w", err)
|
||||||
}
|
}
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) renderTaskCheckBox(w util.BufWriter, source []byte, node ast.Node, enter bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderTaskCheckBox(w util.BufWriter, _ []byte, node ast.Node, enter bool) (ast.WalkStatus, error) {
|
||||||
if enter {
|
if enter {
|
||||||
var fill byte = ' '
|
var fill byte = ' '
|
||||||
if task := node.(*exast.TaskCheckBox); task.IsChecked {
|
if task := node.(*exast.TaskCheckBox); task.IsChecked {
|
||||||
|
|
|
||||||
|
|
@ -11,25 +11,26 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var author = &object.Signature{
|
||||||
author = &object.Signature{
|
Name: "releaser-pleaser",
|
||||||
Name: "releaser-pleaser",
|
When: time.Date(2020, 01, 01, 01, 01, 01, 01, time.UTC),
|
||||||
When: time.Date(2020, 01, 01, 01, 01, 01, 01, time.UTC),
|
}
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type CommitOption func(*commitOptions)
|
type CommitOption func(*commitOptions)
|
||||||
|
|
||||||
type commitOptions struct {
|
type commitOptions struct {
|
||||||
cleanFiles bool
|
cleanFiles bool
|
||||||
files []commitFile
|
files []commitFile
|
||||||
tags []string
|
tags []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type commitFile struct {
|
type commitFile struct {
|
||||||
path string
|
path string
|
||||||
content string
|
content string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Commit func(*testing.T, *git.Repository) error
|
type Commit func(*testing.T, *git.Repository) error
|
||||||
|
|
||||||
type Repo func(*testing.T) *git.Repository
|
type Repo func(*testing.T) *git.Repository
|
||||||
|
|
||||||
func WithCommit(message string, options ...CommitOption) Commit {
|
func WithCommit(message string, options ...CommitOption) Commit {
|
||||||
|
|
@ -61,9 +62,9 @@ func WithCommit(message string, options ...CommitOption) Commit {
|
||||||
for _, fileInfo := range opts.files {
|
for _, fileInfo := range opts.files {
|
||||||
file, err := wt.Filesystem.Create(fileInfo.path)
|
file, err := wt.Filesystem.Create(fileInfo.path)
|
||||||
require.NoError(t, err, "failed to create file %q", fileInfo.path)
|
require.NoError(t, err, "failed to create file %q", fileInfo.path)
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
_, err = file.Write([]byte(fileInfo.content))
|
_, err = file.Write([]byte(fileInfo.content))
|
||||||
|
file.Close()
|
||||||
require.NoError(t, err, "failed to write content to file %q", fileInfo.path)
|
require.NoError(t, err, "failed to write content to file %q", fileInfo.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,7 +84,6 @@ func WithCommit(message string, options ...CommitOption) Commit {
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
94
releasepr.go
94
releasepr.go
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/yuin/goldmark/ast"
|
"github.com/yuin/goldmark/ast"
|
||||||
|
|
@ -30,11 +31,14 @@ func init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReleasePullRequest
|
||||||
|
//
|
||||||
|
// TODO: Reuse [PullRequest]
|
||||||
type ReleasePullRequest struct {
|
type ReleasePullRequest struct {
|
||||||
ID int
|
ID int
|
||||||
Title string
|
Title string
|
||||||
Description string
|
Description string
|
||||||
Labels []string
|
Labels []Label
|
||||||
|
|
||||||
Head string
|
Head string
|
||||||
ReleaseCommit *Commit
|
ReleaseCommit *Commit
|
||||||
|
|
@ -43,11 +47,11 @@ type ReleasePullRequest struct {
|
||||||
func NewReleasePullRequest(head, branch, version, changelogEntry string) (*ReleasePullRequest, error) {
|
func NewReleasePullRequest(head, branch, version, changelogEntry string) (*ReleasePullRequest, error) {
|
||||||
rp := &ReleasePullRequest{
|
rp := &ReleasePullRequest{
|
||||||
Head: head,
|
Head: head,
|
||||||
Labels: []string{LabelReleasePending},
|
Labels: []Label{LabelReleasePending},
|
||||||
}
|
}
|
||||||
|
|
||||||
rp.SetTitle(branch, version)
|
rp.SetTitle(branch, version)
|
||||||
if err := rp.SetDescription(changelogEntry); err != nil {
|
if err := rp.SetDescription(changelogEntry, ReleaseOverrides{}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,7 +61,7 @@ func NewReleasePullRequest(head, branch, version, changelogEntry string) (*Relea
|
||||||
type ReleaseOverrides struct {
|
type ReleaseOverrides struct {
|
||||||
Prefix string
|
Prefix string
|
||||||
Suffix string
|
Suffix string
|
||||||
// TODO: Doing the changelog for normal releases after previews requires to know about this while fetching the changesets
|
// TODO: Doing the changelog for normal releases after previews requires to know about this while fetching the commits
|
||||||
NextVersionType NextVersionType
|
NextVersionType NextVersionType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,18 +92,20 @@ func (n NextVersionType) String() string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PR Labels
|
// Label is the string identifier of a pull/merge request label on the forge.
|
||||||
const (
|
type Label string
|
||||||
LabelNextVersionTypeNormal = "rp-next-version::normal"
|
|
||||||
LabelNextVersionTypeRC = "rp-next-version::rc"
|
|
||||||
LabelNextVersionTypeBeta = "rp-next-version::beta"
|
|
||||||
LabelNextVersionTypeAlpha = "rp-next-version::alpha"
|
|
||||||
|
|
||||||
LabelReleasePending = "rp-release::pending"
|
const (
|
||||||
LabelReleaseTagged = "rp-release::tagged"
|
LabelNextVersionTypeNormal Label = "rp-next-version::normal"
|
||||||
|
LabelNextVersionTypeRC Label = "rp-next-version::rc"
|
||||||
|
LabelNextVersionTypeBeta Label = "rp-next-version::beta"
|
||||||
|
LabelNextVersionTypeAlpha Label = "rp-next-version::alpha"
|
||||||
|
|
||||||
|
LabelReleasePending Label = "rp-release::pending"
|
||||||
|
LabelReleaseTagged Label = "rp-release::tagged"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Labels = []string{
|
var KnownLabels = []Label{
|
||||||
LabelNextVersionTypeNormal,
|
LabelNextVersionTypeNormal,
|
||||||
LabelNextVersionTypeRC,
|
LabelNextVersionTypeRC,
|
||||||
LabelNextVersionTypeBeta,
|
LabelNextVersionTypeBeta,
|
||||||
|
|
@ -115,7 +121,6 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MarkdownSectionOverrides = "overrides"
|
|
||||||
MarkdownSectionChangelog = "changelog"
|
MarkdownSectionChangelog = "changelog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -150,6 +155,9 @@ func (pr *ReleasePullRequest) parseVersioningFlags(overrides ReleaseOverrides) R
|
||||||
overrides.NextVersionType = NextVersionTypeBeta
|
overrides.NextVersionType = NextVersionTypeBeta
|
||||||
case LabelNextVersionTypeAlpha:
|
case LabelNextVersionTypeAlpha:
|
||||||
overrides.NextVersionType = NextVersionTypeAlpha
|
overrides.NextVersionType = NextVersionTypeAlpha
|
||||||
|
case LabelReleasePending, LabelReleaseTagged:
|
||||||
|
// These labels have no effect on the versioning.
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -190,51 +198,6 @@ func (pr *ReleasePullRequest) parseDescription(overrides ReleaseOverrides) (Rele
|
||||||
return overrides, nil
|
return overrides, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pr *ReleasePullRequest) overridesText() (string, error) {
|
|
||||||
source := []byte(pr.Description)
|
|
||||||
gm := markdown.New()
|
|
||||||
descriptionAST := gm.Parser().Parse(text.NewReader(source))
|
|
||||||
|
|
||||||
var section *east.Section
|
|
||||||
|
|
||||||
err := ast.Walk(descriptionAST, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
|
|
||||||
if !entering {
|
|
||||||
return ast.WalkContinue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.Type() != ast.TypeBlock || n.Kind() != east.KindSection {
|
|
||||||
return ast.WalkContinue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
anySection, ok := n.(*east.Section)
|
|
||||||
if !ok {
|
|
||||||
return ast.WalkStop, fmt.Errorf("node has unexpected type: %T", n)
|
|
||||||
}
|
|
||||||
|
|
||||||
if anySection.Name != MarkdownSectionOverrides {
|
|
||||||
return ast.WalkContinue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
section = anySection
|
|
||||||
return ast.WalkStop, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if section == nil {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
outputBuffer := new(bytes.Buffer)
|
|
||||||
err = gm.Renderer().Render(outputBuffer, source, section)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return outputBuffer.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pr *ReleasePullRequest) ChangelogText() (string, error) {
|
func (pr *ReleasePullRequest) ChangelogText() (string, error) {
|
||||||
source := []byte(pr.Description)
|
source := []byte(pr.Description)
|
||||||
gm := markdown.New()
|
gm := markdown.New()
|
||||||
|
|
@ -289,11 +252,11 @@ func textFromLines(source []byte, n ast.Node) string {
|
||||||
content = append(content, line.Value(source)...)
|
content = append(content, line.Value(source)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(content)
|
return strings.TrimSpace(string(content))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pr *ReleasePullRequest) SetTitle(branch, version string) {
|
func (pr *ReleasePullRequest) SetTitle(branch, version string) {
|
||||||
pr.Title = fmt.Sprintf("chore(%s): release %s", branch, version)
|
pr.Title = fmt.Sprintf(TitleFormat, branch, version)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pr *ReleasePullRequest) Version() (string, error) {
|
func (pr *ReleasePullRequest) Version() (string, error) {
|
||||||
|
|
@ -305,14 +268,9 @@ func (pr *ReleasePullRequest) Version() (string, error) {
|
||||||
return matches[2], nil
|
return matches[2], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pr *ReleasePullRequest) SetDescription(changelogEntry string) error {
|
func (pr *ReleasePullRequest) SetDescription(changelogEntry string, overrides ReleaseOverrides) error {
|
||||||
overrides, err := pr.overridesText()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var description bytes.Buffer
|
var description bytes.Buffer
|
||||||
err = releasePRTemplate.Execute(&description, map[string]any{
|
err := releasePRTemplate.Execute(&description, map[string]any{
|
||||||
"Changelog": changelogEntry,
|
"Changelog": changelogEntry,
|
||||||
"Overrides": overrides,
|
"Overrides": overrides,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,32 @@
|
||||||
---
|
|
||||||
|
|
||||||
<!-- section-start changelog -->
|
<!-- section-start changelog -->
|
||||||
{{ .Changelog }}
|
{{ .Changelog }}
|
||||||
<!-- section-end changelog -->
|
<!-- section-end changelog -->
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## releaser-pleaser Instructions
|
<details>
|
||||||
{{ if .Overrides }}
|
<summary><h4>PR by <a href="https://github.com/apricote/releaser-pleaser">releaser-pleaser</a> 🤖</h4></summary>
|
||||||
{{- .Overrides -}}
|
|
||||||
{{- else }}
|
|
||||||
<!-- section-start overrides -->
|
|
||||||
> If you want to modify the proposed release, add you overrides here. You can learn more about the options in the docs.
|
|
||||||
|
|
||||||
### Prefix
|
If you want to modify the proposed release, add you overrides here. You can learn more about the options in the docs.
|
||||||
|
|
||||||
|
## Release Notes
|
||||||
|
|
||||||
|
### Prefix / Start
|
||||||
|
|
||||||
|
This will be added to the start of the release notes.
|
||||||
|
|
||||||
```rp-prefix
|
```rp-prefix
|
||||||
|
{{- if .Overrides.Prefix }}
|
||||||
|
{{ .Overrides.Prefix }}{{ end }}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Suffix
|
### Suffix / End
|
||||||
|
|
||||||
|
This will be added to the end of the release notes.
|
||||||
|
|
||||||
```rp-suffix
|
```rp-suffix
|
||||||
|
{{- if .Overrides.Suffix }}
|
||||||
|
{{ .Overrides.Suffix }}{{ end }}
|
||||||
```
|
```
|
||||||
|
|
||||||
<!-- section-end overrides -->
|
</details>
|
||||||
|
|
||||||
{{ end }}
|
|
||||||
#### PR by [releaser-pleaser](https://github.com/apricote/releaser-pleaser)
|
|
||||||
|
|
|
||||||
|
|
@ -49,121 +49,96 @@ func TestReleasePullRequest_SetDescription(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
pr *ReleasePullRequest
|
|
||||||
changelogEntry string
|
changelogEntry string
|
||||||
|
overrides ReleaseOverrides
|
||||||
want string
|
want string
|
||||||
wantErr assert.ErrorAssertionFunc
|
wantErr assert.ErrorAssertionFunc
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "empty description",
|
name: "no overrides",
|
||||||
pr: &ReleasePullRequest{},
|
|
||||||
changelogEntry: `## v1.0.0`,
|
changelogEntry: `## v1.0.0`,
|
||||||
want: `---
|
overrides: ReleaseOverrides{},
|
||||||
|
want: `<!-- section-start changelog -->
|
||||||
<!-- section-start changelog -->
|
|
||||||
## v1.0.0
|
## v1.0.0
|
||||||
<!-- section-end changelog -->
|
<!-- section-end changelog -->
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## releaser-pleaser Instructions
|
<details>
|
||||||
|
<summary><h4>PR by <a href="https://github.com/apricote/releaser-pleaser">releaser-pleaser</a> 🤖</h4></summary>
|
||||||
|
|
||||||
<!-- section-start overrides -->
|
If you want to modify the proposed release, add you overrides here. You can learn more about the options in the docs.
|
||||||
> If you want to modify the proposed release, add you overrides here. You can learn more about the options in the docs.
|
|
||||||
|
|
||||||
### Prefix
|
## Release Notes
|
||||||
|
|
||||||
|
### Prefix / Start
|
||||||
|
|
||||||
|
This will be added to the start of the release notes.
|
||||||
|
|
||||||
` + "```" + `rp-prefix
|
` + "```" + `rp-prefix
|
||||||
` + "```" + `
|
` + "```" + `
|
||||||
|
|
||||||
### Suffix
|
### Suffix / End
|
||||||
|
|
||||||
|
This will be added to the end of the release notes.
|
||||||
|
|
||||||
` + "```" + `rp-suffix
|
` + "```" + `rp-suffix
|
||||||
` + "```" + `
|
` + "```" + `
|
||||||
|
|
||||||
<!-- section-end overrides -->
|
</details>
|
||||||
|
|
||||||
|
|
||||||
#### PR by [releaser-pleaser](https://github.com/apricote/releaser-pleaser)
|
|
||||||
`,
|
`,
|
||||||
wantErr: assert.NoError,
|
wantErr: assert.NoError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "existing overrides",
|
name: "existing overrides",
|
||||||
pr: &ReleasePullRequest{
|
|
||||||
Description: `---
|
|
||||||
|
|
||||||
<!-- section-start changelog -->
|
|
||||||
## v0.1.0
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- bedazzle
|
|
||||||
<!-- section-end changelog -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## releaser-pleaser Instructions
|
|
||||||
|
|
||||||
<!-- section-start overrides -->
|
|
||||||
> If you want to modify the proposed release, add you overrides here. You can learn more about the options in the docs.
|
|
||||||
|
|
||||||
### Prefix
|
|
||||||
|
|
||||||
` + "```" + `rp-prefix
|
|
||||||
This release is awesome!
|
|
||||||
` + "```" + `
|
|
||||||
|
|
||||||
### Suffix
|
|
||||||
|
|
||||||
` + "```" + `rp-suffix
|
|
||||||
` + "```" + `
|
|
||||||
|
|
||||||
<!-- section-end overrides -->
|
|
||||||
|
|
||||||
#### PR by [releaser-pleaser](https://github.com/apricote/releaser-pleaser)
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
changelogEntry: `## v1.0.0`,
|
changelogEntry: `## v1.0.0`,
|
||||||
want: `---
|
overrides: ReleaseOverrides{
|
||||||
|
Prefix: "This release is awesome!",
|
||||||
<!-- section-start changelog -->
|
Suffix: "Fooo",
|
||||||
|
},
|
||||||
|
want: `<!-- section-start changelog -->
|
||||||
## v1.0.0
|
## v1.0.0
|
||||||
<!-- section-end changelog -->
|
<!-- section-end changelog -->
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## releaser-pleaser Instructions
|
<details>
|
||||||
|
<summary><h4>PR by <a href="https://github.com/apricote/releaser-pleaser">releaser-pleaser</a> 🤖</h4></summary>
|
||||||
|
|
||||||
<!-- section-start overrides -->
|
If you want to modify the proposed release, add you overrides here. You can learn more about the options in the docs.
|
||||||
> If you want to modify the proposed release, add you overrides here. You can learn more about the options in the docs.
|
|
||||||
|
|
||||||
### Prefix
|
## Release Notes
|
||||||
|
|
||||||
|
### Prefix / Start
|
||||||
|
|
||||||
|
This will be added to the start of the release notes.
|
||||||
|
|
||||||
` + "```" + `rp-prefix
|
` + "```" + `rp-prefix
|
||||||
This release is awesome!
|
This release is awesome!
|
||||||
` + "```" + `
|
` + "```" + `
|
||||||
|
|
||||||
### Suffix
|
### Suffix / End
|
||||||
|
|
||||||
|
This will be added to the end of the release notes.
|
||||||
|
|
||||||
` + "```" + `rp-suffix
|
` + "```" + `rp-suffix
|
||||||
|
Fooo
|
||||||
` + "```" + `
|
` + "```" + `
|
||||||
|
|
||||||
<!-- section-end overrides -->
|
</details>
|
||||||
|
|
||||||
#### PR by [releaser-pleaser](https://github.com/apricote/releaser-pleaser)
|
|
||||||
`,
|
`,
|
||||||
wantErr: assert.NoError,
|
wantErr: assert.NoError,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
err := tt.pr.SetDescription(tt.changelogEntry)
|
pr := &ReleasePullRequest{}
|
||||||
|
err := pr.SetDescription(tt.changelogEntry, tt.overrides)
|
||||||
if !tt.wantErr(t, err) {
|
if !tt.wantErr(t, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, tt.want, tt.pr.Description)
|
assert.Equal(t, tt.want, pr.Description)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
406
releaserpleaser.go
Normal file
406
releaserpleaser.go
Normal file
|
|
@ -0,0 +1,406 @@
|
||||||
|
package rp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
|
"github.com/go-git/go-git/v5/config"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PullRequestBranchFormat = "releaser-pleaser--branches--%s"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReleaserPleaser struct {
|
||||||
|
forge Forge
|
||||||
|
logger *slog.Logger
|
||||||
|
targetBranch string
|
||||||
|
commitParser CommitParser
|
||||||
|
nextVersion VersioningStrategy
|
||||||
|
extraFiles []string
|
||||||
|
updaters []Updater
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(forge Forge, logger *slog.Logger, targetBranch string, commitParser CommitParser, versioningStrategy VersioningStrategy, extraFiles []string, updaters []Updater) *ReleaserPleaser {
|
||||||
|
return &ReleaserPleaser{
|
||||||
|
forge: forge,
|
||||||
|
logger: logger,
|
||||||
|
targetBranch: targetBranch,
|
||||||
|
commitParser: commitParser,
|
||||||
|
nextVersion: versioningStrategy,
|
||||||
|
extraFiles: extraFiles,
|
||||||
|
updaters: updaters,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *ReleaserPleaser) EnsureLabels(ctx context.Context) error {
|
||||||
|
// TODO: Wrap Error
|
||||||
|
return rp.forge.EnsureLabelsExist(ctx, KnownLabels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *ReleaserPleaser) Run(ctx context.Context) error {
|
||||||
|
err := rp.runOnboarding(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to onboard repository: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rp.runCreatePendingReleases(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create pending releases: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rp.runReconcileReleasePR(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to reconcile release pull request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *ReleaserPleaser) runOnboarding(ctx context.Context) error {
|
||||||
|
err := rp.EnsureLabels(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to ensure all labels exist: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *ReleaserPleaser) runCreatePendingReleases(ctx context.Context) error {
|
||||||
|
logger := rp.logger.With("method", "runCreatePendingReleases")
|
||||||
|
|
||||||
|
logger.InfoContext(ctx, "checking for pending releases")
|
||||||
|
prs, err := rp.forge.PendingReleases(ctx, LabelReleasePending)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(prs) == 0 {
|
||||||
|
logger.InfoContext(ctx, "No pending releases found")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.InfoContext(ctx, "Found pending releases", "length", len(prs))
|
||||||
|
|
||||||
|
for _, pr := range prs {
|
||||||
|
err = rp.createPendingRelease(ctx, pr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *ReleaserPleaser) createPendingRelease(ctx context.Context, pr *ReleasePullRequest) error {
|
||||||
|
logger := rp.logger.With(
|
||||||
|
"method", "createPendingRelease",
|
||||||
|
"pr.id", pr.ID,
|
||||||
|
"pr.title", pr.Title)
|
||||||
|
|
||||||
|
if pr.ReleaseCommit == nil {
|
||||||
|
return fmt.Errorf("pull request is missing the merge commit")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("Creating release", "commit.hash", pr.ReleaseCommit.Hash)
|
||||||
|
|
||||||
|
version, err := pr.Version()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
changelog, err := pr.ChangelogText()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: pre-release & latest
|
||||||
|
|
||||||
|
logger.DebugContext(ctx, "Creating release on forge")
|
||||||
|
err = rp.forge.CreateRelease(ctx, *pr.ReleaseCommit, version, changelog, false, true)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create release on forge: %w", err)
|
||||||
|
}
|
||||||
|
logger.DebugContext(ctx, "created release", "release.title", version, "release.url", rp.forge.ReleaseURL(version))
|
||||||
|
|
||||||
|
logger.DebugContext(ctx, "updating pr labels")
|
||||||
|
err = rp.forge.SetPullRequestLabels(ctx, pr, []Label{LabelReleasePending}, []Label{LabelReleaseTagged})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.DebugContext(ctx, "updated pr labels")
|
||||||
|
|
||||||
|
logger.InfoContext(ctx, "Created release", "release.title", version, "release.url", rp.forge.ReleaseURL(version))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *ReleaserPleaser) runReconcileReleasePR(ctx context.Context) error {
|
||||||
|
logger := rp.logger.With("method", "runReconcileReleasePR")
|
||||||
|
|
||||||
|
releases, err := rp.forge.LatestTags(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if releases.Latest != nil {
|
||||||
|
logger.InfoContext(ctx, "found latest tag", "tag.hash", releases.Latest.Hash, "tag.name", releases.Latest.Name)
|
||||||
|
if releases.Stable != nil && releases.Latest.Hash != releases.Stable.Hash {
|
||||||
|
logger.InfoContext(ctx, "found stable tag", "tag.hash", releases.Stable.Hash, "tag.name", releases.Stable.Name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.InfoContext(ctx, "no latest tag found")
|
||||||
|
}
|
||||||
|
|
||||||
|
releasableCommits, err := rp.forge.CommitsSince(ctx, releases.Stable)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.InfoContext(ctx, "Found releasable commits", "length", len(releasableCommits))
|
||||||
|
|
||||||
|
// TODO: Handle commit overrides
|
||||||
|
analyzedCommits, err := rp.commitParser.Analyze(releasableCommits)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.InfoContext(ctx, "Analyzed commits", "length", len(analyzedCommits))
|
||||||
|
|
||||||
|
rpBranch := fmt.Sprintf(PullRequestBranchFormat, rp.targetBranch)
|
||||||
|
rpBranchRef := plumbing.NewBranchReferenceName(rpBranch)
|
||||||
|
// Check Forge for open PR
|
||||||
|
// Get any modifications from open PR
|
||||||
|
// Clone Repo
|
||||||
|
// Run Updaters + Changelog
|
||||||
|
// Upsert PR
|
||||||
|
pr, err := rp.forge.PullRequestForBranch(ctx, rpBranch)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if pr != nil {
|
||||||
|
logger.InfoContext(ctx, "found existing release pull request", "pr.id", pr.ID, "pr.title", pr.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(analyzedCommits) == 0 {
|
||||||
|
if pr != nil {
|
||||||
|
logger.InfoContext(ctx, "closing existing pull requests, no commits available", "pr.id", pr.ID, "pr.title", pr.Title)
|
||||||
|
err = rp.forge.ClosePullRequest(ctx, pr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.InfoContext(ctx, "No commits available for release")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var releaseOverrides ReleaseOverrides
|
||||||
|
if pr != nil {
|
||||||
|
releaseOverrides, err = pr.GetOverrides()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
versionBump := VersionBumpFromCommits(analyzedCommits)
|
||||||
|
// TODO: Set version in release pr
|
||||||
|
nextVersion, err := rp.nextVersion(releases, versionBump, releaseOverrides.NextVersionType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.InfoContext(ctx, "next version", "version", nextVersion)
|
||||||
|
|
||||||
|
logger.DebugContext(ctx, "cloning repository", "clone.url", rp.forge.CloneURL())
|
||||||
|
repo, err := CloneRepo(ctx, rp.forge.CloneURL(), rp.targetBranch, rp.forge.GitAuth())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to clone repository: %w", err)
|
||||||
|
}
|
||||||
|
worktree, err := repo.Worktree()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if branch, _ := repo.Branch(rpBranch); branch != nil {
|
||||||
|
logger.DebugContext(ctx, "deleting previous releaser-pleaser branch locally", "branch.name", rpBranch)
|
||||||
|
if err = repo.DeleteBranch(rpBranch); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = worktree.Checkout(&git.CheckoutOptions{
|
||||||
|
Branch: rpBranchRef,
|
||||||
|
Create: true,
|
||||||
|
}); err != nil {
|
||||||
|
return fmt.Errorf("failed to check out branch: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
changelogEntry, err := NewChangelogEntry(analyzedCommits, nextVersion, rp.forge.ReleaseURL(nextVersion), releaseOverrides.Prefix, releaseOverrides.Suffix)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to build changelog entry: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info for updaters
|
||||||
|
info := ReleaseInfo{Version: nextVersion, ChangelogEntry: changelogEntry}
|
||||||
|
|
||||||
|
updateFile := func(path string, updaters []Updater) error {
|
||||||
|
file, err := worktree.Filesystem.OpenFile(path, os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
content, err := io.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedContent := string(content)
|
||||||
|
|
||||||
|
for _, updater := range updaters {
|
||||||
|
updatedContent, err = updater.UpdateContent(updatedContent, info)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to run updater %T on file %s", updater, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = file.Truncate(0)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to replace file content: %w", err)
|
||||||
|
}
|
||||||
|
_, err = file.Seek(0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to replace file content: %w", err)
|
||||||
|
}
|
||||||
|
_, err = file.Write([]byte(updatedContent))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to replace file content: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = worktree.Add(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to add updated file to git worktree: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
_, err = worktree.Filesystem.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Check for non existing file or dirs
|
||||||
|
return fmt.Errorf("failed to run file updater because the file %s does not exist: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = updateFile(path, rp.updaters)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to run file updater: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseCommitMessage := fmt.Sprintf("chore(%s): release %s", rp.targetBranch, nextVersion)
|
||||||
|
releaseCommitHash, err := worktree.Commit(releaseCommitMessage, &git.CommitOptions{
|
||||||
|
Author: GitSignature(),
|
||||||
|
Committer: GitSignature(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to commit changes: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.InfoContext(ctx, "created release commit", "commit.hash", releaseCommitHash.String(), "commit.message", releaseCommitMessage)
|
||||||
|
|
||||||
|
newReleasePRChanges := true
|
||||||
|
|
||||||
|
// Check if anything changed in comparison to the remote branch (if exists)
|
||||||
|
if remoteRef, err := repo.Reference(plumbing.NewRemoteReferenceName(GitRemoteName, rpBranch), false); err != nil {
|
||||||
|
if err.Error() != "reference not found" {
|
||||||
|
// "reference not found" is expected and we should always push
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
remoteCommit, err := repo.CommitObject(remoteRef.Hash())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
localCommit, err := repo.CommitObject(releaseCommitHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
diff, err := localCommit.PatchContext(ctx, remoteCommit)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newReleasePRChanges = len(diff.FilePatches()) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if newReleasePRChanges {
|
||||||
|
pushRefSpec := config.RefSpec(fmt.Sprintf(
|
||||||
|
"+%s:%s",
|
||||||
|
rpBranchRef,
|
||||||
|
// This needs to be the local branch name, not the remotes/origin ref
|
||||||
|
// See https://stackoverflow.com/a/75727620
|
||||||
|
rpBranchRef,
|
||||||
|
))
|
||||||
|
logger.DebugContext(ctx, "pushing branch", "commit.hash", releaseCommitHash.String(), "branch.name", rpBranch, "refspec", pushRefSpec.String())
|
||||||
|
if err = repo.PushContext(ctx, &git.PushOptions{
|
||||||
|
RemoteName: GitRemoteName,
|
||||||
|
RefSpecs: []config.RefSpec{pushRefSpec},
|
||||||
|
Force: true,
|
||||||
|
Auth: rp.forge.GitAuth(),
|
||||||
|
}); err != nil {
|
||||||
|
return fmt.Errorf("failed to push branch: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.InfoContext(ctx, "pushed branch", "commit.hash", releaseCommitHash.String(), "branch.name", rpBranch, "refspec", pushRefSpec.String())
|
||||||
|
} else {
|
||||||
|
logger.InfoContext(ctx, "file content is already up-to-date in remote branch, skipping push")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open/Update PR
|
||||||
|
if pr == nil {
|
||||||
|
pr, err = NewReleasePullRequest(rpBranch, rp.targetBranch, nextVersion, changelogEntry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rp.forge.CreatePullRequest(ctx, pr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.InfoContext(ctx, "opened pull request", "pr.title", pr.Title, "pr.id", pr.ID)
|
||||||
|
} else {
|
||||||
|
pr.SetTitle(rp.targetBranch, nextVersion)
|
||||||
|
|
||||||
|
overrides, err := pr.GetOverrides()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = pr.SetDescription(changelogEntry, overrides)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rp.forge.UpdatePullRequest(ctx, pr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.InfoContext(ctx, "updated pull request", "pr.title", pr.Title, "pr.id", pr.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
47
updater.go
47
updater.go
|
|
@ -1,12 +1,47 @@
|
||||||
package rp
|
package rp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"github.com/go-git/go-git/v5"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RunUpdater(ctx context.Context, version string, worktree *git.Worktree) error {
|
var (
|
||||||
// TODO: Implement updater for Go,Python,ExtraFilesMarkers
|
GenericUpdaterSemVerRegex = regexp.MustCompile(`\d+\.\d+\.\d+(-[\w.]+)?(.*x-releaser-pleaser-version)`)
|
||||||
return nil
|
ChangelogUpdaterHeaderRegex = regexp.MustCompile(`^# Changelog\n`)
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReleaseInfo struct {
|
||||||
|
Version string
|
||||||
|
ChangelogEntry string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Updater interface {
|
||||||
|
UpdateContent(content string, info ReleaseInfo) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenericUpdater struct{}
|
||||||
|
|
||||||
|
func (u *GenericUpdater) UpdateContent(content string, info ReleaseInfo) (string, error) {
|
||||||
|
// We strip the "v" prefix to avoid adding/removing it from the users input.
|
||||||
|
version := strings.TrimPrefix(info.Version, "v")
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
129
updater_test.go
Normal file
129
updater_test.go
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
package rp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type updaterTestCase struct {
|
||||||
|
name string
|
||||||
|
content string
|
||||||
|
info ReleaseInfo
|
||||||
|
want string
|
||||||
|
wantErr assert.ErrorAssertionFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func runUpdaterTest(t *testing.T, updater Updater, tt updaterTestCase) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
got, err := updater.UpdateContent(tt.content, tt.info)
|
||||||
|
if !tt.wantErr(t, err, fmt.Sprintf("UpdateContent(%v, %v)", tt.content, tt.info)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Equalf(t, tt.want, got, "UpdateContent(%v, %v)", tt.content, tt.info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenericUpdater_UpdateContent(t *testing.T) {
|
||||||
|
updater := &GenericUpdater{}
|
||||||
|
|
||||||
|
tests := []updaterTestCase{
|
||||||
|
{
|
||||||
|
name: "single line",
|
||||||
|
content: "v1.0.0 // x-releaser-pleaser-version",
|
||||||
|
info: ReleaseInfo{
|
||||||
|
Version: "v1.2.0",
|
||||||
|
},
|
||||||
|
want: "v1.2.0 // x-releaser-pleaser-version",
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiline line",
|
||||||
|
content: "Foooo\n\v1.2.0\nv1.0.0 // x-releaser-pleaser-version\n",
|
||||||
|
info: ReleaseInfo{
|
||||||
|
Version: "v1.2.0",
|
||||||
|
},
|
||||||
|
want: "Foooo\n\v1.2.0\nv1.2.0 // x-releaser-pleaser-version\n",
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid existing version",
|
||||||
|
content: "1.0 // x-releaser-pleaser-version",
|
||||||
|
info: ReleaseInfo{
|
||||||
|
Version: "v1.2.0",
|
||||||
|
},
|
||||||
|
want: "1.0 // x-releaser-pleaser-version",
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "complicated line",
|
||||||
|
content: "version: v1.2.0-alpha.1 => Awesome, isnt it? x-releaser-pleaser-version foobar",
|
||||||
|
info: ReleaseInfo{
|
||||||
|
Version: "v1.2.0",
|
||||||
|
},
|
||||||
|
want: "version: v1.2.0 => Awesome, isnt it? x-releaser-pleaser-version foobar",
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
runUpdaterTest(t, updater, tt)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,11 @@ type Releases struct {
|
||||||
Stable *Tag
|
Stable *Tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Releases) NextVersion(versionBump conventionalcommits.VersionBump, nextVersionType NextVersionType) (string, error) {
|
type VersioningStrategy = func(Releases, conventionalcommits.VersionBump, NextVersionType) (string, error)
|
||||||
|
|
||||||
|
var _ VersioningStrategy = SemVerNextVersion
|
||||||
|
|
||||||
|
func SemVerNextVersion(r Releases, versionBump conventionalcommits.VersionBump, nextVersionType NextVersionType) (string, error) {
|
||||||
latest, err := parseSemverWithDefault(r.Latest)
|
latest, err := parseSemverWithDefault(r.Latest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to parse latest version: %w", err)
|
return "", fmt.Errorf("failed to parse latest version: %w", err)
|
||||||
|
|
@ -65,24 +69,22 @@ func (r Releases) NextVersion(versionBump conventionalcommits.VersionBump, nextV
|
||||||
return "v" + next.String(), nil
|
return "v" + next.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func VersionBumpFromChangesets(changesets []Changeset) conventionalcommits.VersionBump {
|
func VersionBumpFromCommits(commits []AnalyzedCommit) conventionalcommits.VersionBump {
|
||||||
bump := conventionalcommits.UnknownVersion
|
bump := conventionalcommits.UnknownVersion
|
||||||
|
|
||||||
for _, changeset := range changesets {
|
for _, commit := range commits {
|
||||||
for _, entry := range changeset.ChangelogEntries {
|
entryBump := conventionalcommits.UnknownVersion
|
||||||
entryBump := conventionalcommits.UnknownVersion
|
switch {
|
||||||
switch {
|
case commit.BreakingChange:
|
||||||
case entry.BreakingChange:
|
entryBump = conventionalcommits.MajorVersion
|
||||||
entryBump = conventionalcommits.MajorVersion
|
case commit.Type == "feat":
|
||||||
case entry.Type == "feat":
|
entryBump = conventionalcommits.MinorVersion
|
||||||
entryBump = conventionalcommits.MinorVersion
|
case commit.Type == "fix":
|
||||||
case entry.Type == "fix":
|
entryBump = conventionalcommits.PatchVersion
|
||||||
entryBump = conventionalcommits.PatchVersion
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if entryBump > bump {
|
if entryBump > bump {
|
||||||
bump = entryBump
|
bump = entryBump
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,23 +10,23 @@ import (
|
||||||
|
|
||||||
func TestReleases_NextVersion(t *testing.T) {
|
func TestReleases_NextVersion(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
|
releases Releases
|
||||||
versionBump conventionalcommits.VersionBump
|
versionBump conventionalcommits.VersionBump
|
||||||
nextVersionType NextVersionType
|
nextVersionType NextVersionType
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
releases Releases
|
args args
|
||||||
args args
|
want string
|
||||||
want string
|
wantErr assert.ErrorAssertionFunc
|
||||||
wantErr assert.ErrorAssertionFunc
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "simple bump (major)",
|
name: "simple bump (major)",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "v1.1.1"},
|
|
||||||
Stable: &Tag{Name: "v1.1.1"},
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "v1.1.1"},
|
||||||
|
Stable: &Tag{Name: "v1.1.1"},
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.MajorVersion,
|
versionBump: conventionalcommits.MajorVersion,
|
||||||
nextVersionType: NextVersionTypeUndefined,
|
nextVersionType: NextVersionTypeUndefined,
|
||||||
},
|
},
|
||||||
|
|
@ -35,12 +35,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "simple bump (minor)",
|
name: "simple bump (minor)",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "v1.1.1"},
|
|
||||||
Stable: &Tag{Name: "v1.1.1"},
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "v1.1.1"},
|
||||||
|
Stable: &Tag{Name: "v1.1.1"},
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.MinorVersion,
|
versionBump: conventionalcommits.MinorVersion,
|
||||||
nextVersionType: NextVersionTypeUndefined,
|
nextVersionType: NextVersionTypeUndefined,
|
||||||
},
|
},
|
||||||
|
|
@ -49,12 +48,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "simple bump (patch)",
|
name: "simple bump (patch)",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "v1.1.1"},
|
|
||||||
Stable: &Tag{Name: "v1.1.1"},
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "v1.1.1"},
|
||||||
|
Stable: &Tag{Name: "v1.1.1"},
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.PatchVersion,
|
versionBump: conventionalcommits.PatchVersion,
|
||||||
nextVersionType: NextVersionTypeUndefined,
|
nextVersionType: NextVersionTypeUndefined,
|
||||||
},
|
},
|
||||||
|
|
@ -63,12 +61,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "normal to prerelease (major)",
|
name: "normal to prerelease (major)",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "v1.1.1"},
|
|
||||||
Stable: &Tag{Name: "v1.1.1"},
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "v1.1.1"},
|
||||||
|
Stable: &Tag{Name: "v1.1.1"},
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.MajorVersion,
|
versionBump: conventionalcommits.MajorVersion,
|
||||||
nextVersionType: NextVersionTypeRC,
|
nextVersionType: NextVersionTypeRC,
|
||||||
},
|
},
|
||||||
|
|
@ -77,12 +74,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "normal to prerelease (minor)",
|
name: "normal to prerelease (minor)",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "v1.1.1"},
|
|
||||||
Stable: &Tag{Name: "v1.1.1"},
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "v1.1.1"},
|
||||||
|
Stable: &Tag{Name: "v1.1.1"},
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.MinorVersion,
|
versionBump: conventionalcommits.MinorVersion,
|
||||||
nextVersionType: NextVersionTypeRC,
|
nextVersionType: NextVersionTypeRC,
|
||||||
},
|
},
|
||||||
|
|
@ -91,12 +87,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "normal to prerelease (patch)",
|
name: "normal to prerelease (patch)",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "v1.1.1"},
|
|
||||||
Stable: &Tag{Name: "v1.1.1"},
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "v1.1.1"},
|
||||||
|
Stable: &Tag{Name: "v1.1.1"},
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.PatchVersion,
|
versionBump: conventionalcommits.PatchVersion,
|
||||||
nextVersionType: NextVersionTypeRC,
|
nextVersionType: NextVersionTypeRC,
|
||||||
},
|
},
|
||||||
|
|
@ -105,11 +100,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "prerelease bump (major)",
|
name: "prerelease bump (major)",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "v2.0.0-rc.0"},
|
|
||||||
Stable: &Tag{Name: "v1.1.1"},
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "v2.0.0-rc.0"},
|
||||||
|
Stable: &Tag{Name: "v1.1.1"},
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.MajorVersion,
|
versionBump: conventionalcommits.MajorVersion,
|
||||||
nextVersionType: NextVersionTypeRC,
|
nextVersionType: NextVersionTypeRC,
|
||||||
},
|
},
|
||||||
|
|
@ -118,11 +113,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "prerelease bump (minor)",
|
name: "prerelease bump (minor)",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "v1.2.0-rc.0"},
|
|
||||||
Stable: &Tag{Name: "v1.1.1"},
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "v1.2.0-rc.0"},
|
||||||
|
Stable: &Tag{Name: "v1.1.1"},
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.MinorVersion,
|
versionBump: conventionalcommits.MinorVersion,
|
||||||
nextVersionType: NextVersionTypeRC,
|
nextVersionType: NextVersionTypeRC,
|
||||||
},
|
},
|
||||||
|
|
@ -131,11 +126,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "prerelease bump (patch)",
|
name: "prerelease bump (patch)",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "v1.1.2-rc.0"},
|
|
||||||
Stable: &Tag{Name: "v1.1.1"},
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "v1.1.2-rc.0"},
|
||||||
|
Stable: &Tag{Name: "v1.1.1"},
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.PatchVersion,
|
versionBump: conventionalcommits.PatchVersion,
|
||||||
nextVersionType: NextVersionTypeRC,
|
nextVersionType: NextVersionTypeRC,
|
||||||
},
|
},
|
||||||
|
|
@ -144,11 +139,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "prerelease different bump (major)",
|
name: "prerelease different bump (major)",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "v1.2.0-rc.0"},
|
|
||||||
Stable: &Tag{Name: "v1.1.1"},
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "v1.2.0-rc.0"},
|
||||||
|
Stable: &Tag{Name: "v1.1.1"},
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.MajorVersion,
|
versionBump: conventionalcommits.MajorVersion,
|
||||||
nextVersionType: NextVersionTypeRC,
|
nextVersionType: NextVersionTypeRC,
|
||||||
},
|
},
|
||||||
|
|
@ -157,11 +152,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "prerelease different bump (minor)",
|
name: "prerelease different bump (minor)",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "v1.1.2-rc.0"},
|
|
||||||
Stable: &Tag{Name: "v1.1.1"},
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "v1.1.2-rc.0"},
|
||||||
|
Stable: &Tag{Name: "v1.1.1"},
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.MinorVersion,
|
versionBump: conventionalcommits.MinorVersion,
|
||||||
nextVersionType: NextVersionTypeRC,
|
nextVersionType: NextVersionTypeRC,
|
||||||
},
|
},
|
||||||
|
|
@ -170,11 +165,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "prerelease to prerelease",
|
name: "prerelease to prerelease",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "v1.1.1-alpha.2"},
|
|
||||||
Stable: &Tag{Name: "v1.1.0"},
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "v1.1.1-alpha.2"},
|
||||||
|
Stable: &Tag{Name: "v1.1.0"},
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.PatchVersion,
|
versionBump: conventionalcommits.PatchVersion,
|
||||||
nextVersionType: NextVersionTypeRC,
|
nextVersionType: NextVersionTypeRC,
|
||||||
},
|
},
|
||||||
|
|
@ -183,11 +178,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "prerelease to normal (explicit)",
|
name: "prerelease to normal (explicit)",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "v1.1.1-alpha.2"},
|
|
||||||
Stable: &Tag{Name: "v1.1.0"},
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "v1.1.1-alpha.2"},
|
||||||
|
Stable: &Tag{Name: "v1.1.0"},
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.PatchVersion,
|
versionBump: conventionalcommits.PatchVersion,
|
||||||
nextVersionType: NextVersionTypeNormal,
|
nextVersionType: NextVersionTypeNormal,
|
||||||
},
|
},
|
||||||
|
|
@ -196,11 +191,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "prerelease to normal (implicit)",
|
name: "prerelease to normal (implicit)",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "v1.1.1-alpha.2"},
|
|
||||||
Stable: &Tag{Name: "v1.1.0"},
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "v1.1.1-alpha.2"},
|
||||||
|
Stable: &Tag{Name: "v1.1.0"},
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.PatchVersion,
|
versionBump: conventionalcommits.PatchVersion,
|
||||||
nextVersionType: NextVersionTypeUndefined,
|
nextVersionType: NextVersionTypeUndefined,
|
||||||
},
|
},
|
||||||
|
|
@ -209,11 +204,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "nil tag (major)",
|
name: "nil tag (major)",
|
||||||
releases: Releases{
|
|
||||||
Latest: nil,
|
|
||||||
Stable: nil,
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: nil,
|
||||||
|
Stable: nil,
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.MajorVersion,
|
versionBump: conventionalcommits.MajorVersion,
|
||||||
nextVersionType: NextVersionTypeUndefined,
|
nextVersionType: NextVersionTypeUndefined,
|
||||||
},
|
},
|
||||||
|
|
@ -222,11 +217,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "nil tag (minor)",
|
name: "nil tag (minor)",
|
||||||
releases: Releases{
|
|
||||||
Latest: nil,
|
|
||||||
Stable: nil,
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: nil,
|
||||||
|
Stable: nil,
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.MinorVersion,
|
versionBump: conventionalcommits.MinorVersion,
|
||||||
nextVersionType: NextVersionTypeUndefined,
|
nextVersionType: NextVersionTypeUndefined,
|
||||||
},
|
},
|
||||||
|
|
@ -235,11 +230,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "nil tag (patch)",
|
name: "nil tag (patch)",
|
||||||
releases: Releases{
|
|
||||||
Latest: nil,
|
|
||||||
Stable: nil,
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: nil,
|
||||||
|
Stable: nil,
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.PatchVersion,
|
versionBump: conventionalcommits.PatchVersion,
|
||||||
nextVersionType: NextVersionTypeUndefined,
|
nextVersionType: NextVersionTypeUndefined,
|
||||||
},
|
},
|
||||||
|
|
@ -248,11 +243,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "nil stable release (major)",
|
name: "nil stable release (major)",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "v1.1.1-rc.0"},
|
|
||||||
Stable: nil,
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "v1.1.1-rc.0"},
|
||||||
|
Stable: nil,
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.MajorVersion,
|
versionBump: conventionalcommits.MajorVersion,
|
||||||
nextVersionType: NextVersionTypeUndefined,
|
nextVersionType: NextVersionTypeUndefined,
|
||||||
},
|
},
|
||||||
|
|
@ -261,12 +256,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "nil stable release (minor)",
|
name: "nil stable release (minor)",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "v1.1.1-rc.0"},
|
|
||||||
Stable: nil,
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "v1.1.1-rc.0"},
|
||||||
|
Stable: nil,
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.MinorVersion,
|
versionBump: conventionalcommits.MinorVersion,
|
||||||
nextVersionType: NextVersionTypeUndefined,
|
nextVersionType: NextVersionTypeUndefined,
|
||||||
},
|
},
|
||||||
|
|
@ -275,12 +269,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "nil stable release (patch)",
|
name: "nil stable release (patch)",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "v1.1.1-rc.0"},
|
|
||||||
Stable: nil,
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "v1.1.1-rc.0"},
|
||||||
|
Stable: nil,
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.PatchVersion,
|
versionBump: conventionalcommits.PatchVersion,
|
||||||
nextVersionType: NextVersionTypeUndefined,
|
nextVersionType: NextVersionTypeUndefined,
|
||||||
},
|
},
|
||||||
|
|
@ -290,11 +283,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error on invalid tag semver",
|
name: "error on invalid tag semver",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "foodazzle"},
|
|
||||||
Stable: &Tag{Name: "foodazzle"},
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "foodazzle"},
|
||||||
|
Stable: &Tag{Name: "foodazzle"},
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.PatchVersion,
|
versionBump: conventionalcommits.PatchVersion,
|
||||||
nextVersionType: NextVersionTypeRC,
|
nextVersionType: NextVersionTypeRC,
|
||||||
},
|
},
|
||||||
|
|
@ -303,11 +296,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error on invalid tag prerelease",
|
name: "error on invalid tag prerelease",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "v1.1.1-rc.foo"},
|
|
||||||
Stable: &Tag{Name: "v1.1.1-rc.foo"},
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "v1.1.1-rc.foo"},
|
||||||
|
Stable: &Tag{Name: "v1.1.1-rc.foo"},
|
||||||
|
},
|
||||||
versionBump: conventionalcommits.PatchVersion,
|
versionBump: conventionalcommits.PatchVersion,
|
||||||
nextVersionType: NextVersionTypeRC,
|
nextVersionType: NextVersionTypeRC,
|
||||||
},
|
},
|
||||||
|
|
@ -316,11 +309,11 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error on invalid bump",
|
name: "error on invalid bump",
|
||||||
releases: Releases{
|
|
||||||
Latest: &Tag{Name: "v1.1.1"},
|
|
||||||
Stable: &Tag{Name: "v1.1.1"},
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
|
releases: Releases{
|
||||||
|
Latest: &Tag{Name: "v1.1.1"},
|
||||||
|
Stable: &Tag{Name: "v1.1.1"},
|
||||||
|
},
|
||||||
|
|
||||||
versionBump: conventionalcommits.UnknownVersion,
|
versionBump: conventionalcommits.UnknownVersion,
|
||||||
nextVersionType: NextVersionTypeUndefined,
|
nextVersionType: NextVersionTypeUndefined,
|
||||||
|
|
@ -331,95 +324,65 @@ func TestReleases_NextVersion(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := tt.releases.NextVersion(tt.args.versionBump, tt.args.nextVersionType)
|
got, err := SemVerNextVersion(tt.args.releases, tt.args.versionBump, tt.args.nextVersionType)
|
||||||
if !tt.wantErr(t, err, fmt.Sprintf("Releases(%v, %v).NextVersion(%v, %v)", tt.releases.Latest, tt.releases.Stable, tt.args.versionBump, tt.args.nextVersionType)) {
|
if !tt.wantErr(t, err, fmt.Sprintf("SemVerNextVersion(Releases(%v, %v), %v, %v)", tt.args.releases.Latest, tt.args.releases.Stable, tt.args.versionBump, tt.args.nextVersionType)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
assert.Equalf(t, tt.want, got, "Releases(%v, %v).NextVersion(%v, %v)", tt.releases.Latest, tt.releases.Stable, tt.args.versionBump, tt.args.nextVersionType)
|
assert.Equalf(t, tt.want, got, "SemVerNextVersion(Releases(%v, %v), %v, %v)", tt.args.releases.Latest, tt.args.releases.Stable, tt.args.versionBump, tt.args.nextVersionType)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVersionBumpFromChangesets(t *testing.T) {
|
func TestVersionBumpFromCommits(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
changesets []Changeset
|
analyzedCommits []AnalyzedCommit
|
||||||
want conventionalcommits.VersionBump
|
want conventionalcommits.VersionBump
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no entries (unknown)",
|
name: "no entries (unknown)",
|
||||||
changesets: []Changeset{},
|
analyzedCommits: []AnalyzedCommit{},
|
||||||
want: conventionalcommits.UnknownVersion,
|
want: conventionalcommits.UnknownVersion,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "non-release type (unknown)",
|
name: "non-release type (unknown)",
|
||||||
changesets: []Changeset{{ChangelogEntries: []AnalyzedCommit{{Type: "docs"}}}},
|
analyzedCommits: []AnalyzedCommit{{Type: "docs"}},
|
||||||
want: conventionalcommits.UnknownVersion,
|
want: conventionalcommits.UnknownVersion,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "single breaking (major)",
|
name: "single breaking (major)",
|
||||||
changesets: []Changeset{{ChangelogEntries: []AnalyzedCommit{{BreakingChange: true}}}},
|
analyzedCommits: []AnalyzedCommit{{BreakingChange: true}},
|
||||||
want: conventionalcommits.MajorVersion,
|
want: conventionalcommits.MajorVersion,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "single feat (minor)",
|
name: "single feat (minor)",
|
||||||
changesets: []Changeset{{ChangelogEntries: []AnalyzedCommit{{Type: "feat"}}}},
|
analyzedCommits: []AnalyzedCommit{{Type: "feat"}},
|
||||||
want: conventionalcommits.MinorVersion,
|
want: conventionalcommits.MinorVersion,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "single fix (patch)",
|
name: "single fix (patch)",
|
||||||
changesets: []Changeset{{ChangelogEntries: []AnalyzedCommit{{Type: "fix"}}}},
|
analyzedCommits: []AnalyzedCommit{{Type: "fix"}},
|
||||||
want: conventionalcommits.PatchVersion,
|
want: conventionalcommits.PatchVersion,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple changesets (major)",
|
name: "multiple entries (major)",
|
||||||
changesets: []Changeset{
|
analyzedCommits: []AnalyzedCommit{{Type: "fix"}, {BreakingChange: true}},
|
||||||
{ChangelogEntries: []AnalyzedCommit{{Type: "fix"}}},
|
want: conventionalcommits.MajorVersion,
|
||||||
{ChangelogEntries: []AnalyzedCommit{{BreakingChange: true}}},
|
|
||||||
},
|
|
||||||
want: conventionalcommits.MajorVersion,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple changesets (minor)",
|
name: "multiple entries (minor)",
|
||||||
changesets: []Changeset{
|
analyzedCommits: []AnalyzedCommit{{Type: "fix"}, {Type: "feat"}},
|
||||||
{ChangelogEntries: []AnalyzedCommit{{Type: "fix"}}},
|
want: conventionalcommits.MinorVersion,
|
||||||
{ChangelogEntries: []AnalyzedCommit{{Type: "feat"}}},
|
|
||||||
},
|
|
||||||
want: conventionalcommits.MinorVersion,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple changesets (patch)",
|
name: "multiple entries (patch)",
|
||||||
changesets: []Changeset{
|
analyzedCommits: []AnalyzedCommit{{Type: "docs"}, {Type: "fix"}},
|
||||||
{ChangelogEntries: []AnalyzedCommit{{Type: "docs"}}},
|
want: conventionalcommits.PatchVersion,
|
||||||
{ChangelogEntries: []AnalyzedCommit{{Type: "fix"}}},
|
|
||||||
},
|
|
||||||
want: conventionalcommits.PatchVersion,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "multiple entries in one changeset (major)",
|
|
||||||
changesets: []Changeset{
|
|
||||||
{ChangelogEntries: []AnalyzedCommit{{Type: "fix"}, {BreakingChange: true}}},
|
|
||||||
},
|
|
||||||
want: conventionalcommits.MajorVersion,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "multiple entries in one changeset (minor)",
|
|
||||||
changesets: []Changeset{
|
|
||||||
{ChangelogEntries: []AnalyzedCommit{{Type: "fix"}, {Type: "feat"}}},
|
|
||||||
},
|
|
||||||
want: conventionalcommits.MinorVersion,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "multiple entries in one changeset (patch)",
|
|
||||||
changesets: []Changeset{
|
|
||||||
{ChangelogEntries: []AnalyzedCommit{{Type: "docs"}, {Type: "fix"}}},
|
|
||||||
},
|
|
||||||
want: conventionalcommits.PatchVersion,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
assert.Equalf(t, tt.want, VersionBumpFromChangesets(tt.changesets), "VersionBumpFromChangesets(%v)", tt.changesets)
|
assert.Equalf(t, tt.want, VersionBumpFromCommits(tt.analyzedCommits), "VersionBumpFromCommits(%v)", tt.analyzedCommits)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue