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
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
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.
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ặccommit-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
-
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ằngfeat!:,fix!:, hoặc footerBREAKING CHANGE:. -
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. -
Tại sao hotfix phải được merge vào cả
mainlẫndevelop?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. -
conventional-changelogcầ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ằngcommitlint+ Husky hook. -
Lệnh nào xoá một tag trên remote repository?
Xem đáp án
git push origin --delete v1.0.0-betahoặ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
- Semantic Versioning 2.0.0
- Git Tagging
- conventional-changelog
- GitHub: Managing releases
- Git Tools: Signing Your Work
Tiếp theo: Ngày 5 — Fork/Upstream Sync