Skip to content

Instantly share code, notes, and snippets.

@agrcrobles
Created January 8, 2026 23:03
Show Gist options
  • Select an option

  • Save agrcrobles/9cd9e29569b7e1bf74350739ddf19d7b to your computer and use it in GitHub Desktop.

Select an option

Save agrcrobles/9cd9e29569b7e1bf74350739ddf19d7b to your computer and use it in GitHub Desktop.
Incremental Semantic Version Calculator

Version Calculator

Generates semantic versions for builds based on branch type.

Version Format

<major>.<minor>.<patch>-rc.<buildNumber>[+<prefix>.<identifier>]

Examples

  • Master: 0.0.1-rc.0023
  • PR: 0.0.1-rc.0045+pr.0012
  • Feature: 0.0.1-rc.0067+feature.3421
  • Hotfix: 0.0.1-rc.0089+hotfix.5678

Usage

  • Tested on Azure DevOps
- template: build/version-calculator.yml
  parameters:
    majorVersion: 0
    minorVersion: 0
    patchVersion: 1

Branch Types

Branch Pattern Version Suffix Example
master/main none 0.0.1-rc.0001
refs/pull/* +pr.NNNN 0.0.1-rc.0001+pr.0042
feature/* +feature.NNNN 0.0.1-rc.0001+feature.1234
hotfix/* +hotfix.NNNN 0.0.1-rc.0001+hotfix.5678
bugfix/* +bugfix.NNNN 0.0.1-rc.0001+bugfix.3210
release/* +release.NNNN 0.0.1-rc.0001+release.6543
develop +dev.NNNN 0.0.1-rc.0001+dev.1234
staging +staging.NNNN 0.0.1-rc.0001+staging.7890
test/* +test.NNNN 0.0.1-rc.0001+test.4321
other +alpha.NNNN 0.0.1-rc.0001+alpha.5678

Output Variables

  • BuildVersion - Full version string
  • BuildVersionBase - Base version without suffix
  • RcNumber - Release candidate number (4 digits)
  • BranchType - Branch name/type
# ==========================================
# Incremental Semantic Version Calculator
# Starting from 0.0.1-rc.0001+pr.0000
# ==========================================
parameters:
- name: majorVersion
type: number
default: 0
- name: minorVersion
type: number
default: 0
- name: patchVersion
type: number
default: 1
jobs:
- job: CalculateVersion
displayName: 'Calculate Incremental Version'
steps:
- task: PowerShell@2
displayName: 'Generate Incremental Build Version'
inputs:
targetType: 'inline'
script: |
# Incremental version calculation starting from 0.0.1-rc.0001
$major = ${{ parameters.majorVersion }}
$minor = ${{ parameters.minorVersion }}
$patch = ${{ parameters.patchVersion }}
# Get build and source info
$buildId = $env:BUILD_BUILDID
$sourceBranch = $env:BUILD_SOURCEBRANCH
$sourceVersion = $env:BUILD_SOURCEVERSION
Write-Host "=== Build Information ==="
Write-Host "Build ID: $buildId"
Write-Host "Source Branch: $sourceBranch"
Write-Host "Source Version: $sourceVersion"
# Calculate RC number (padded to 4 digits, starting from 0001)
$rcNumber = if ($buildId) {
[int]$buildId
} else {
# Fallback: use timestamp-based number
[int](Get-Date -UFormat %s) % 9999 + 1
}
$rcNumberPadded = "{0:D4}" -f $rcNumber
# Extract branch information and determine prefix
$branchName = ""
$prNumber = ""
$versionPrefix = ""
if ($sourceBranch -eq "refs/heads/master" -or $sourceBranch -eq "refs/heads/main") {
$branchName = "master"
} elseif ($sourceBranch -match "refs/pull/(\d+)/merge") {
$prNumber = $matches[1]
$prNumberPadded = "{0:D4}" -f [int]$prNumber
$branchName = "pr"
$versionPrefix = "pr"
} elseif ($sourceBranch -match "refs/heads/(.+)") {
$fullBranchName = $matches[1]
# Determine prefix based on branch naming convention
if ($fullBranchName -match "^(feature|feat)/(.+)") {
$versionPrefix = "feature"
$branchName = $matches[2]
} elseif ($fullBranchName -match "^(hotfix|fix)/(.+)") {
$versionPrefix = "hotfix"
$branchName = $matches[2]
} elseif ($fullBranchName -match "^(bugfix|bug)/(.+)") {
$versionPrefix = "bugfix"
$branchName = $matches[2]
} elseif ($fullBranchName -match "^(release|rel)/(.+)") {
$versionPrefix = "release"
$branchName = $matches[2]
} elseif ($fullBranchName -match "^develop$") {
$versionPrefix = "dev"
$branchName = "develop"
} elseif ($fullBranchName -match "^staging$") {
$versionPrefix = "staging"
$branchName = "staging"
} elseif ($fullBranchName -match "^(test|testing)") {
$versionPrefix = "test"
$branchName = $fullBranchName
} else {
# Default for unknown branch patterns
$versionPrefix = "alpha"
$branchName = $fullBranchName
}
}
# Base version with padded RC number
$baseVersion = "$major.$minor.$patch-rc.$rcNumberPadded"
# Generate full version based on branch type
if ($branchName -eq "master") {
# Master: 0.0.1-rc.0001
$fullVersion = $baseVersion
Write-Host "=== MASTER BRANCH DEPLOYMENT ===" -ForegroundColor Green
} elseif ($versionPrefix -eq "pr") {
# Pull Request: 0.0.1-rc.0001+pr.0000
$fullVersion = "$baseVersion+pr.$prNumberPadded"
Write-Host "=== PULL REQUEST DEPLOYMENT ===" -ForegroundColor Yellow
Write-Host "PR Number: $prNumber (padded: $prNumberPadded)"
} else {
# Branch deployments with specific prefixes
# Generate consistent number based on branch name
$branchHash = $branchName.GetHashCode()
$suffixNumber = [Math]::Abs($branchHash % 9999) + 1
$suffixNumberPadded = "{0:D4}" -f $suffixNumber
$fullVersion = "$baseVersion+$versionPrefix.$suffixNumberPadded"
# Display deployment type with color coding
switch ($versionPrefix) {
"feature" {
Write-Host "=== FEATURE BRANCH DEPLOYMENT ===" -ForegroundColor Blue
Write-Host "Feature: $branchName (ID: $suffixNumberPadded)"
}
"hotfix" {
Write-Host "=== HOTFIX BRANCH DEPLOYMENT ===" -ForegroundColor Red
Write-Host "Hotfix: $branchName (ID: $suffixNumberPadded)"
}
"bugfix" {
Write-Host "=== BUGFIX BRANCH DEPLOYMENT ===" -ForegroundColor DarkYellow
Write-Host "Bugfix: $branchName (ID: $suffixNumberPadded)"
}
"release" {
Write-Host "=== RELEASE BRANCH DEPLOYMENT ===" -ForegroundColor Magenta
Write-Host "Release: $branchName (ID: $suffixNumberPadded)"
}
"dev" {
Write-Host "=== DEVELOPMENT BRANCH DEPLOYMENT ===" -ForegroundColor DarkCyan
Write-Host "Development branch (ID: $suffixNumberPadded)"
}
"staging" {
Write-Host "=== STAGING BRANCH DEPLOYMENT ===" -ForegroundColor DarkGreen
Write-Host "Staging environment (ID: $suffixNumberPadded)"
}
"test" {
Write-Host "=== TEST BRANCH DEPLOYMENT ===" -ForegroundColor DarkMagenta
Write-Host "Test: $branchName (ID: $suffixNumberPadded)"
}
default {
Write-Host "=== CUSTOM BRANCH DEPLOYMENT ===" -ForegroundColor Cyan
Write-Host "Branch: $branchName (alpha: $suffixNumberPadded)"
}
}
}
Write-Host ""
Write-Host "=== VERSION INFORMATION ==="
Write-Host "Base Version: $baseVersion" -ForegroundColor Blue
Write-Host "Full Version: $fullVersion" -ForegroundColor Magenta
Write-Host "RC Number: $rcNumberPadded" -ForegroundColor Green
Write-Host "Build ID: $buildId"
# Validation - Updated regex to support all prefixes
if ($fullVersion -match "^\d+\.\d+\.\d+-rc\.\d{4}(\+(pr|feature|hotfix|bugfix|release|dev|staging|test|alpha)\.\d{4})?$") {
Write-Host "[PASS] Version format is valid" -ForegroundColor Green
} else {
Write-Host "[FAIL] Version format is invalid!" -ForegroundColor Red
Write-Host "Generated: $fullVersion"
exit 1
}
# Set pipeline variables for other stages
Write-Host "##vso[task.setvariable variable=BuildVersion;isOutput=true]$fullVersion"
Write-Host "##vso[task.setvariable variable=BuildVersionBase;isOutput=true]$baseVersion"
Write-Host "##vso[task.setvariable variable=RcNumber;isOutput=true]$rcNumberPadded"
Write-Host "##vso[task.setvariable variable=BranchType;isOutput=true]$branchName"
# Update build number for display in Azure DevOps
Write-Host "##vso[build.updatebuildnumber]$fullVersion"
Write-Host ""
Write-Host "Version $fullVersion ready for deployment!" -ForegroundColor Green
name: 'SetVersion'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment