Skip to content

Release Engineering - Tags, SemVer & Changelogs 🏷️

ROLE: HPN (Release Management).
AUDIENCE: Engineers shipping production software professionally.

Releasing software không chỉ là push code. Module này dạy bạn cách versioning đúng chuẩn, tagging releases, và auto-generate changelogs từ commit history.


🎯 Mục tiêu

Sau module này, bạn sẽ:

  • Hiểu và áp dụng Semantic Versioning (SemVer)
  • Tạo Git Tags đúng cách (Annotated vs Lightweight)
  • Setup auto-changelog từ Conventional Commits
  • Build professional release workflow

📐 Semantic Versioning (SemVer)

The Format

MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]

Examples:
1.0.0          ← Initial release
1.0.1          ← Patch (bug fix)
1.1.0          ← Minor (new feature)
2.0.0          ← Major (breaking change)
2.0.0-alpha.1  ← Pre-release
2.0.0-rc.1     ← Release candidate
2.0.0+build.42 ← Build metadata

When to Increment What?

SemVer Rules

Change TypeExampleVersion Change
Breaking API changeRemove endpoint, change signatureMAJOR
Deprecate featureMark as deprecated (still works)MINOR + docs
New feature (backward compatible)Add new endpointMINOR
Bug fixFix null pointerPATCH
Performance improvementOptimize queryPATCH
Documentation onlyUpdate READMENo change (or PATCH)
Dependency update (compatible)Update lodashPATCH
Dependency update (breaking)Major dependency upgradeMAJOR

Pre-release Versions

1.0.0-alpha.1    ← Internal testing
1.0.0-alpha.2    ← More alpha testing
1.0.0-beta.1     ← External beta testing
1.0.0-rc.1       ← Release candidate
1.0.0-rc.2       ← Fix issues from rc.1
1.0.0            ← Stable release!

Precedence: 1.0.0-alpha < 1.0.0-beta < 1.0.0-rc.1 < 1.0.0

HPN'S VERSIONING RULE

0.x.x  = Development, anything can change
1.0.0  = First stable release, public API locked
1.x.x+ = Stable, follow SemVer strictly

Đừng release 1.0.0 quá sớm. Một khi bạn commit 1.0.0, breaking changes = increment MAJOR.


🏷️ Git Tags

Lightweight vs Annotated Tags

FeatureLightweightAnnotated
StorageJust a pointerFull Git object
MetadataNoneTagger name, email, date, message
SigningNoYes (GPG/SSH)
Use caseTemporary/privateReleases

ALWAYS USE ANNOTATED FOR RELEASES

Lightweight tags chỉ là pointer. Annotated tags có metadata và có thể signed. Production releases luôn dùng Annotated Tags.

Creating Tags

bash
# ❌ Lightweight tag (avoid for releases)
git tag v1.0.0

# ✅ Annotated tag (recommended)
git tag -a v1.0.0 -m "Release version 1.0.0"

# ✅ Signed annotated tag (best for production)
git tag -s v1.0.0 -m "Release version 1.0.0"

# Tag a specific commit
git tag -a v1.0.0 abc123 -m "Release version 1.0.0"

Managing Tags

bash
# List all tags
git tag

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

# Show tag details
git show v1.0.0

# Push single tag
git push origin v1.0.0

# Push all tags
git push origin --tags

# Delete local tag
git tag -d v1.0.0

# Delete remote tag
git push origin --delete v1.0.0
# or
git push origin :refs/tags/v1.0.0

# Checkout a tag (detached HEAD)
git checkout v1.0.0

# Create branch from tag
git checkout -b hotfix/v1.0.1 v1.0.0

Tag Naming Conventions

bash
# Standard format
v1.0.0          # Production release
v1.0.0-rc.1     # Release candidate
v1.0.0-beta.1   # Beta
v1.0.0-alpha.1  # Alpha

# Alternative (without 'v' prefix)
1.0.0           # Some projects prefer this

# Environment tags (less common)
prod-2024-01-15
staging-abc123

📝 Auto-Changelog Generation

The Foundation: Conventional Commits

<type>(<scope>): <description>

[optional body]

[optional footer(s)]

---

Examples:
feat(auth): add OAuth2 login
fix(api): resolve memory leak in connection pool
docs(readme): update installation instructions
BREAKING CHANGE: remove deprecated endpoints

Tool: standard-version (Node.js)

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

# Add to package.json scripts
{
  "scripts": {
    "release": "standard-version",
    "release:minor": "standard-version --release-as minor",
    "release:major": "standard-version --release-as major"
  }
}

# Usage
npm run release           # Auto-determine version
npm run release:minor     # Force minor bump
npm run release:major     # Force major bump

What it does:

  1. Analyzes commit messages since last tag
  2. Determines version bump (based on conventional commits)
  3. Updates package.json version
  4. Generates/updates CHANGELOG.md
  5. Creates commit and tag

Tool: conventional-changelog-cli

bash
# Install
npm install -g conventional-changelog-cli

# Generate changelog
conventional-changelog -p angular -i CHANGELOG.md -s

# First time (full history)
conventional-changelog -p angular -i CHANGELOG.md -s -r 0

Tool: git-cliff (Rust - Fast)

bash
# Install
cargo install git-cliff

# Or via package managers
brew install git-cliff        # macOS
winget install orhun.git-cliff  # Windows

# Generate changelog
git cliff -o CHANGELOG.md

# Generate for specific range
git cliff v1.0.0..v2.0.0 -o CHANGELOG.md

Configuration (cliff.toml):

toml
[changelog]
header = """
# Changelog\n
All notable changes to this project will be documented in this file.\n
"""
body = """
{% for group, commits in commits | group_by(attribute="group") %}
    ## {{ group | upper_first }}
    {% for commit in commits %}
        - {{ commit.message | upper_first }}\
    {% endfor %}
{% endfor %}\n
"""
footer = """
<!-- Generated by git-cliff -->
"""

[git]
conventional_commits = true
filter_unconventional = true
commit_parsers = [
    { message = "^feat", group = "Features" },
    { message = "^fix", group = "Bug Fixes" },
    { message = "^doc", group = "Documentation" },
    { message = "^perf", group = "Performance" },
    { message = "^refactor", group = "Refactoring" },
    { message = "^style", group = "Style" },
    { message = "^test", group = "Testing" },
]

Example CHANGELOG.md Output

markdown
# Changelog

All notable changes to this project will be documented in this file.

## [2.0.0] - 2024-01-15

### ⚠️ BREAKING CHANGES

- Remove deprecated `/v1/users` endpoint
- Change authentication from API key to OAuth2

### Features

- Add user profile management
- Implement rate limiting per API key
- Add WebSocket support for real-time updates

### Bug Fixes

- Fix memory leak in connection pooling
- Resolve race condition in cache invalidation

## [1.2.0] - 2023-12-01

### Features

- Add bulk user import
- Implement email verification

### Bug Fixes

- Fix pagination offset calculation

## [1.1.0] - 2023-11-15

### Features

- Add user search functionality
- Implement role-based access control

🔄 Complete Release Workflow

Manual Release

bash
# 1. Ensure clean state
git checkout main
git pull origin main
git status  # Should be clean

# 2. Run tests
npm test

# 3. Update version (nếu không dùng auto-tool)
npm version minor  # Updates package.json, creates commit and tag

# 4. Generate changelog (nếu không auto)
conventional-changelog -p angular -i CHANGELOG.md -s
git add CHANGELOG.md
git commit --amend --no-edit

# 5. Push with tags
git push origin main --follow-tags

Automated Release (CI/CD)

yaml
# .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

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: npx semantic-release

semantic-release Configuration

json
// .releaserc.json
{
  "branches": ["main"],
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/changelog",
    "@semantic-release/npm",
    "@semantic-release/github",
    ["@semantic-release/git", {
      "assets": ["package.json", "CHANGELOG.md"],
      "message": "chore(release): ${nextRelease.version} [skip ci]"
    }]
  ]
}

📊 Quick Reference

Version Commands

CommandEffect
npm version patch1.0.0 → 1.0.1
npm version minor1.0.0 → 1.1.0
npm version major1.0.0 → 2.0.0
npm version prerelease1.0.0 → 1.0.1-0
npm version 2.0.0-rc.1Explicit version

Tag Commands

CommandPurpose
git tag -a v1.0.0 -m "msg"Create annotated tag
git tag -s v1.0.0 -m "msg"Create signed tag
git push origin v1.0.0Push single tag
git push origin --tagsPush all tags
git tag -d v1.0.0Delete local tag
git push origin --delete v1.0.0Delete remote tag

Changelog Tools

ToolLanguageBest For
standard-versionNode.jsnpm projects, simple
semantic-releaseNode.jsFull automation, CI/CD
git-cliffRustFast, customizable
conventional-changelogNode.jsManual control

💡 Key Takeaways

HPN'S INSIGHT

"Version numbers are a contract with your users. Breaking that contract (breaking changes without MAJOR bump) destroys trust."

  1. SemVer is a promise: MAJOR = breaking, MINOR = features, PATCH = fixes
  2. Tags are immutable: Một khi tag được push, không nên sửa
  3. Annotated > Lightweight: Always cho releases
  4. Conventional Commits enable automation: Invest vào commit format, reap benefits in changelog
  5. Automate releases: Less human error, more consistent

The Release Checklist

□ All tests passing
□ CHANGELOG.md updated
□ Version bumped (package.json, etc.)
□ Annotated tag created
□ Tag signed (production)
□ Pushed to remote with tags
□ GitHub/GitLab Release created
□ Notify stakeholders

DON'T MANUALLY EDIT TAGS

bash
# ❌ Này là anti-pattern
git tag -d v1.0.0
git tag -a v1.0.0 <different-commit> -m "..."
git push --force origin v1.0.0

Nếu sai version, release version mới (v1.0.1), không sửa tag cũ.