< Summary - go-semantic-release Coverage

Information
Line coverage
100%
Covered lines: 13
Uncovered lines: 0
Coverable lines: 13
Total lines: 153
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
IsMaintenance0%00100%
IsPrerelease0%00100%
MaintenanceRange0%00100%

File(s)

/home/runner/work/go-semantic-release/go-semantic-release/internal/domain/branch_policy.go

#LineLine coverage
 1package domain
 2
 3import (
 4  "fmt"
 5  "path/filepath"
 6  "strconv"
 7  "strings"
 8)
 9
 10// BranchType categorizes how a branch participates in the release process.
 11type BranchType string
 12
 13const (
 14  BranchTypeRelease     BranchType = "release"
 15  BranchTypeMaintenance BranchType = "maintenance"
 16  BranchTypePrerelease  BranchType = "prerelease"
 17)
 18
 19// BranchPolicy defines version behavior for a specific branch or branch pattern.
 20type BranchPolicy struct {
 21  Name       string     `mapstructure:"name"`
 22  Channel    string     `mapstructure:"channel"`
 23  Prerelease bool       `mapstructure:"prerelease"`
 24  IsDefault  bool       `mapstructure:"is_default"`
 25  Range      string     `mapstructure:"range"` // maintenance range e.g. "1.x", "1.0.x"
 26  Type       BranchType `mapstructure:"branch_type"`
 27}
 28
 29// IsMaintenance returns true if this is a maintenance branch with a version range.
 930func (bp BranchPolicy) IsMaintenance() bool {
 931  return bp.Range != "" || bp.Type == BranchTypeMaintenance
 932}
 33
 34// IsPrerelease returns true if this is a prerelease branch.
 635func (bp BranchPolicy) IsPrerelease() bool {
 636  return bp.Prerelease || bp.Type == BranchTypePrerelease
 637}
 38
 39// MaintenanceRange parses the Range field into min/max version constraints.
 40// Range format: "N.x" (major only) or "N.N.x" (major.minor).
 1141func (bp BranchPolicy) MaintenanceRange() (minVer, maxVer Version, err error) {
 1142  r := bp.Range
 143  if r == "" {
 144    // Try to infer from branch name (e.g. "1.x", "1.0.x").
 145    r = bp.Name
 146  }
 47
 1148  return parseMaintenanceRange(r)
 49}
 50
 51func parseMaintenanceRange(r string) (minVer, maxVer Version, err error) {
 52  parts := strings.Split(r, ".")
 53  if len(parts) < 2 || parts[len(parts)-1] != "x" {
 54    return minVer, maxVer, fmt.Errorf("invalid maintenance range %q: expected N.x or N.N.x", r)
 55  }
 56
 57  major, err := strconv.Atoi(parts[0])
 58  if err != nil {
 59    return minVer, maxVer, fmt.Errorf("invalid major in range %q: %w", r, err)
 60  }
 61
 62  if len(parts) == 2 {
 63    // "N.x" — allows any minor/patch within this major.
 64    return NewVersion(major, 0, 0), NewVersion(major+1, 0, 0), nil
 65  }
 66
 67  if len(parts) == 3 {
 68    minor, err := strconv.Atoi(parts[1])
 69    if err != nil {
 70      return minVer, maxVer, fmt.Errorf("invalid minor in range %q: %w", r, err)
 71    }
 72    // "N.N.x" — allows any patch within this major.minor.
 73    return NewVersion(major, minor, 0), NewVersion(major, minor+1, 0), nil
 74  }
 75
 76  return minVer, maxVer, fmt.Errorf("invalid maintenance range %q", r)
 77}
 78
 79// VersionInRange checks if a version falls within the maintenance branch range.
 80// minVer <= version < maxVer.
 81func VersionInRange(version, minVer, maxVer Version) bool {
 82  if version.Equal(minVer) {
 83    return true
 84  }
 85  return (version.GreaterThan(minVer) || version.Equal(minVer)) && maxVer.GreaterThan(version)
 86}
 87
 88// ValidateMaintenanceVersion checks that a proposed version is valid for the maintenance range.
 89func ValidateMaintenanceVersion(proposed Version, policy BranchPolicy) error {
 90  if !policy.IsMaintenance() {
 91    return nil
 92  }
 93
 94  minVer, maxVer, err := policy.MaintenanceRange()
 95  if err != nil {
 96    return err
 97  }
 98
 99  if !VersionInRange(proposed, minVer, maxVer) {
 100    return fmt.Errorf(
 101      "version %s is outside maintenance range [%s, %s) for branch %q",
 102      proposed, minVer, maxVer, policy.Name,
 103    )
 104  }
 105  return nil
 106}
 107
 108// DefaultBranchPolicies returns the standard branch configuration matching semantic-release defaults.
 109func DefaultBranchPolicies() []BranchPolicy {
 110  return []BranchPolicy{
 111    {Name: "main", IsDefault: true, Type: BranchTypeRelease},
 112    {Name: "master", IsDefault: true, Type: BranchTypeRelease},
 113    {Name: "next", Prerelease: true, Channel: "next", Type: BranchTypePrerelease},
 114    {Name: "next-major", Prerelease: true, Channel: "next-major", Type: BranchTypePrerelease},
 115    {Name: "beta", Prerelease: true, Channel: "beta", Type: BranchTypePrerelease},
 116    {Name: "alpha", Prerelease: true, Channel: "alpha", Type: BranchTypePrerelease},
 117  }
 118}
 119
 120// FindBranchPolicy returns the matching policy for a branch name, or nil if none matches.
 121// Supports glob patterns in policy names.
 122func FindBranchPolicy(policies []BranchPolicy, branch string) *BranchPolicy {
 123  for i := range policies {
 124    if policies[i].Name == branch {
 125      return &policies[i]
 126    }
 127    // Try glob match for patterns like "release/*".
 128    if matched, _ := filepath.Match(policies[i].Name, branch); matched {
 129      return &policies[i]
 130    }
 131  }
 132
 133  // Auto-detect maintenance branches by name pattern (e.g. "1.x", "1.0.x").
 134  if isMaintenancePattern(branch) {
 135    return &BranchPolicy{
 136      Name:    branch,
 137      Range:   branch,
 138      Channel: "release-" + branch,
 139      Type:    BranchTypeMaintenance,
 140    }
 141  }
 142
 143  return nil
 144}
 145
 146func isMaintenancePattern(name string) bool {
 147  parts := strings.Split(name, ".")
 148  if len(parts) < 2 || parts[len(parts)-1] != "x" {
 149    return false
 150  }
 151  _, err := strconv.Atoi(parts[0])
 152  return err == nil
 153}