Thẻ (tag) trong Git: cách dùng git tag và đánh số phiên bản phát hành
git tag gắn một nhãn cố định vào một commit cụ thể, thường để đánh dấu bản phát hành như v1.2.0. Thẻ chú thích được tạo bằng git tag -a v1.2.0 -m "thông điệp", còn gửi lên máy chủ riêng bằng git push origin v1.2.0 hoặc git push --tags.
git tag là nhãn có tên gắn với một commit cụ thể. Khác với nhánh, thẻ không di chuyển khi có commit mới: v1.2.0 sau một năm vẫn trỏ tới đúng ảnh chụp mã đó. Vì vậy thẻ được dùng cho các bản phát hành, và hệ thống CI/CD thường bắt đầu build và công bố ngay khi có thẻ mới.
Thẻ nhẹ và thẻ chú thích
Trong Git có hai loại thẻ, và sự khác biệt rất quan trọng.
| Thẻ nhẹ (lightweight) | Thẻ chú thích (annotated) | |
|---|---|---|
| Cách tạo | git tag v1.0.0 | git tag -a v1.0.0 -m "..." |
| Bản chất | chỉ là con trỏ tới commit | một đối tượng riêng trong cơ sở dữ liệu Git |
| Tác giả, ngày, thông điệp | không có | có |
| Chữ ký GPG/SSH | không | có thể (-s) |
Mặc định git describe nhìn thấy | không | có |
| Khi nào dùng | nhãn tạm cá nhân | bản phát hành và mọi thứ được công bố |
Với bản phát hành hãy dùng thẻ chú thích: chúng ghi lại ai và khi nào đã phát hành phiên bản, và có thể ký số.
Cách tạo thẻ
Trên commit hiện tại
git tag -a v1.2.0 -m "Bản phát hành 1.2.0: xuất báo cáo, sửa timeout API"
Nếu bỏ -m, Git sẽ mở trình soạn thảo để viết thông điệp — tiện khi muốn ghi danh sách thay đổi.
Trên commit cũ
Quên đặt thẻ khi phát hành cũng không sao — có thể gắn thẻ vào bất kỳ commit nào:
git log --oneline -5
# 7be41d0 feat: xuất báo cáo
# 1c02e8f fix: timeout khi gọi yêu cầu
git tag -a v1.1.0 1c02e8f -m "Bản phát hành 1.1.0"
Thẻ có chữ ký
git tag -s v1.2.0 -m "Bản phát hành 1.2.0"
git tag -v v1.2.0 # kiểm tra chữ ký
Cần khóa GPG đã được cấu hình (hoặc khóa SSH khi gpg.format=ssh). GitHub và GitLab hiển thị các thẻ như vậy với dấu xác minh.
Cách xem thẻ
git tag # tất cả thẻ theo thứ tự bảng chữ cái
git tag -l "v1.*" # theo mẫu
git tag -n # kèm dòng đầu của thông điệp
git tag --sort=-v:refname # theo phiên bản, mới nhất lên đầu
git show v1.2.0 # thông tin thẻ và commit
git describe --tags # thẻ gần nhất: v1.2.0-3-g7be41d0
Chú ý thứ tự sắp xếp: mặc định v1.10.0 sẽ đứng trước v1.9.0, vì chuỗi được so sánh theo từng ký tự. Khóa --sort=v:refname sắp xếp theo phiên bản.
Kết quả git describe đọc như sau: có ba commit sau thẻ v1.2.0, commit hiện tại là 7be41d0 (chữ g nghĩa là git). Đây là nguồn số phiên bản tiện cho các bản build giữa các lần phát hành.
Cách gửi thẻ lên máy chủ
Lệnh git push thông thường không gửi thẻ — đây là lý do phổ biến khiến người ta hỏi «sao trên GitHub không có thẻ».
git push origin v1.2.0 # một thẻ
git push --tags # tất cả thẻ cục bộ
git push --follow-tags # commit cùng các thẻ chú thích trỏ tới chúng
--follow-tags an toàn hơn --tags: nó không gửi các thẻ nhẹ bạn đặt cho riêng mình. Có thể bật mặc định: git config --global push.followTags true.
Để lấy thẻ từ máy chủ: git fetch --tags. Lệnh git fetch thông thường cũng kéo về các thẻ trỏ tới commit được tải.
Cách xóa hoặc chuyển thẻ
Xóa
git tag -d v1.2.0 # cục bộ
git push origin --delete v1.2.0 # trên máy chủ
# cú pháp cũ tương đương: git push origin :refs/tags/v1.2.0
Chuyển thẻ sang commit khác
git tag -a -f v1.2.0 <hash-mới> -m "Bản phát hành 1.2.0"
git push origin -f v1.2.0
Về kỹ thuật là làm được, nhưng trong dự án công khai không nên chuyển thẻ đã công bố. Những người đã tải thẻ về sẽ không được cập nhật tự động: Git không ghi đè thẻ cục bộ đã có khi fetch nếu không có --force. Kết quả là mỗi người có một mã khác nhau cho cùng «v1.2.0». Đúng hơn là phát hành v1.2.1.
Biết những commit nào đã vào bản phát hành
Thẻ tiện làm ranh giới khoảng trong mọi lệnh đọc lịch sử:
git log v1.1.0..v1.2.0 --oneline # các commit giữa hai bản phát hành
git diff v1.1.0 v1.2.0 --stat # những tệp nào đã thay đổi
git tag --contains 1c02e8f # commit này có trong những bản phát hành nào
git merge-base --is-ancestor 1c02e8f v1.2.0 && echo "có trong bản phát hành"
Hai lệnh cuối trả lời câu hỏi hay gặp của đội hỗ trợ: «bản sửa đã đến tay người dùng chưa?». Nếu bản sửa chỉ nằm trong main còn bản phát hành được build trước đó, git tag --contains sẽ trả về danh sách rỗng — rõ ràng là cần thẻ mới.
Một mẹo khác là lấy mã nguồn của một phiên bản cụ thể mà không kèm lịch sử: git archive --format=zip -o app-1.2.0.zip v1.2.0. Cách này thường dùng để tạo gói giao cho khách hàng hoặc build trong môi trường cô lập.
Chuyển sang phiên bản theo thẻ
git switch --detach v1.1.0
# HEAD is now at 1c02e8f fix: timeout khi gọi yêu cầu
Bạn sẽ ở trạng thái detached HEAD: có thể xem và build, nhưng các commit mới sẽ không thuộc nhánh nào. Nếu cần sửa một phiên bản cũ, hãy tạo nhánh từ thẻ:
git switch -c hotfix/1.1.x v1.1.0
# sửa, commit
git tag -a v1.1.1 -m "Bản vá bảo mật"
git push origin hotfix/1.1.x --follow-tags
Đánh số phiên bản: SemVer
Cách đặt tên thẻ phổ biến nhất là Semantic Versioning: MAJOR.MINOR.PATCH.
- MAJOR tăng khi API thay đổi không tương thích ngược:
1.4.2 → 2.0.0. - MINOR — tính năng mới, vẫn tương thích:
1.4.2 → 1.5.0. - PATCH — sửa lỗi:
1.4.2 → 1.4.3. - Bản tiền phát hành được đánh dấu bằng hậu tố:
2.0.0-beta.1,2.0.0-rc.1.
Tiền tố v (v1.4.2) là quy ước thường gặp cho tên thẻ, còn bản thân SemVer không bắt buộc nó. Điều quan trọng là chọn một định dạng và giữ nguyên, nếu không các script tìm bản phát hành mới nhất sẽ bị hỏng.
Thẻ trong quy trình phát hành
Quy trình điển hình trong nhóm như sau:
- Các thay đổi được gộp vào main, test xanh.
- Cập nhật CHANGELOG và số phiên bản trong các tệp của dự án, nếu có lưu ở đó.
- Đặt thẻ chú thích:
git tag -a v1.5.0 -m "Bản phát hành 1.5.0". git push origin main --follow-tags.- CI thấy thẻ mới, build artifact và công bố bản phát hành; trên GitHub và GitLab, từ thẻ sẽ tạo trang Release kèm mô tả và tệp.
Nhờ vậy, mọi phiên bản đang chạy trên production đều ánh xạ rõ ràng tới một đoạn mã: thẻ trong tên bản build cho biết nó là gì, còn git show cho biết có những gì bên trong. Lệnh git log v1.4.0..v1.5.0 --oneline cho danh sách thay đổi giữa các bản phát hành để viết ghi chú.
Ghi chú phát hành từ thẻ
Nếu thông điệp commit đầy đủ ý nghĩa, thẻ biến lịch sử thành danh sách thay đổi có sẵn. Có thể tìm bản phát hành trước và sau mà không cần dò tay:
LAST=$(git describe --tags --abbrev=0)
PREV=$(git describe --tags --abbrev=0 "$LAST^")
git log "$PREV..$LAST" --no-merges --pretty=format:"- %s (%h)"
# - feat: xuất báo cáo (7be41d0)
# - fix: timeout khi gọi yêu cầu (1c02e8f)
Nếu nhóm viết commit theo quy ước Conventional Commits (feat:, fix:, feat!:), số phiên bản tiếp theo có thể tính tự động: fix tăng PATCH, feat tăng MINOR, dấu chấm than hoặc BREAKING CHANGE tăng MAJOR. Các công cụ phát hành tự động phổ biến được xây dựng trên nguyên tắc này: chúng tự đặt thẻ và viết CHANGELOG.
Thẻ và CI/CD
Pipeline thường phân biệt hai sự kiện: push lên một nhánh và xuất hiện một thẻ. Khi có push lên nhánh, chạy test và build môi trường thử; khi có thẻ — công bố. Trong GitHub Actions điều này trông như sau:
on:
push:
tags:
- "v*.*.*"
Trong GitLab CI, vai trò tương tự do biến CI_COMMIT_TAG và quy tắc rules: - if: $CI_COMMIT_TAG đảm nhận. Tên thẻ trong pipeline trở thành số phiên bản của image Docker hoặc gói, vì vậy định dạng thẻ phải nghiêm ngặt và dễ đoán.
Đây cũng là lý do khác để không chuyển thẻ: nếu pipeline đã công bố gói 1.2.0 lên kho, việc công bố lại với cùng số thường bị cấm, còn image có thẻ 1.2.0 ở phía người dùng sẽ vẫn là bản cũ.
Quy tắc đặt tên thẻ
Tên thẻ tuân theo cùng quy tắc với tên nhánh: không có khoảng trắng, không có các ký tự ~ ^ : ? * [ và không có hai dấu chấm liên tiếp. Dấu gạch chéo được phép, vì vậy trong monorepo có thể gặp thẻ dạng api/v1.2.0 và web/v3.0.1 — mỗi gói có dãy phiên bản riêng. Có thể kiểm tra trước tên bằng git check-ref-format "refs/tags/v1.2.0": nếu sai, lệnh trả về mã khác 0.
Bảo vệ thẻ
Trong các kho làm việc nhóm, nên bảo vệ thẻ phát hành: GitHub và GitLab cho phép cấm tạo, ghi đè và xóa thẻ theo mẫu (ví dụ v*) đối với mọi người trừ người bảo trì. Nhờ vậy, một lệnh git push --tags vô tình từ máy người khác sẽ không công bố bản phát hành ngoài kế hoạch, và lệnh git push origin --delete nhầm không xóa mất lịch sử phiên bản.
Lỗi thường gặp
- Thẻ không có trên máy chủ. Quên
git push origin <thẻ>hoặc--follow-tags. - Thẻ trùng tên với nhánh. Git cảnh báo
refname is ambiguous; hãy dùng tiền tốvcho thẻ và đặt tên khác nhau. - Đặt thẻ trên trạng thái chưa commit. Thẻ gắn vào commit, không phải tệp: thay đổi chưa commit sẽ không đi vào bản phát hành.
- Dùng thẻ nhẹ cho bản phát hành. Không có tác giả, ngày và thông điệp, còn
git describemặc định không nhìn thấy nó.
Tóm lại
git tag đánh dấu một điểm bất biến trong lịch sử: với bản phát hành hãy tạo thẻ chú thích bằng git tag -a, đặt tên theo SemVer, và gửi lên máy chủ một cách tường minh — git push origin <thẻ> hoặc --follow-tags. Thẻ đã công bố thì đừng chuyển, hãy phát hành phiên bản mới. Thẻ cùng với rebase, stash và cherry-pick được thực hành trong khóa học Git.
Câu hỏi thường gặp
Thẻ khác nhánh ở điểm nào?
Nhánh tự động dịch chuyển khi có commit mới, còn thẻ cố định trỏ mãi tới một commit.
Có thể đặt hai thẻ lên cùng một commit không?
Có, số lượng thẻ trên một commit không giới hạn, ví dụ v2.0.0 và latest-stable.
Xóa nhánh có làm mất thẻ không?
Không, thẻ độc lập với nhánh, và commit có thẻ vẫn còn truy cập được ngay cả sau khi nhánh bị xóa.
Làm sao biết một commit đã vào bản phát hành nào?
Chạy git tag --contains <hash> — lệnh sẽ liệt kê mọi thẻ có commit này trong lịch sử.
Làm sao lấy thẻ mới nhất trong script?
Dùng git describe --tags --abbrev=0 — lệnh in tên thẻ gần nhất, không kèm phần hash.
Chủ đề này được giảng chi tiết kèm thực hành trong khóa học “Git с нуля до уверенного разработчика”.
Đến khóa học