Back to blog

February 03, 2024 • 4 min read

Automated Semantic Versioning with CI

How I automate semantic versioning with shell scripts and branch-aware CI pipelines.

All code used in this write-up lives on GitHub.


Branching strategy#

Diagram illustrating the GitFlow branching model
Figure 1: GitFlow branching model - main branches and their relationships.

This setup uses three core branches:

  • develop: active development branch. Feature branches start here. Merging back triggers a snapshot build.
  • release/*: cut from develop when a release is ready for QA. Every push produces a release candidate build.
  • main: latest stable release. Production builds and release tags live here. Hotfixes branch from and merge back to main.

I also use feature/*, bugfix/*, and hotfix/* branches. This is standard GitFlow with a bit of CI automation layered on top.

Reference: GitFlow


Semantic versioning#

Version format is MAJOR.MINOR.PATCH with branch-specific suffixes.

  • develop: <MAJOR>.<MINOR>.<PATCH>-SNAPSHOT
  • release/*: <MAJOR>.<MINOR>.<PATCH>-rcN
  • main: <MAJOR>.<MINOR>.<PATCH>

This keeps branch intent obvious and prevents people from guessing which build is safe to deploy.

Reference: Semantic Versioning


Branch pipelines#

Each branch has its own pipeline behavior.

Flowchart of the main branch pipeline
Figure 2: Main branch pipeline - production deployment workflow with version management.

The main pipeline deploys what lands in main. It runs version.sh to strip prerelease suffixes, syncs develop after merges via release.sh and hotfix.sh, tags the release, then builds the Docker image.

File: semver_ci/bitbucket-pipelines.yml

release/* pipeline#

Flowchart of the release branch pipeline
Figure 3: Release branch pipeline - release candidate versioning and deployment.

When a release/* branch is created, the pipeline turns -SNAPSHOT into -rc1. Every next push increments the RC number and builds again. That gives QA a clear sequence of candidates.

develop pipeline#

Flowchart of the develop branch pipeline
Figure 4: Develop branch pipeline - snapshot build workflow for active development.

Merging feature/* or main into develop triggers the snapshot flow. version.sh ensures the suffix is -SNAPSHOT, then the pipeline builds the Docker image.


Custom pipelines#

version-bump pipeline#

Flowchart of the version-bump pipeline
Figure 5: Version-bump pipeline - manual version increment workflow for major, minor, or patch updates.

version-bump is a manual helper for develop. You pick major, minor, or patch, and version.sh updates the version.

initiate-release-branch pipeline#

Flowchart of the initiate-release-branch pipeline
Figure 6: Initiate-release-branch pipeline - automated release branch creation from develop.

initiate-release-branch creates a new release/* branch from develop, commits an empty change to trigger CI, and pushes it.


Versioning scripts#

version.sh#

version.sh is the core versioning script. It accepts two argument groups.

Release mode (snapshot, rc, main)#

  • snapshot: sets the version like 2.0.0-SNAPSHOT on develop
  • rc: increments release candidate versions like 2.0.0-rc1, 2.0.0-rc2
  • main: removes prerelease suffixes on main and creates a Git tag

SemVer segment (major, minor, patch)#

Increments the selected segment.

File: semver_ci/version.sh

release.sh#

release.sh runs in the main pipeline. It compares release/* and develop versions, then updates develop when needed.

File: semver_ci/release.sh

hotfix.sh#

hotfix.sh increments patch versions for hotfixes, syncs develop if required, and commits the result.

File: semver_ci/hotfix.sh

initiate-release-branch.sh#

Creates a release branch from develop.

File: semver_ci/initiate-release-branch.sh


Alternative: GitFlow CLI by NVIE#

  • GitFlow adds higher-level commands for Vincent Driessen’s branching model.
  • If you do not need SemVer automation, this tool is a solid way to keep GitFlow consistent.

Conclusion#

The goal here is simple: make release versions predictable without manual edits.

This setup gave me repeatable releases, clearer RC tracking, and less pipeline guesswork.