move to gitea actions
This commit is contained in:
337
main.go
Normal file
337
main.go
Normal file
@@ -0,0 +1,337 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var Version = "UNKNOWN"
|
||||
var BuildTime = "UNKNOWN"
|
||||
|
||||
const DefaultVersion = "v0.0.0"
|
||||
|
||||
type Config struct {
|
||||
printVersion bool
|
||||
dryRun bool
|
||||
forcedBump string
|
||||
versionPrefix string
|
||||
patterns Patterns
|
||||
noPush bool
|
||||
}
|
||||
|
||||
type Patterns struct {
|
||||
major []string
|
||||
minor []string
|
||||
patch []string
|
||||
chore []string
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Printf("version: %s\n", Version)
|
||||
fmt.Printf("built: %s\n", BuildTime)
|
||||
|
||||
config := parseFlags()
|
||||
|
||||
if config.printVersion {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("fetching tags:")
|
||||
if err := fetchTags(); err != nil {
|
||||
log.Fatalf("Error fetching tags: %v", err)
|
||||
}
|
||||
|
||||
if os.Getenv("DRONE") == "true" || os.Getenv("GITEA_ACTIONS") == "true" || os.Getenv("GITHUB_ACTIONS") == "true" {
|
||||
must(git("config user.email zlymeda@gmail.com"))
|
||||
must(git("config user.name \"gitbump\""))
|
||||
}
|
||||
|
||||
tag, err := getLatestTag()
|
||||
if err != nil {
|
||||
log.Fatalf("Error getting latest tag: %v", err)
|
||||
}
|
||||
fmt.Println("latest tag:", tag)
|
||||
|
||||
commits, err := getCommitsSinceTag(tag)
|
||||
if err != nil {
|
||||
log.Fatalf("Error getting commits: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("\nCommits since last tag:")
|
||||
for _, commit := range commits {
|
||||
fmt.Printf("- %s\n", commit)
|
||||
}
|
||||
|
||||
if len(commits) == 0 {
|
||||
fmt.Println("No commits found since last tag. No version bump needed.")
|
||||
return
|
||||
}
|
||||
|
||||
major, minor, patch, err := parseVersion(tag, config.versionPrefix)
|
||||
if err != nil {
|
||||
log.Fatalf("Error parsing version: %v", err)
|
||||
}
|
||||
fmt.Println("version:", major, minor, patch)
|
||||
|
||||
bumpType := getBumpType(commits, config)
|
||||
fmt.Println("bump type:", bumpType)
|
||||
if bumpType == "chore" {
|
||||
fmt.Println("Only chore commits found -> no version bump needed.")
|
||||
return
|
||||
}
|
||||
|
||||
newVersion := calculateNewVersion(major, minor, patch, bumpType, config.versionPrefix)
|
||||
fmt.Println("new version:", newVersion)
|
||||
|
||||
if config.dryRun {
|
||||
fmt.Printf("Dry run: Would bump version from %s to %s (bump type: %s)\n", tag, newVersion, bumpType)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Trying to bump version from %s to %s\n", tag, newVersion)
|
||||
err = createAndPushTag(newVersion, config.noPush)
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating/pushing tag: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully bumped version from %s to %s\n", tag, newVersion)
|
||||
}
|
||||
|
||||
func parseFlags() Config {
|
||||
config := 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.versionPrefix, "prefix", getEnv("PREFIX", "v"), "Version prefix (default 'v')")
|
||||
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")
|
||||
minorPatterns := flag.String("minor-patterns", getEnv("MINOR_PATTERNS", "bump minor,feat"), "Comma-separated list will be joined with '|' for minor version bumps")
|
||||
patchPatterns := flag.String("patch-patterns", getEnv("PATCH_PATTERNS", "bump patch,fix"), "Comma-separated list will be joined with '|' for patch version bumps")
|
||||
chorePatterns := flag.String("chore-patterns", getEnv("CHORE_PATTERNS", "chore,bump:skip"), "Comma-separated list will be joined with '|' for commits that should be ignored")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
config.patterns = Patterns{
|
||||
major: strings.Split(*majorPatterns, ","),
|
||||
minor: strings.Split(*minorPatterns, ","),
|
||||
patch: strings.Split(*patchPatterns, ","),
|
||||
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)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func getEnv(key, fallback string) string {
|
||||
if value, ok := os.LookupEnv(key); ok {
|
||||
return value
|
||||
}
|
||||
// Check for Gitea/GitHub Actions input prefix
|
||||
inputKey := "INPUT_" + strings.ReplaceAll(strings.ToUpper(key), "-", "_")
|
||||
if value, ok := os.LookupEnv(inputKey); ok {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func getEnvBool(key string, fallback bool) bool {
|
||||
if value, ok := os.LookupEnv(key); ok {
|
||||
b, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
return b
|
||||
}
|
||||
// Check for Gitea/GitHub Actions input prefix
|
||||
inputKey := "INPUT_" + strings.ReplaceAll(strings.ToUpper(key), "-", "_")
|
||||
if value, ok := os.LookupEnv(inputKey); ok {
|
||||
b, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
return b
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func contains(slice []string, item string) bool {
|
||||
for _, s := range slice {
|
||||
if s == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getLatestTag() (string, error) {
|
||||
output, err := git("describe --tags --abbrev=0")
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
if strings.Contains(err.Error(), "No names found") {
|
||||
return DefaultVersion, nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func fetchTags() error {
|
||||
_, err := git("fetch --tags")
|
||||
return err
|
||||
}
|
||||
|
||||
func getCommitsSinceTag(tag string) ([]string, error) {
|
||||
var cmd string
|
||||
|
||||
if tag == DefaultVersion {
|
||||
cmd = "log --pretty=format:%s"
|
||||
} else {
|
||||
cmd = "log --pretty=format:%s " + tag + "..HEAD"
|
||||
}
|
||||
|
||||
output, err := git(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commits := strings.Split(output, "\n")
|
||||
if len(commits) == 1 && commits[0] == "" {
|
||||
return []string{}, nil
|
||||
}
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
func parseVersion(tag, prefix string) (int, int, int, error) {
|
||||
tag = strings.TrimPrefix(tag, prefix)
|
||||
|
||||
parts := strings.Split(tag, ".")
|
||||
if len(parts) != 3 {
|
||||
return 0, 0, 0, fmt.Errorf("invalid version format: %s", tag)
|
||||
}
|
||||
|
||||
major, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
minor, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
patch, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
return major, minor, patch, nil
|
||||
}
|
||||
|
||||
func getBumpType(commits []string, config Config) string {
|
||||
// If bump type is forced via flag, use that
|
||||
if config.forcedBump != "" {
|
||||
return config.forcedBump
|
||||
}
|
||||
|
||||
majorPattern := regexp.MustCompile(`(?i)(` + strings.Join(config.patterns.major, "|") + `)`)
|
||||
minorPattern := regexp.MustCompile(`(?i)(` + strings.Join(config.patterns.minor, "|") + `)`)
|
||||
patchPattern := regexp.MustCompile(`(?i)(` + strings.Join(config.patterns.patch, "|") + `)`)
|
||||
chorePattern := regexp.MustCompile(`(?i)(` + strings.Join(config.patterns.chore, "|") + `)`)
|
||||
|
||||
chores := 0
|
||||
for _, commit := range commits {
|
||||
if majorPattern.MatchString(commit) {
|
||||
return "major"
|
||||
}
|
||||
if minorPattern.MatchString(commit) {
|
||||
return "minor"
|
||||
}
|
||||
if patchPattern.MatchString(commit) {
|
||||
return "patch"
|
||||
}
|
||||
if chorePattern.MatchString(commit) {
|
||||
chores++
|
||||
}
|
||||
}
|
||||
|
||||
// Default to patch bump if there are any non-chore commits
|
||||
if len(commits) > chores {
|
||||
return "patch"
|
||||
}
|
||||
|
||||
return "chore"
|
||||
}
|
||||
|
||||
func calculateNewVersion(major, minor, patch int, bumpType, prefix string) string {
|
||||
switch bumpType {
|
||||
case "major":
|
||||
major++
|
||||
minor = 0
|
||||
patch = 0
|
||||
case "minor":
|
||||
minor++
|
||||
patch = 0
|
||||
case "patch":
|
||||
patch++
|
||||
}
|
||||
return fmt.Sprintf("%s%d.%d.%d", prefix, major, minor, patch)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if noPush {
|
||||
fmt.Println("pushing disabled")
|
||||
return nil
|
||||
}
|
||||
|
||||
if out, err := git("push origin " + version); err != nil {
|
||||
return fmt.Errorf("error pushing tag: %v; stdout: %v", err, out)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func git(command string) (string, error) {
|
||||
parts := strings.Split(command, " ")
|
||||
|
||||
cmd := exec.Command("git", parts...)
|
||||
fmt.Printf("running %s\n", cmd)
|
||||
output, err := cmd.Output()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
|
||||
var ee *exec.ExitError
|
||||
if errors.As(err, &ee) {
|
||||
stderr := strings.TrimSpace(string(ee.Stderr))
|
||||
fmt.Println(stderr)
|
||||
return "", fmt.Errorf("git command: %v: %s", err, stderr)
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("git command: %v", err)
|
||||
}
|
||||
result := strings.TrimSpace(string(output))
|
||||
fmt.Printf("output: %s\n", result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func must(_ string, err error) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user