feat(releaser): initial release v0.4.0
ci / vet, staticcheck, test, build (push) Failing after 3m26s
release / Build and publish release (push) Successful in 4m28s

Complete GitFlow release automation tool for Conventional Commits workflows:

- Core pipeline: branch parsing, tag discovery, commit analysis, version bump
- Maven pom.xml read/write, git commit/tag, HTTPS push with token auth
- GitLab release creation via API with auto-generated release notes
- Configurable via .releaser.yml (tag_prefix, branch_pattern, commit_message, pom_path, gitlab)
- CLI flags: --dry-run, --no-push, --no-commit, --tag-only, --branch, --pom, --tag-prefix, --branch-pattern
- Dockerfile (multi-stage Alpine), .releaser.gitlab-ci.yml reusable template
- Gitea CI (vet + staticcheck + test + build) and release (5-platform cross-compilation) workflows
- Taskfile with build/test/cov/lint/fuzz/ci/docker tasks
- 96% test coverage with real in-memory git repos and fuzz tests for all parsers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 00:07:53 +02:00
commit f0723e706a
30 changed files with 3959 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
package branch
import (
"fmt"
"regexp"
"strconv"
)
// DefaultBranchPattern matches "release/X.Y" or "origin/release/X.Y".
// Group 1 = major, group 2 = minor.
const DefaultBranchPattern = `^(?:.*/)?release/(\d+)\.(\d+)$`
// Info holds the major.minor version pinned by the branch name, plus the tag prefix from config.
type Info struct {
Major int
Minor int
TagPrefix string // set from config after Parse (default "v")
}
// Parse extracts major and minor from a branch name using the provided regex pattern.
// The pattern must contain exactly two capture groups: group 1 = major, group 2 = minor.
// TagPrefix is left at its zero value — the caller sets it from config.
func Parse(name, pattern string) (Info, error) {
re, err := regexp.Compile(pattern)
if err != nil {
return Info{}, fmt.Errorf("invalid branch pattern %q: %w", pattern, err)
}
m := re.FindStringSubmatch(name)
if len(m) < 3 {
return Info{}, fmt.Errorf("branch %q does not match pattern %q", name, pattern)
}
major, err := strconv.Atoi(m[1])
if err != nil {
return Info{}, fmt.Errorf("invalid major version in branch %q: %w", name, err)
}
minor, err := strconv.Atoi(m[2])
if err != nil {
return Info{}, fmt.Errorf("invalid minor version in branch %q: %w", name, err)
}
return Info{Major: major, Minor: minor}, nil
}
// MatchesTag checks whether a tag (e.g. "v1.2.3" or "1.2.3") belongs to this branch's version range.
// Uses TagPrefix to match the expected format. Returns the patch number and true on match.
func (i Info) MatchesTag(tag string) (patch int, ok bool) {
prefix := fmt.Sprintf("%s%d.%d.", i.TagPrefix, i.Major, i.Minor)
if len(tag) <= len(prefix) || tag[:len(prefix)] != prefix {
return 0, false
}
p, err := strconv.Atoi(tag[len(prefix):])
if err != nil {
return 0, false
}
return p, true
}
// TagName formats a version string (e.g. "1.2.4") into a full tag name (e.g. "v1.2.4" or "1.2.4").
func (i Info) TagName(version string) string {
return i.TagPrefix + version
}