Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95b7758c42 | ||
|
|
e88bce176e | ||
|
|
54a7b1e6d8 | ||
|
|
d6cf715a4a | ||
|
|
d4ab1e690c | ||
|
|
5637163ed0 | ||
|
|
f7aeaaca5c | ||
|
|
1357993978 | ||
|
|
0b27479ce3 | ||
|
|
1b8c6d789a | ||
|
|
f575d0ea75 |
No files matched your search
+14
-16
@@ -8,14 +8,14 @@ jobs:
|
||||
test:
|
||||
runs-on: [ubuntu-latest, amd64]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version: '1.26'
|
||||
go-version: '1.27'
|
||||
|
||||
- name: Install just
|
||||
run: |
|
||||
@@ -26,6 +26,8 @@ jobs:
|
||||
/root/bin/just vet test
|
||||
/root/bin/just build-linux-amd64
|
||||
./bin/gitbump_linux_amd64 --version
|
||||
- name: Lint
|
||||
uses: https://lab.zlymeda.dynu.net/actions/go-lint@master
|
||||
|
||||
build:
|
||||
runs-on: [ubuntu-latest, amd64]
|
||||
@@ -34,30 +36,26 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: true
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version: '1.26'
|
||||
go-version: '1.27'
|
||||
|
||||
- name: Bump & Tag
|
||||
run: go run main.go
|
||||
|
||||
notify-failure:
|
||||
notify:
|
||||
runs-on: [ubuntu-latest, amd64]
|
||||
needs: [test, build]
|
||||
if: always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped'))
|
||||
if: always()
|
||||
steps:
|
||||
- name: ntfy-failed-notifications
|
||||
uses: niniyas/ntfy-action@master
|
||||
- name: Notify
|
||||
uses: https://lab.zlymeda.dynu.net/actions/ci-notify@master
|
||||
with:
|
||||
url: ${{ secrets.NTFY_URL }}
|
||||
topic: ${{ secrets.NTFY_TOPIC }}
|
||||
priority: 5
|
||||
tags: flying_saucer,warning,🚨,🛠️,action,failed
|
||||
details: Workflow has failed!
|
||||
actions: 'default'
|
||||
needs: ${{ toJSON(needs) }}
|
||||
ntfy_token: ${{ secrets.NTFY_TOKEN }}
|
||||
@@ -19,7 +19,16 @@ jobs:
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.23'
|
||||
- uses: https://gitea.com/your-user/gitbump@master
|
||||
- uses: https://lab.zlymeda.dynu.net/actions/gitbump@master
|
||||
```
|
||||
|
||||
For a fixed base version with an incrementing fork suffix:
|
||||
|
||||
```yaml
|
||||
- uses: https://lab.zlymeda.dynu.net/actions/gitbump@master
|
||||
with:
|
||||
bump: suffix
|
||||
suffix: meda
|
||||
```
|
||||
|
||||
### Inputs
|
||||
@@ -27,7 +36,8 @@ jobs:
|
||||
| Input | Description | Default |
|
||||
|-------|-------------|---------|
|
||||
| `dry-run` | Show what would be done without making changes | `false` |
|
||||
| `bump` | Force specific bump type (`major`, `minor`, or `patch`) | |
|
||||
| `bump` | Force specific bump type (`major`, `minor`, `patch`, or `suffix`) | |
|
||||
| `suffix` | Suffix label used when creating the first suffix | |
|
||||
| `prefix` | Version prefix | `v` |
|
||||
| `no-push` | Create tag but don't push to remote | `false` |
|
||||
| `major-patterns` | Comma-separated list for major version bumps | `bump major,breaking change` |
|
||||
|
||||
+4
-1
@@ -6,7 +6,10 @@ inputs:
|
||||
required: false
|
||||
default: 'false'
|
||||
bump:
|
||||
description: 'Force specific bump type (major, minor, or patch)'
|
||||
description: 'Force specific bump type (major, minor, patch, or suffix)'
|
||||
required: false
|
||||
suffix:
|
||||
description: 'Suffix label used when creating the first suffix'
|
||||
required: false
|
||||
prefix:
|
||||
description: 'Version prefix (default "v")'
|
||||
|
||||
@@ -22,10 +22,20 @@ type Config struct {
|
||||
dryRun bool
|
||||
forcedBump string
|
||||
versionPrefix string
|
||||
suffixLabel string
|
||||
patterns Patterns
|
||||
noPush bool
|
||||
}
|
||||
|
||||
type taggedVersion struct {
|
||||
major int
|
||||
minor int
|
||||
patch int
|
||||
suffixLabel string
|
||||
suffixRevision int
|
||||
hasSuffix bool
|
||||
}
|
||||
|
||||
type Patterns struct {
|
||||
major []string
|
||||
minor []string
|
||||
@@ -74,11 +84,11 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
major, minor, patch, err := parseVersion(tag, config.versionPrefix)
|
||||
version, err := parseTaggedVersion(tag, config.versionPrefix)
|
||||
if err != nil {
|
||||
log.Fatalf("Error parsing version: %v", err)
|
||||
}
|
||||
fmt.Println("version:", major, minor, patch)
|
||||
fmt.Println("version:", version.major, version.minor, version.patch)
|
||||
|
||||
bumpType := getBumpType(commits, config)
|
||||
fmt.Println("bump type:", bumpType)
|
||||
@@ -87,7 +97,18 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
newVersion := calculateNewVersion(major, minor, patch, bumpType, config.versionPrefix)
|
||||
var newVersion string
|
||||
if bumpType == "suffix" {
|
||||
newVersion, err = calculateSuffixVersion(version, config.suffixLabel, config.versionPrefix)
|
||||
} else {
|
||||
if version.hasSuffix {
|
||||
log.Fatalf("Cannot apply %s bump to suffixed version %s", bumpType, tag)
|
||||
}
|
||||
newVersion = calculateNewVersion(version.major, version.minor, version.patch, bumpType, config.versionPrefix)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("Error calculating version: %v", err)
|
||||
}
|
||||
fmt.Println("new version:", newVersion)
|
||||
|
||||
if config.dryRun {
|
||||
@@ -109,8 +130,9 @@ func parseFlags() Config {
|
||||
|
||||
flag.BoolVar(&config.printVersion, "version", false, "Just print version")
|
||||
flag.BoolVar(&config.dryRun, "dry-run", getEnvBool("DRY_RUN", false), "Show what would be done without making changes")
|
||||
flag.StringVar(&config.forcedBump, "bump", os.Getenv("BUMP"), "Force specific bump type (major, minor, or patch)")
|
||||
flag.StringVar(&config.forcedBump, "bump", getEnv("BUMP", ""), "Force specific bump type (major, minor, patch, or suffix)")
|
||||
flag.StringVar(&config.versionPrefix, "prefix", getEnv("PREFIX", "v"), "Version prefix (default 'v')")
|
||||
flag.StringVar(&config.suffixLabel, "suffix", getEnv("SUFFIX", ""), "Suffix label used when creating the first suffix")
|
||||
flag.BoolVar(&config.noPush, "no-push", getEnvBool("NO_PUSH", false), "Create tag but don't push to remote")
|
||||
|
||||
majorPatterns := flag.String("major-patterns", getEnv("MAJOR_PATTERNS", "bump major,breaking change"), "Comma-separated list will be joined with '|' for major version bumps")
|
||||
@@ -127,20 +149,20 @@ func parseFlags() Config {
|
||||
chore: strings.Split(*chorePatterns, ","),
|
||||
}
|
||||
|
||||
if config.forcedBump != "" && !contains([]string{"major", "minor", "patch"}, config.forcedBump) {
|
||||
log.Fatalf("Invalid bump type: %s. Must be one of: major, minor, patch", config.forcedBump)
|
||||
if config.forcedBump != "" && !contains([]string{"major", "minor", "patch", "suffix"}, config.forcedBump) {
|
||||
log.Fatalf("Invalid bump type: %s. Must be one of: major, minor, patch, suffix", config.forcedBump)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func getEnv(key, fallback string) string {
|
||||
if value, ok := os.LookupEnv(key); ok {
|
||||
if value, ok := os.LookupEnv(key); ok && value != "" {
|
||||
return value
|
||||
}
|
||||
// Check for Gitea/GitHub Actions input prefix
|
||||
inputKey := "INPUT_" + strings.ReplaceAll(strings.ToUpper(key), "-", "_")
|
||||
if value, ok := os.LookupEnv(inputKey); ok {
|
||||
if value, ok := os.LookupEnv(inputKey); ok && value != "" {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
@@ -214,29 +236,60 @@ func getCommitsSinceTag(tag string) ([]string, error) {
|
||||
}
|
||||
|
||||
func parseVersion(tag, prefix string) (int, int, int, error) {
|
||||
tag = strings.TrimPrefix(tag, prefix)
|
||||
version, err := parseTaggedVersion(tag, prefix)
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
if version.hasSuffix {
|
||||
return 0, 0, 0, fmt.Errorf("version has a suffix: %s", tag)
|
||||
}
|
||||
return version.major, version.minor, version.patch, nil
|
||||
}
|
||||
|
||||
parts := strings.Split(tag, ".")
|
||||
func parseTaggedVersion(tag, prefix string) (taggedVersion, error) {
|
||||
rawVersion := strings.TrimPrefix(tag, prefix)
|
||||
baseVersion := rawVersion
|
||||
suffix := ""
|
||||
if separator := strings.IndexByte(rawVersion, '-'); separator >= 0 {
|
||||
baseVersion = rawVersion[:separator]
|
||||
suffix = rawVersion[separator+1:]
|
||||
}
|
||||
|
||||
parts := strings.Split(baseVersion, ".")
|
||||
if len(parts) != 3 {
|
||||
return 0, 0, 0, fmt.Errorf("invalid version format: %s", tag)
|
||||
return taggedVersion{}, fmt.Errorf("invalid version format: %s", rawVersion)
|
||||
}
|
||||
|
||||
major, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
if err != nil || major < 0 {
|
||||
return taggedVersion{}, fmt.Errorf("invalid major version: %s", parts[0])
|
||||
}
|
||||
|
||||
minor, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
if err != nil || minor < 0 {
|
||||
return taggedVersion{}, fmt.Errorf("invalid minor version: %s", parts[1])
|
||||
}
|
||||
|
||||
patch, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
if err != nil || patch < 0 {
|
||||
return taggedVersion{}, fmt.Errorf("invalid patch version: %s", parts[2])
|
||||
}
|
||||
|
||||
return major, minor, patch, nil
|
||||
version := taggedVersion{major: major, minor: minor, patch: patch}
|
||||
if suffix == "" {
|
||||
return version, nil
|
||||
}
|
||||
|
||||
suffixParts := strings.Split(suffix, ".")
|
||||
if len(suffixParts) != 2 || suffixParts[0] == "" {
|
||||
return taggedVersion{}, fmt.Errorf("invalid version suffix: %s", suffix)
|
||||
}
|
||||
revision, err := strconv.Atoi(suffixParts[1])
|
||||
if err != nil || revision < 1 {
|
||||
return taggedVersion{}, fmt.Errorf("invalid version suffix revision: %s", suffix)
|
||||
}
|
||||
version.suffixLabel = suffixParts[0]
|
||||
version.suffixRevision = revision
|
||||
version.hasSuffix = true
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func getBumpType(commits []string, config Config) string {
|
||||
@@ -289,6 +342,32 @@ func calculateNewVersion(major, minor, patch int, bumpType, prefix string) strin
|
||||
return fmt.Sprintf("%s%d.%d.%d", prefix, major, minor, patch)
|
||||
}
|
||||
|
||||
func calculateSuffixVersion(version taggedVersion, suffixLabel, prefix string) (string, error) {
|
||||
if version.hasSuffix {
|
||||
if suffixLabel != "" && suffixLabel != version.suffixLabel {
|
||||
return "", fmt.Errorf("suffix label %q does not match %q", suffixLabel, version.suffixLabel)
|
||||
}
|
||||
version.suffixRevision++
|
||||
return formatTaggedVersion(version, prefix), nil
|
||||
}
|
||||
|
||||
if suffixLabel == "" {
|
||||
return "", fmt.Errorf("suffix label is required for a base version")
|
||||
}
|
||||
version.suffixLabel = suffixLabel
|
||||
version.suffixRevision = 1
|
||||
version.hasSuffix = true
|
||||
return formatTaggedVersion(version, prefix), nil
|
||||
}
|
||||
|
||||
func formatTaggedVersion(version taggedVersion, prefix string) string {
|
||||
base := fmt.Sprintf("%s%d.%d.%d", prefix, version.major, version.minor, version.patch)
|
||||
if version.hasSuffix {
|
||||
return fmt.Sprintf("%s-%s.%d", base, version.suffixLabel, version.suffixRevision)
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func createAndPushTag(version string, noPush bool) error {
|
||||
if out, err := git("tag " + version); err != nil {
|
||||
return fmt.Errorf("error creating tag: %v; stdout: %v", err, out)
|
||||
|
||||
@@ -12,3 +12,89 @@ func TestParseVersion(t *testing.T) {
|
||||
t.Fatal("version does not match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTaggedVersion(t *testing.T) {
|
||||
version, err := parseTaggedVersion("v0.10.50-meda.2", "v")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if version.major != 0 || version.minor != 10 || version.patch != 50 {
|
||||
t.Fatalf("base version does not match: %+v", version)
|
||||
}
|
||||
if !version.hasSuffix || version.suffixLabel != "meda" || version.suffixRevision != 2 {
|
||||
t.Fatalf("suffix does not match: %+v", version)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseVersionRejectsSuffix(t *testing.T) {
|
||||
if _, _, _, err := parseVersion("v0.10.50-meda.2", "v"); err == nil {
|
||||
t.Fatal("expected suffixed version to be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEnvActionInput(t *testing.T) {
|
||||
t.Setenv("BUMP", "")
|
||||
t.Setenv("SUFFIX", "")
|
||||
t.Setenv("INPUT_BUMP", "suffix")
|
||||
t.Setenv("INPUT_SUFFIX", "meda")
|
||||
|
||||
if value := getEnv("BUMP", ""); value != "suffix" {
|
||||
t.Fatalf("expected suffix bump, got %s", value)
|
||||
}
|
||||
if value := getEnv("SUFFIX", ""); value != "meda" {
|
||||
t.Fatalf("expected meda suffix, got %s", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateSuffixVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
version taggedVersion
|
||||
suffix string
|
||||
expected string
|
||||
expectsErr bool
|
||||
}{
|
||||
{
|
||||
name: "first suffix",
|
||||
version: taggedVersion{major: 0, minor: 10, patch: 50},
|
||||
suffix: "meda",
|
||||
expected: "v0.10.50-meda.1",
|
||||
},
|
||||
{
|
||||
name: "increment suffix",
|
||||
version: taggedVersion{major: 0, minor: 10, patch: 50, suffixLabel: "meda", suffixRevision: 1, hasSuffix: true},
|
||||
suffix: "meda",
|
||||
expected: "v0.10.50-meda.2",
|
||||
},
|
||||
{
|
||||
name: "missing label",
|
||||
version: taggedVersion{major: 0, minor: 10, patch: 50},
|
||||
expectsErr: true,
|
||||
},
|
||||
{
|
||||
name: "label mismatch",
|
||||
version: taggedVersion{major: 0, minor: 10, patch: 50, suffixLabel: "meda", suffixRevision: 1, hasSuffix: true},
|
||||
suffix: "other",
|
||||
expectsErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual, err := calculateSuffixVersion(test.version, test.suffix, "v")
|
||||
if test.expectsErr {
|
||||
if err == nil {
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if actual != test.expected {
|
||||
t.Fatalf("expected %s, got %s", test.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user