TechAnek

In today’s software development world, semantic versioning (SemVer) has become the go-to way of handling version numbers. Instead of random or confusing version bumps, SemVer provides a clear, predictable, and meaningful structure to versions helping teams instantly understand whether a release introduces a bug fix, a new feature, or a breaking change.

When you combine this with Azure DevOps pipelines, you unlock a powerful automation workflow. Every build, release, or deployment can automatically generate the right version number, attach it to your artifacts, and push it all the way through your environments. This makes your releases not just consistent, but also traceable and auditable.

Understanding Semantic Versioning

Semantic Versioning (often called SemVer) uses a simple, three-part number format: MAJOR.MINOR.PATCH (for example, 2.1.3). Each part of the version number has a specific meaning:

  • MAJOR: goes up when you make big changes that break compatibility with previous versions.
  • MINOR: increases when you add new features that still work with the old versions.
  • PATCH: updates when you fix bugs or make small improvements that don’t affect compatibility.

This structured approach provides clarity about what changed, predictability for upgrades, and automated dependency management.

Why Automate Versioning?

As teams grow and deployments become more frequent, manually managing versions quickly turns into a challenge. Automated semantic versioning helps solve this problem by bringing structure and reliability to the process.

Consistency and Reliability
Automation removes the risk of human error and ensures version numbers are always applied in a predictable, consistent way across all environments.
Traceability and Accountability
Every version is linked directly to the changes that introduced it, making it easy to see what went into a release and who contributed.
Better Release Management
With clear versioning, teams can quickly identify which release includes new features, bug fixes, or breaking changes—making planning and rollbacks far simpler.

Implementing Semantic Versioning in Azure DevOps Pipeline

Automating semantic versioning in Azure DevOps Pipelines makes version management easier and more reliable. Instead of manually updating version numbers, the pipeline handles it for you — ensuring every build and release gets a clear, consistent, and meaningful version. By applying SemVer rules directly, your pipeline can automatically bump versions based on the type of changes made, making it simpler to track updates and keep your CI/CD process smooth and efficient.

Step 1: Create the First Git Tag

  • Go to your Azure DevOps project.
  • Navigate to Repos → Tags.
  • Click “New Tag”.
  • Enter:
    • Tag name: 0.0.0
    • Message: Initial version
    • Choose branch/commit: Select the commit you want to tag (usually main).
  • Click Create.

Step 2: Create semantic-version.yaml file in .pipeline folder

  • In your .pipeline folder go to steps folder (if not exist create one).
  • Inside a steps folder create a semantic-version.yaml file.
  • And Add the below content as it is.
  • Understand the below script, click here.
semantic-version.yaml
Copy to clipboard
steps:
  - checkout: self
    persistCredentials: true

  - script: |
      echo "Detecting latest valid Semantic Version tag..."

      # Get valid semver tags only
      tags=$(git tag -l | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V)
      latestTag=$(echo "$tags" | tail -n 1)

      if [ -z "$latestTag" ]; then
        latestTag="0.0.0"
      fi

      echo "Latest tag: $latestTag"

      version=$latestTag
      IFS='.' read -r major minor patch <<< "$version"

      branchName="$(Build.SourceBranch)"
      echo "Current branch: $branchName"

      if [[ "$branchName" == *"/fix/"* || "$branchName" == *"/bugfix/"* || "$branchName" == *"/hotfix/"* ]]; then
        echo "Patch branch detected - incrementing patch version"
        patch=$((patch + 1))
      elif [[ "$branchName" == *"/feature/"* || "$branchName" == *"/feat/"* ]]; then
        echo "Feature branch detected - incrementing minor version"
        minor=$((minor + 1))
        patch=0
      elif [[ "$branchName" == *"/major/"* ]]; then
        echo "Major branch detected - incrementing major version"
        major=$((major + 1))
        minor=0
        patch=0
      else
        echo "No matching branch type found - defaulting to patch increment"
        patch=$((patch + 1))
      fi

      newTag="$major.$minor.$patch"
      echo "New version: $newTag"

      echo "##vso[task.setvariable variable=ImageTag;isOutput=true]$newTag"

      echo "Pushing tag $newTag..."
      git tag "$newTag"
      git push origin "$newTag"
    displayName: "Generate Semantic Version Tag"
    name: SetVersion

Step 3: Add Script to Azure DevOps Pipeline

In your azure-pipelines.yml (or your main pipeline file), add a step to run the script after a successful build:
Copy to clipboard
- stage: Versioning
  displayName: "Generate Semantic Version"
  jobs:
    - job: TagVersion
      displayName: "Compute Semantic Version"
      steps:
        - template: steps/semantic-version.yaml
          parameters:
            name: SetVersion

Now add the below line as a variable to use this tag as you Docker image tag or in any other steps of your pipeline.

Copy to clipboard
variables:
  IMAGE_TAG: $[stageDependencies.Versioning.TagVersion.outputs['SetVersion.ImageTag']]

NOTE: In above line IMAGE_TAG is the variable name, used to tag the Docker Image. You can change that as per you variable name.

Step 4: Branch Naming Conventions

The script uses branch names to determine version bumps:

  • fix/, bugfix/, hotfix/PATCH increment (e.g., 1.2.3 → 1.2.4)
  • feature/, feat/MINOR increment (e.g., 1.2.3 → 1.3.0)
  • major/MAJOR increment (e.g., 1.2.3 → 2.0.0)

So, when creating branches, stick to these conventions.

Step 5: Verify Tags in Azure Repos

After the pipeline runs, check the Tags section in your Azure Repo. You should see automatically created tags like 1.0.0, 1.1.0, 1.1.1, etc.

Semantic Versioning Script Explained

The following Bash script is used in the Azure DevOps pipeline to automatically generate the next semantic version tag based on the branch name and here is the detailed Explanation of the the script.

1. Get the Latest Semantic Version Tag

tags=$(git tag -l | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V)
latestTag=$(echo "$tags" | tail -n 1)

if [ -z "$latestTag" ]; then
  latestTag="0.0.0"
fi

echo "Latest tag: $latestTag"
  • git tag -l → lists all tags in the repository.
  • grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' → filters only valid SemVer tags (like 1.2.3)
  • sort -V → sorts them in version order (so 1.10.0 comes after 1.2.0)
  • tail -n 1 → gets the latest version tag.
  • If no tag exists, we start from 0.0.0.

2. Split the Version into Major, Minor, and Patch

version=$latestTag
IFS='.' read -r major minor patch <<< "$version"
  • IFS='.' → sets the delimiter as .
  • read -r major minor patch → splits the version string into variables.
    • Example: 1.2.3major=1, minor=2, patch=3.

3. Get the Current Branch Name

branchName="$(Build.SourceBranch)"
echo "Current branch: $branchName"
  • $(Build.SourceBranch) → Azure DevOps variable that contains the branch name (e.g., refs/heads/feature/add-login).
  • This determines which part of the version number to bump.

4. Decide Which Version to Increment

if [[ "$branchName" == *"/fix/"* || "$branchName" == *"/bugfix/"* || "$branchName" == *"/hotfix/"* ]]; then
  echo "Patch branch detected - incrementing patch version"
  patch=$((patch + 1))
elif [[ "$branchName" == *"/feature/"* || "$branchName" == *"/feat/"* ]]; then
  echo "Feature branch detected - incrementing minor version"
  minor=$((minor + 1))
  patch=0
elif [[ "$branchName" == *"/major/"* ]]; then
  echo "Major branch detected - incrementing major version"
  major=$((major + 1))
  minor=0
  patch=0
else
  echo "No matching branch type found - defaulting to patch increment"
  patch=$((patch + 1))
fi
  • If the branch contains fix/bugfix/hotfix → bump the patch version (1.2.3 → 1.2.4).
  • If the branch contains feature/feat → bump the minor version (1.2.3 → 1.3.0).
  • If the branch contains major → bump the major version (1.2.3 → 2.0.0).
  • Otherwise → default to patch increment.

5. Build the New Tag

newTag="$major.$minor.$patch"
echo "New version: $newTag"
  • Combines the updated version numbers into the new semantic version tag.

6. Create and Push the Tag

git tag "$newTag"
git push origin "$newTag"
  • git tag "$newTag" → creates the new tag locally.
  • git push origin "$newTag" → pushes it back to Azure Repos, making it available for everyone.

Conclusion

In the end, adding semantic versioning to your Azure DevOps pipeline isn’t just about following a standard, it’s about making life easier for your team and anyone using your software. With clear version numbers that reflect the type of change, you remove guesswork, build trust in your releases and keep deployments smooth and predictable. It’s a small step in setup, but it pays off big in long-term clarity and confidence.

Leave a Reply

Your email address will not be published. Required fields are marked *