Chapter 10-10 — Tags, Releases & Semantic Versioning | DevInHyderabad☕ ☕ 20 min read

Tags, Releases & Semantic Versioning

Versioning samjho, toh software ka itihaas samajh aa gaya.

01What Are Git Tags?

Git tags are named references to specific commits — like bookmarks that never move. When you mark a commit as v1.0.0, that tag permanently points to that exact commit, even as the branch continues to grow with new commits.

Unlike branches, tags are IMMUTABLE — once created, they always point to the same commit. Branches move forward with each new commit; tags stay fixed forever.

Two types of tags:

  • Lightweight tag: just a pointer (like a branch that doesn't move). No metadata — just the commit hash.
  • Annotated tag: stores metadata (tagger name, date, message, GPG signature) — RECOMMENDED for releases.

Tags are used to mark release points: v1.0.0, v2.1.3, etc. They give human-readable names to specific commits in your project's history.

Important: tags are NOT pushed by default with git push. You must explicitly push them to the remote.

# Create a lightweight tag (just a pointer)
git tag v1.0.0

# Create an annotated tag (RECOMMENDED — has metadata)
git tag -a v1.0.0 -m "Release 1.0.0: First public release"

# List tags
git tag
git tag -l "v1.*"

# See tag details
git show v1.0.0
# Tagger: Sai <sai@devinhyd.com>
# Date:   Mon Jan 15 10:00:00 2024 +0530
# Release 1.0.0: First public release

# Tag a specific past commit
git tag -a v0.9.0 abc1234 -m "Pre-release beta"

# Delete a tag
git tag -d v1.0.0              # local
git push origin --delete v1.0.0 # remote

# Push tags to remote
git push origin v1.0.0         # push specific tag
git push origin --tags          # push ALL tags
Lightweight vs Annotated: A lightweight tag is just a file storing the commit hash — no metadata, no message, no signature. An annotated tag is a full Git object with tagger info, date, message, and optional GPG signature. For releases, always use annotated tags (git tag -a). The Git documentation itself recommends annotated tags because they contain additional information.
02Semantic Versioning: The Math Behind Versions

Semantic Versioning (SemVer) is a versioning system that gives meaning to version numbers: MAJOR.MINOR.PATCH (e.g., 2.1.3).

Each number has a specific meaning:

  • MAJOR version (2): incompatible API changes (breaking changes). Users must update their code.
  • MINOR version (1): new backward-compatible features. Users can upgrade safely.
  • PATCH version (3): backward-compatible bug fixes. Users should always upgrade.

The rules of SemVer:

  • Initial development: 0.x.x — anything can change at any time.
  • Version 1.0.0: the public API is now defined. This is your first stable release.
  • Patch: increment for bug fixes (2.1.32.1.4)
  • Minor: increment for new features, reset patch (2.1.42.2.0)
  • Major: increment for breaking changes, reset minor+patch (2.2.03.0.0)

Pre-release versions use hyphens: 1.0.0-alpha.1, 1.0.0-beta.2, 1.0.0-rc.1

Build metadata uses plus: 1.0.0+build.123 (ignored for version precedence)

# Semantic Versioning examples

# 0.1.0 → Initial development (unstable API)
# 0.2.0 → New feature, still unstable
# 1.0.0 → First stable release! Public API defined.
# 1.0.1 → Bug fix (PATCH)
# 1.1.0 → New feature (MINOR)
# 1.1.1 → Another bug fix (PATCH)
# 2.0.0 → Breaking change! (MAJOR)
# 2.0.1 → Bug fix for the breaking change (PATCH)
# 2.1.0 → New feature (MINOR)
# 3.0.0-beta.1 → Pre-release for next major

# Conventional Commits + SemVer mapping:
# feat: → MINOR bump (1.1.0 → 1.2.0)
# fix: → PATCH bump (1.2.0 → 1.2.1)
# feat!: or BREAKING CHANGE → MAJOR bump (1.2.1 → 2.0.0)
# chore, docs, test, ci → NO bump (no release needed)
💡 Pro Tip: SemVer is a CONTRACT with your users. When you release version 2.1.3, you are promising: "2.x means no breaking changes since 2.0.0, 2.1 means new features since 2.0, and .3 means three bug fix patches." If you break this contract, users lose trust in your project. Follow the rules strictly.
03Creating Releases with Git Tags

A release is a tag + release notes + (optionally) binary artifacts. GitHub Releases are built on top of Git tags — they add a UI, release notes, and downloadable assets.

The release workflow:

  • 1) Update version in code (package.json, VERSION file)
  • 2) Commit the version change
  • 3) Create an annotated tag
  • 4) Push the tag to remote
  • 5) Create a GitHub Release from the tag

Pre-release tags: v2.0.0-alpha.1, v2.0.0-beta.1, v2.0.0-rc.1 — these are NOT considered stable. Package managers won't install them by default.

Tag naming convention: always use the v prefix (v1.0.0 not 1.0.0) — this is the industry standard.

# Manual release workflow

# Step 1: Update version in package.json / version file
npm version patch  # 1.0.0 → 1.0.1 (bug fix)
npm version minor  # 1.0.1 → 1.1.0 (new feature)
npm version major  # 1.1.0 → 2.0.0 (breaking change)

# This automatically:
# - Updates package.json version
# - Creates a git commit ("1.1.0")
# - Creates a git tag (v1.1.0)

# Step 2: Push commit + tag
git push --follow-tags origin main

# Step 3: Create GitHub Release
gh release create v1.1.0 \
  --title "v1.1.0 - New Analytics Dashboard" \
  --notes "## What's New
- Real-time analytics dashboard
- Export to CSV feature

## Bug Fixes
- Fixed timeout on slow networks
- Resolved null pointer in payment module

**Full Changelog**: https://github.com/user/repo/compare/v1.0.0...v1.1.0"

# Step 4: Attach release assets (optional)
gh release upload v1.1.0 ./dist/app.zip ./dist/checksums.txt
04Pre-release Versions: Alpha, Beta, RC

Pre-release versions allow testing before the stable release. They follow a clear progression: alpha → beta → rc → stable.

  • Alpha (v2.0.0-alpha.1): internal testing, features may be incomplete, bugs expected. Don't share with external users.
  • Beta (v2.0.0-beta.1): external testing, features complete, some bugs remain. Share with a limited group of testers.
  • RC — Release Candidate (v2.0.0-rc.1): final testing, expected to be the stable release unless critical bugs found. Nearly production-ready.

Pre-release precedence: alpha < beta < rc < stable (1.0.0-alpha < 1.0.0-beta < 1.0.0)

Package managers (npm, pip) don't install pre-releases by default — users must explicitly opt in.

GitHub Releases can be marked as "Pre-release" to warn users that this version is not stable.

# Pre-release workflow

# Alpha release (internal testing)
git tag -a v2.0.0-alpha.1 -m "Alpha: new API v2"
git push origin v2.0.0-alpha.1
gh release create v2.0.0-alpha.1 --prerelease \
  --title "v2.0.0-alpha.1" \
  --notes "Pre-release for internal testing only"

# Beta release (external testing)
git tag -a v2.0.0-beta.1 -m "Beta: API v2 feature-complete"
git push origin v2.0.0-beta.1
gh release create v2.0.0-beta.1 --prerelease

# Release Candidate (final testing)
git tag -a v2.0.0-rc.1 -m "RC: final testing before stable"
git push origin v2.0.0-rc.1
gh release create v2.0.0-rc.1 --prerelease

# Stable release
git tag -a v2.0.0 -m "Release 2.0.0: New API"
git push origin v2.0.0
gh release create v2.0.0 \
  --title "v2.0.0 - New API" \
  --notes "## Breaking Changes
- API responses now use JSON instead of XML
- Authentication endpoint changed"

# Install pre-release (npm)
npm install package@2.0.0-beta.1  # explicit
npm install package@latest         # WON'T install pre-release
Pre-release precedence matters. SemVer defines strict ordering: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta < 1.0.0-rc.1 < 1.0.0. Package managers use this to decide which version to install. npm install package@latest will skip all pre-releases and give you the latest stable. This protects production systems from accidentally running unstable code.
05Automated Releases with standard-version

standard-version automates the entire release process using conventional commits. No more manual version bumps or changelogs.

What it does:

  • 1) Analyzes commits since last tag
  • 2) Determines version bump (feat→minor, fix→patch, breaking→major)
  • 3) Updates CHANGELOG.md with categorized changes
  • 4) Bumps version in package.json
  • 5) Creates a git commit with the version bump
  • 6) Creates an annotated git tag

Usage: npx standard-version — patch/feat/breaking determines the bump automatically.

npx standard-version --release-as minor — force a minor bump

npx standard-version --first-release — for the first release (no version bump, just changelog)

# Automated release with standard-version

# Install
npm install --save-dev standard-version

# Add script to package.json
# "scripts": { "release": "standard-version" }

# Run it (analyzes commits since last tag)
npm run release
# Outputs:
# ✔ bumping version in package.json from 1.1.0 to 1.2.0
# ✔ outputting changes to CHANGELOG.md
# ✔ committing package.json and CHANGELOG.md
# ✔ tagging release v1.2.0

# Push commit + tag
git push --follow-tags origin main

# GitHub Release from tag
gh release create v1.2.0 --generate-notes

# First release
npx standard-version --first-release

# Force specific bump
npx standard-version --release-as major   # 1.2.0 → 2.0.0
npx standard-version --release-as minor   # 1.2.3 → 1.3.0
npx standard-version --release-as patch   # 1.2.3 → 1.2.4

# Dry run (see what would happen)
npx standard-version --dry-run

# Full CI automation (GitHub Actions)
# .github/workflows/release.yml
name: Release
on:
  push:
    branches: [main]
jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # need full history
      - run: npm ci
      - run: npm test
      - run: npx standard-version
      - run: git push --follow-tags origin main
💡 Pro Tip: Add a release script to package.json: "release": "standard-version". Then run npm run release before pushing. This ensures consistent version bumps and changelogs without manual intervention. Never edit CHANGELOG.md manually — let the tool generate it. Your commits ARE your changelog.
Git tags are the bridge between your code and your users. Every tag marks a point in time that people rely on for stability. Never delete or move a published tag — other systems (npm, Docker, CI) may depend on it. If a release has a critical bug, create a PATCH release (v1.2.1) instead of modifying the v1.2.0 tag. Tags are immutable commitments to your users.

Lo kar liya — Key Points:

  • ✅ Git tags are immutable named references to specific commits — unlike branches, they never move
  • ✅ Annotated tags (git tag -a) store metadata and are recommended over lightweight tags
  • ✅ Semantic Versioning (SemVer) uses MAJOR.MINOR.PATCH format (2.1.3)
  • ✅ MAJOR = breaking changes, MINOR = new features, PATCH = bug fixes
  • ✅ Tags are NOT pushed by default — use git push --tags or git push origin <tag>
  • ✅ Pre-release versions (alpha, beta, rc) allow testing before stable release
  • standard-version automates version bump + changelog + tag creation from conventional commits
  • ✅ Never delete or modify published tags — other systems and users depend on them
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase