Version Management with Git Tag and SemVer: From Chaos to Professional Workflow

Git tutorial - IT technology blog
Git tutorial - IT technology blog

Friday afternoon, preparing a product release for a client. My team used to fall into complete chaos. Dev A would insist: “This version is v1.2”. Dev B would argue: “We just fixed a bug, it should be v1.2.1!”. The result? A server filled with compressed files like source_code_final.zip, source_code_final_v2.zip, and even source_code_FINAL_FINAL_FIX.zip. Looking at it, no one knew which version was actually stable.

Everything changed when I implemented Git Tag combined with Semantic Versioning (SemVer). This workflow helped clean up the mess and turned deployment into a single push command. Life became much easier for both me and my colleagues.

“Classic” Mistakes in Version Tagging

Many teams still use three manual methods to mark release milestones:

  1. Copy-Pasting Folders: Compressing source code by date. This is extremely storage-intensive. If five people do this simultaneously, you’ll quickly lose control.
  2. Abusing Release Branches: Creating a new branch like release-v1.0 for every release. The Git Graph eventually looks like a tangled mess of wires, making it incredibly hard to manage.
  3. Manual Notes: Only recording versions in a CHANGELOG file. Computers can’t understand this, so you can’t automate your CI/CD pipeline.

I once worked on a project with a team of eight. Abusing branches made merging code a nightmare. Some branches were left abandoned for a year because no one dared to delete them. That’s when I realized Git Tag was a mandatory solution.

How is Git Tag Different from a Branch?

Many Git beginners confuse these two concepts. Think of it this way: a Branch is like a road under construction—it’s always changing and moving forward. A Tag is like a fixed milestone marker on that road. Once you attach a Tag to a commit, it stays there forever, regardless of how much code you add afterward.

Why Should You Use Tags?

  • Immutability: Tag v1.0.0 will always point to that exact commit. No one can accidentally commit over it.
  • Lightweight: A Tag is essentially just a small pointer; it doesn’t bloat your repository.
  • Fast Retrieval: You can jump back to the exact code state of an old version to fix a bug in three seconds.

Applying Semantic Versioning (SemVer) in Practice

Don’t name your tags v1 or v2 randomly. Use the MAJOR.MINOR.PATCH standard (e.g., 1.2.3). This helps everyone understand the impact of changes at a glance:

  • MAJOR (First digit): Increment when there are significant changes that break backward compatibility (Breaking changes). For example: You change the entire API structure so the old app can no longer call it.
  • MINOR (Middle digit): Increment when adding new features that are still backward compatible. For example: Adding a “Send image via chat” button.
  • PATCH (Last digit): Increment for bug fixes only. No new features, no breaking changes.

Since adopting this rule, my QA team has worked much more efficiently. If only the PATCH number increases, they only need a quick smoke test. If the MAJOR number increases, they know they need a full system regression test.

Implementing Git Tag: Essential Commands

There are two types of tags: Lightweight (just a name) and Annotated (includes creator info, date, and message). I always prioritize Annotated tags for releases because they are more professional and informative.

1. Creating a New Tag

Suppose you just fixed a display bug and want to release version v1.0.1:

# Create an Annotated Tag with a description
git tag -a v1.0.1 -m "Fix display issues on Safari and update API endpoint"

# List existing tags
git tag

2. Pushing Tags to the Server

Note: A standard git push will not push tags to GitHub/GitLab. You need a specific command:

# Push a specific tag
git push origin v1.0.1

# Push all existing tags (use with caution)
git push origin --tags

3. Handling Tagging Mistakes

If you tag the wrong commit, don’t worry. You can delete and recreate it using these steps:

# Delete the tag locally
git tag -d v1.0.1

# Delete the tag on the server
git push --delete origin v1.0.1

Automating CI/CD with Git Tag

This is where you save hours of work. Instead of logging into the server to run manual commands, configure your system to build automatically whenever a new tag is detected. Here is an example using GitHub Actions:

name: Deploy Production
on:
  push:
    tags:
      - 'v*' # Run script when a tag starting with 'v' is pushed

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Build and Push Docker
        run: |
          docker build -t my-app:${{ github.ref_name }} .
          docker push my-app:${{ github.ref_name }}

With this configuration, every time you push tag v1.1.0, the system will automatically package and deploy it to production. You can relax and grab a coffee instead of watching a progress bar.

Real-World Advice

After years of managing large projects, I’ve gathered three golden rules:

  • Never fix code directly on a Tag: If version v1.0.0 has a bug, create a hotfix branch, fix it, and then tag it as v1.0.1.
  • Always use the ‘v’ prefix: Naming it v1.0.0 instead of 1.0.0 helps automation scripts work more accurately.
  • Write meaningful messages: Don’t just write -m "Update". Summarize the main changes so you can automatically generate Changelog files for your clients later.

Git Tag and SemVer are not just technical tools; they represent a management mindset. When everything is clear, communication between Devs, Testers, and clients becomes much smoother. If your team is still struggling with zip files, try switching to Tags today.

Share: