</>Học Dev
Bài học

Tuần 2 - Ngày 4: Release Management

Tuần 2 – Ngày 4

Tuần 2 - Ngày 4: Release Management

Mục tiêu học tập

  • Tạo và quản lý Git tags (annotated vs lightweight)
  • Hiểu Semantic Versioning (MAJOR.MINOR.PATCH) và quy tắc áp dụng
  • Nắm vững release branch workflow và hotfix flow
  • Tự động hóa changelog với conventional-changelog
  • Tạo GitHub Releases và ký tags với GPG

1. Git Tags — Đánh Dấu Version

Hai loại tag

# Lightweight tag — đơn giản, chỉ là pointer tới commit
git tag v1.0.0

# Annotated tag — khuyến nghị cho release — có author, date, message
git tag -a v1.0.0 -m "Release version 1.0.0 — initial stable release"

# Tạo tag cho commit cũ (không phải HEAD)
git tag -a v0.9.0 abc1234 -m "Release v0.9.0"

# Xem danh sách tags
git tag
git tag -l "v1.*"   # filter theo pattern

# Xem chi tiết annotated tag
git show v1.0.0

Sự khác biệt quan trọng

Lightweighttag:pointerthngticommitobjectkhôngcómetadatariêngdùngchotemporary/localmarkersAnnotatedtag:totagobjectriêngtrong.git/objects/có:taggername,email,date,messagecóthGPG-signedgitdescribe--tagsưutiênannotatedtagdùngchopublicreleases

Push tags lên remote

# Push một tag cụ thể
git push origin v1.0.0

# Push tất cả tags
git push origin --tags

# Xoá tag local
git tag -d v1.0.0-beta

# Xoá tag remote
git push origin :refs/tags/v1.0.0-beta
# Hoặc:
git push origin --delete v1.0.0-beta

2. Semantic Versioning — MAJOR.MINOR.PATCH

Semantic Versioning (SemVer) là tiêu chuẩn đặt tên version cho phần mềm. Nguồn: semver.org.

Quy tắc tăng version

MAJOR.MINOR.PATCH

MAJOR: tăng khi có breaking change (API không tương thích ngược)
MINOR: tăng khi thêm tính năng mới (tương thích ngược)
PATCH: tăng khi sửa bug (tương thích ngược)

Ví dụ:
  1.2.3 → 1.2.4  (fix bug)
  1.2.3 → 1.3.0  (thêm feature mới, PATCH reset về 0)
  1.2.3 → 2.0.0  (breaking change, MINOR + PATCH reset về 0)

Pre-release và build metadata

# Pre-release (thứ tự: alpha < beta < rc < stable)
v1.0.0-alpha.1
v1.0.0-beta.3
v1.0.0-rc.1
v1.0.0

# Build metadata (không ảnh hưởng thứ tự, chỉ thông tin)
v1.0.0+build.42
v1.0.0-beta.1+sha.abc1234

Kết hợp với Conventional Commits

# Conventional Commits giúp automation xác định version bump:
#
# feat: add OAuth login          → MINOR bump (1.2.3 → 1.3.0)
# fix: resolve token expiry bug  → PATCH bump (1.2.3 → 1.2.4)
# feat!: redesign auth API       → MAJOR bump (1.2.3 → 2.0.0)
# BREAKING CHANGE: footer text   → MAJOR bump
#
# Commits dạng chore/docs/style  → không bump version

3. Release Branch Workflow

Git Flow Release Branch

main(production)^^^|||tagv1.0.0tagv1.1.0tagv1.2.0|||develop(integration)\\\release/1.0release/1.1release/1.2||(hotfixv1.0.1)(bugfixestrưclaunch)

Tạo và làm việc với release branch

# 1. Tạo release branch từ develop khi feature freeze
git switch develop
git pull origin develop
git switch -c release/1.2.0

# 2. Trên release branch: chỉ sửa bug, cập nhật version number, docs
# KHÔNG thêm feature mới
echo "1.2.0" > VERSION
npm version 1.2.0 --no-git-tag-version  # cập nhật package.json

git add VERSION package.json
git commit -m "chore: bump version to 1.2.0"

# 3. Merge vào main khi sẵn sàng release
git switch main
git merge --no-ff release/1.2.0 -m "chore: release v1.2.0"
git tag -a v1.2.0 -m "Release v1.2.0"
git push origin main --tags

# 4. Merge ngược lại develop để sync bug fixes
git switch develop
git merge --no-ff release/1.2.0 -m "chore: sync release/1.2.0 back to develop"
git push origin develop

# 5. Xoá release branch
git branch -d release/1.2.0
git push origin --delete release/1.2.0

4. Hotfix Flow

Hotfix xử lý bug nghiêm trọng trực tiếp trên production.

#Sơđhotfixflow:##mainv1.2.0v1.2.1(production)#\/#hotfix/1.2.1#\#develop(syncback)#1.Tohotfixtmain(ttagproductionđangchy)gitswitchmaingitpulloriginmaingitswitch-chotfix/1.2.1#2.Fixbugvimsrc/api/auth.ts#sasecuritybuggitadd.gitcommit-m"fix:resolveJWTvalidationbypass(CVE-2024-XXXX)"#3.Bumppatchversionnpmversionpatch--no-git-tag-versiongitaddpackage.jsongitcommit-m"chore:bumpversionto1.2.1"#4.Mergevàomainvàtaggitswitchmaingitmerge--no-ffhotfix/1.2.1-m"chore:hotfixv1.2.1"gittag-av1.2.1-m"Hotfixv1.2.1CVE-2024-XXXXJWTfix"gitpushoriginmain--tags#5.Mergevàodevelop(KHÔNGquênbưcnày!)gitswitchdevelopgitmerge--no-ffhotfix/1.2.1-m"chore:synchotfix/1.2.1todevelop"gitpushorigindevelop#6.Xoáhotfixbranchgitbranch-dhotfix/1.2.1

5. Changelog Tự Động

Conventional Changelog

# Cài đặt
npm install -g conventional-changelog-cli

# Tạo CHANGELOG.md từ lịch sử commits (Conventional Commits format)
conventional-changelog -p angular -i CHANGELOG.md -s

# -p angular: sử dụng Angular preset (feat/fix/perf/docs/...)
# -i CHANGELOG.md: input file
# -s: write output to input file (overwrite nếu cần)

# Tạo từ đầu (toàn bộ lịch sử)
conventional-changelog -p angular -i CHANGELOG.md -s -r 0

Ví dụ CHANGELOG.md được sinh ra

# Changelog

## [1.2.0] - 2024-01-15

### Features
* add OAuth 2.0 integration with Google ([abc1234])
* add dark mode support ([def5678])

### Bug Fixes
* resolve token refresh race condition ([ghi9012])

## [1.1.0] - 2024-01-01
...

Tích hợp vào npm scripts

Lưu ý (2026): standard-version đã bị deprecated từ tháng 5/2022. Các thay thế được khuyến nghị: semantic-release (tự động hoàn toàn, phổ biến nhất), release-please (của Google, tạo release PR), hoặc commit-and-tag-version (fork cộng đồng tiếp nối standard-version). Ví dụ dưới đây minh hoạ cơ chế hoạt động — không dùng standard-version cho project mới.

{
  "scripts": {
    "release": "standard-version",
    "release:minor": "standard-version --release-as minor",
    "release:major": "standard-version --release-as major"
  }
}
# standard-version tự động:
# 1. Tính version bump dựa trên commits
# 2. Cập nhật package.json version
# 3. Tạo/cập nhật CHANGELOG.md
# 4. Tạo commit "chore(release): v1.2.0"
# 5. Tạo annotated tag v1.2.0
npm run release

6. GitHub Releases

GitHub Releases mở rộng Git tags với release notes, binary assets, và link tải xuống.

# Tạo GitHub Release qua CLI (gh)
gh release create v1.2.0 \
  --title "Version 1.2.0 — OAuth & Dark Mode" \
  --notes-file RELEASE_NOTES.md \
  --target main

# Tải artifact lên release
gh release upload v1.2.0 dist/app-linux.tar.gz dist/app-macos.tar.gz

# Tạo draft release (chưa publish)
gh release create v1.3.0-rc.1 --draft --prerelease

# Xem releases
gh release list
gh release view v1.2.0

Tự động tạo release notes từ PR titles

# .github/release.yml — cấu hình auto-generated notes
changelog:
  categories:
    - title: "Breaking Changes"
      labels: ["breaking-change"]
    - title: "New Features"
      labels: ["enhancement", "feature"]
    - title: "Bug Fixes"
      labels: ["bug"]
    - title: "Documentation"
      labels: ["documentation"]

7. Signed Tags với GPG

Signed tags chứng minh danh tính người tạo release — quan trọng cho security-sensitive projects.

# Kiểm tra GPG key đã có chưa
gpg --list-secret-keys --keyid-format=long

# Nếu chưa có, tạo GPG key mới
gpg --gen-key

# Lấy key ID (dạng ABCD1234EFGH5678)
gpg --list-secret-keys --keyid-format=long | grep sec

# Cấu hình Git dùng GPG key
git config --global user.signingkey ABCD1234EFGH5678
git config --global gpg.program gpg

# Tạo signed tag (-s thay vì -a)
git tag -s v1.2.0 -m "Release v1.2.0 (signed)"

# Verify tag signature
git tag -v v1.2.0
# Output: gpg: Good signature from "Your Name <your@email.com>"

# Tự động ký tất cả tags
git config --global tag.gpgSign true

# Push signed tag (giống push thường)
git push origin v1.2.0

Câu hỏi ôn tập

  1. Khi nào nên tăng MAJOR version theo SemVer?

    Xem đáp án

    Tăng MAJOR khi có breaking change — thay đổi API/interface không tương thích ngược. Ví dụ: đổi tên function public, xoá endpoint, thay đổi request/response format, đổi function signature. 1.2.3 → 2.0.0 (MINOR và PATCH reset về 0). Conventional Commits báo hiệu bằng feat!:, fix!:, hoặc footer BREAKING CHANGE:.

  2. Sự khác biệt chính giữa annotated tag và lightweight tag là gì?

    Xem đáp án

    Lightweight tag: chỉ là pointer đến commit, không có metadata riêng — như một branch không di chuyển. Annotated tag: tạo một tag object riêng trong .git/objects/ với tagger name, email, date, message, và có thể GPG-signed. git describe ưu tiên annotated tag. Dùng annotated tag cho public releases (có thể verify, có đầy đủ metadata); lightweight cho private/temporary markers.

  3. Tại sao hotfix phải được merge vào cả main lẫn develop?

    Xem đáp án

    main = production code → cần fix ngay để deploy. develop = integration branch cho next release → nếu không merge hotfix vào, bug sẽ reappear trong release tiếp theo (develop không có fix). Đây là lỗi phổ biến trong Git Flow: chỉ merge vào main và nghĩ xong, nhưng develop vẫn có bug. Luôn merge hotfix branch vào cả hai.

  4. conventional-changelog cần điều kiện gì trong commit history để hoạt động tốt?

    Xem đáp án

    Commit history phải theo Conventional Commits format (feat:, fix:, docs:, chore:, BREAKING CHANGE:...). Tool parse commit messages để phân loại và generate changelog. Nếu commit messages không theo format (ví dụ "update stuff", "fix bug"), tool không thể phân loại và changelog sẽ thiếu/sai. Cần enforce bằng commitlint + Husky hook.

  5. Lệnh nào xoá một tag trên remote repository?

    Xem đáp án

    git push origin --delete v1.0.0-beta hoặc cú pháp cũ git push origin :refs/tags/v1.0.0-beta. Nhớ xoá cả local tag: git tag -d v1.0.0-beta. Xoá tag trên remote không tự động xoá tag local và ngược lại — cần làm cả hai bước riêng.

Bài tập thực hành

mkdir release-lab && cd release-lab
git init
git config user.name "Dev" && git config user.email "dev@test.com"

# Tạo vài commits theo Conventional Commits
echo "version: 1.0.0" > package.json
git add . && git commit -m "feat: initial project setup"
echo "login handler" > auth.js
git add . && git commit -m "feat: add OAuth login"
echo "bug fix" >> auth.js
git add . && git commit -m "fix: resolve token refresh bug"

# Tạo annotated tag v1.0.0
git tag -a v1.0.0 -m "Release v1.0.0 — initial stable release"
git show v1.0.0

# Thêm commits sau release
echo "dark mode" > theme.js
git add . && git commit -m "feat: add dark mode support"

# Tạo release branch
git switch -c release/1.1.0
echo "1.1.0" > VERSION
git add . && git commit -m "chore: bump version to 1.1.0"

# Merge vào main và tag
git switch main
git merge --no-ff release/1.1.0 -m "chore: release v1.1.0"
git tag -a v1.1.0 -m "Release v1.1.0"

# Xem toàn bộ graph
git log --oneline --graph --all

# Simulate hotfix
git switch -c hotfix/1.1.1
echo "critical fix" >> auth.js
git add . && git commit -m "fix: patch critical security vulnerability"
git switch main
git merge --no-ff hotfix/1.1.1 -m "chore: hotfix v1.1.1"
git tag -a v1.1.1 -m "Hotfix v1.1.1"

# Xem tags
git tag -l
git log --oneline --graph --all

Tài liệu tham khảo chính thức


Tiếp theo: Ngày 5 — Fork/Upstream Sync