Show HN: GitOps SemVer Script

5 hours ago 2

This is a language agnostic git versioning tool using tags.

There are plenty of tools available that can generate a version based on Git tags. However, they are typically:

  • driven by commit messages, not configuration
  • dependent on programming languages

I didn't want to have to introduce a programming language into my pipeline, especially when it was a language that had absolutely nothing to do with my pipeline, e.g. using an action implemented in JS in a Python project. I also didn't feel like commit messages were the way to go, typos happen, and then someone needs to go and update it manually.

The aim has been to have a very simple approach at versioning without introducing new dependencies, and for it to be configuration driven.

You can either:

A bash script that calculates semantic versions from git tags and branches, with support for feature branch pre-releases.

  • Git repository with commit history
  • jq command-line JSON processor
  • Bash shell
  1. Download the script:

    curl -sL https://raw.githubusercontent.com/DragosDumitrache/versioner/main/version.sh -o version.sh chmod +x version.sh
  2. Create version.json in your repository root:

    { "default_branch": "main", "major": "1", "minor": "0", "tag_prefix": "v", "include_v_prefix": true }
  3. Run the script:

The version.json file supports the following options:

Field Description Default Example
default_branch Main branch name "master" "main", "master"
major Minimum major version "0" "1", "2"
minor Minimum minor version "0" "0", "5"
tag_prefix Prefix for git tags "" "v", "release-"
include_v_prefix Include v in output false true, false

On your default branch (e.g., main), the script produces clean semantic versions:

# No previous tags ./version.sh # Output: v1.0.0 # After tag v1.0.0, with 3 more commits ./version.sh # Output: v1.0.3 # With version constraints (major: "2", minor: "1") ./version.sh # Output: v2.1.0 (enforces minimums)

On feature branches, the script produces pre-release versions:

# On branch "feature/user-auth" ./version.sh # Output: v1.0.3-dev.feature-user-auth.abc1234 # On branch "bugfix/login-issue" ./version.sh # Output: v1.0.3-dev.bugfix-login-issue.def5678

When you have uncommitted changes:

./version.sh # Main branch: v1.0.3-dirty # Feature branch: v1.0.3-dev.feature-auth.abc1234.dirty
# Initialize repository git init git config user.name "Your Name" git config user.email "[email protected]" # Create version configuration cat <<EOF > version.json { "default_branch": "main", "major": "1", "minor": "0", "tag_prefix": "v", "include_v_prefix": true } EOF # Download and run script curl -sL https://raw.githubusercontent.com/DragosDumitrache/versioner/main/version.sh | bash # Output: v1.0.0

Example 2: With Existing Tags

# After creating some tags git tag v1.2.0 git commit -m "Add feature" git commit -m "Fix bug" ./version.sh # Output: v1.2.2 (v1.2.0 + 2 commits)

Example 3: Feature Branch

git checkout -b feature/api-improvement git commit -m "Improve API" ./version.sh # Output: v1.2.3-dev.feature-api-improvement.a1b2c3d

Example 4: No Prefix Configuration

{ "default_branch": "main", "major": "0", "minor": "1", "tag_prefix": "", "include_v_prefix": false }
./version.sh # Output: 0.1.0 (no "v" prefix)
#!/bin/bash VERSION=$(./version.sh) echo "Building version: $VERSION" # Use in Docker build docker build -t myapp:$VERSION . # Use in package.json npm version $VERSION --no-git-tag-version
VERSION := $(shell ./version.sh) .PHONY: version version: @echo $(VERSION) .PHONY: build build: docker build -t myapp:$(VERSION) . .PHONY: tag tag: git tag $(VERSION) git push origin $(VERSION)
#!/bin/bash set -e # Get version VERSION=$(./version.sh) CLEAN_VERSION=${VERSION#v} # Remove v prefix # Check if pre-release if [[ "$VERSION" =~ -dev\. ]]; then echo "Pre-release version: $VERSION" echo "Deploying to staging..." else echo "Release version: $VERSION" echo "Deploying to production..." fi
./version.sh # Output: 1.0.0-SNAPSHOT
# Install jq first # Ubuntu/Debian: sudo apt-get install jq # macOS: brew install jq # CentOS/RHEL: sudo yum install jq

If version.json is malformed, the script will use defaults and may produce unexpected results. Validate your JSON:

jq . version.json # Should output the parsed JSON without errors

The script automatically normalizes branch names:

  • feature/user-auth → feature-user-auth
  • bugfix/fix_login → bugfix-fix-login
  • release/v2.0 → release-v2.0
// Docker-style tags { "tag_prefix": "v", "include_v_prefix": true } // Result: v1.2.3 // No prefix { "tag_prefix": "", "include_v_prefix": false } // Result: 1.2.3 // Custom prefix { "tag_prefix": "release-", "include_v_prefix": false } // Result: 1.2.3 (but looks for release-1.2.2 tags)

Use version constraints to enforce minimum versions:

{ "major": "2", "minor": "1" }

Even if your latest tag is v1.5.0, the script will output v2.1.0 to meet the minimum requirements.

  • Ensure you're on the correct branch
  • Check that commits exist since the last tag
  • Verify git history with git log --oneline
  • Check version.json syntax with jq . version.json
  • Ensure all values are strings: "1" not 1
  • Verify tag prefix matches existing tags

Script Returns "SNAPSHOT"

  • You're not in a git repository
  • Run git init and make at least one commit
  • Ensure .git directory exists

Run the included tests:

# Install bats npm install -g bats # Run tests bats test.bats

MIT

Read Entire Article