Branching strategy: main được bảo vệ và nhánh feature ngắn ngày
Mục tiêu bài học
Sau bài này, bạn sẽ:
- Giải thích được vì sao
mainphải là nhánh "sạch" và chỉ nhận thay đổi qua pull request. - Bật được bộ rule bảo vệ
maintối thiểu trên GitHub cho một team nhỏ. - Viết được quy ước đặt tên và vòng đời cho nhánh feature ngắn ngày.
- Chọn được chiến lược nhánh phù hợp với team 2 đến 5 người làm project blog.
Ôn nhanh
Ba bài trước bạn đã có đủ đồ nghề: tạo và dọn branch, đọc dấu conflict khi merge, rebase và cắt tag v1.0. Bài này không dạy lại các lệnh đó. Câu hỏi mới là: nhánh nào được sống, sống bao lâu, và ai được phép merge vào main.
Mình hay nói với intern: lệnh Git thì bạn học trong một buổi, còn quy ước thì quyết định team bạn có đau đầu hay không.
Lý thuyết
1. main là sự thật chung, không phải chỗ thử nghiệm
Trong project blog, main là bản đang chạy thật. Nếu ai cũng push thẳng lên main, ba chuyện sau sẽ xảy ra, thường trong cùng một tuần:
- Có commit làm hỏng build, và không ai biết ai đẩy.
- Hai người sửa cùng file, người sau
push --forceđè mất code người trước. - Lịch sử commit thành một mớ hỗn độn, không
revertnổi một feature.
Cách chặn rẻ nhất là protected branch: GitHub từ chối mọi push trực tiếp, buộc mọi thay đổi đi qua pull request.
flowchart TD
Main["🛡️<br/>main (protected)"]
Feat["🌿<br/>feature/them-trang-lien-he"]
PR["🔀<br/>Pull request"]
Rev["👀<br/>Review + CI xanh"]
Merge["✅<br/>Squash merge"]
Del["🗑️<br/>Xoá branch"]
Main -->|"tạo branch"| Feat
Feat -->|"push"| PR
PR --> Rev
Rev -->|"approve"| Merge
Merge --> Del
2. Bộ rule tối thiểu cho team nhỏ
Vào Settings → Rules → Rulesets (hoặc Branches với repo cũ), tạo ruleset nhắm vào main. Đây là bộ mình dùng cho team 3 người:
| Rule | Bật? | Vì sao |
|---|---|---|
| Require a pull request before merging | Có | Chặn push thẳng, mọi thay đổi đều có ngữ cảnh |
| Required approvals: 1 | Có | Một người review là đủ, miễn không phải tác giả |
| Dismiss stale approvals | Có | Có commit mới thì approval cũ hết giá trị |
| Require conversation resolution | Có | Không bỏ sót comment review |
| Require linear history | Có | Khớp với rebase/squash, lịch sử một đường thẳng |
| Block force pushes | Có | Không ai push --force đè main |
| Restrict deletions | Có | Không xoá nhầm nhánh main |
| Require status checks | Khi có CI | Chưa có CI thì bật sau, đừng bật cho có |
| Do not allow bypassing | Tuỳ team | Bật thì admin cũng phải theo rule |
[!WARNING] Đừng bật hết mọi rule trong ngày đầu. Team sẽ bực vì PR kẹt, rồi admin tắt rule để "chạy gấp", và thế là mất luôn thói quen. Bật 4 rule đầu, ổn định một tuần, rồi thêm.
Với repo private trên gói Free, một số rule bảo vệ có thể không bật được. Kiểm tra plan trước khi hứa với team.
3. Nhánh feature ngắn ngày
Nguyên tắc: một branch, một mục tiêu, một issue. Nhánh sống càng lâu, conflict càng lớn, review càng mệt.
- Vòng đời: 1 đến 3 ngày. Ước lượng dài hơn thì tách thành hai nhánh.
- Luôn tạo từ
mainmới nhất:git switch main && git pull && git switch -c feature/... - Mỗi sáng rebase lên
main:git fetch origin && git rebase origin/main - Đặt tên theo tiền tố:
feature/,fix/,chore/+ mô tả ngắn có gạch nối. - Merge xong thì xoá, cả remote lẫn local.
4. Chọn chiến lược nào?
[
{ "label": "GitHub Flow", "content": "main + nhánh feature ngắn ngày + PR. Đây là lựa chọn mặc định cho team nhỏ làm blog. Ít khái niệm, ít nhánh, dễ dạy người mới." },
{ "label": "Trunk-based", "content": "Gần giống GitHub Flow nhưng nhánh còn ngắn hơn (dưới 1 ngày), thường có feature flag. Tốt khi team đã quen và có CI mạnh. Team mới dễ vỡ nếu chưa có test." },
{ "label": "Git Flow", "content": "Có develop, release/*, hotfix/* sống dài hạn. Phù hợp sản phẩm đóng gói theo quý. Với blog của 3 người thì đây là overhead: nhánh lệch nhau, merge chéo, release ì ạch." }
]
Với capstone blog của khoá này, mình chốt GitHub Flow.
Ví dụ minh họa
Bối cảnh: blog đã có trang chủ, bạn nhận issue "Thêm trang liên hệ".
Bước 1: tạo nhánh từ main mới nhất
git switch main
git pull origin main
git switch -c feature/them-trang-lien-he
Bước 2: làm việc nhỏ, commit rõ ràng
# tạo contact.html, thêm link ở nav
git add contact.html
git commit -m "feat: them trang lien he co form lien lac"
Bước 3: đẩy lên và mở pull request
git push -u origin feature/them-trang-lien-he
Bước 4: bật bảo vệ main, rồi merge qua PR
1. Vào Settings → Rules → New ruleset → Target branches: `main`
2. Bật Require a pull request, Required approvals = 1, Block force pushes, Restrict deletions
3. Mở PR từ `feature/them-trang-lien-he` vào `main`, gắn issue vào PR
4. Nhờ một bạn review, sửa theo comment, đẩy thêm commit nếu cần
5. Bấm Squash and merge, rồi Delete branch trên GitHub
6. Dọn local: `git switch main && git pull && git branch -d feature/them-trang-lien-he`
Kết quả: main nhận đúng một commit gọn gàng, lịch sử thẳng, và nhánh feature biến mất sau 2 ngày.
Luyện tập có hướng dẫn
Bài 1. Team bạn 3 người, repo private trên gói Free. Bạn muốn bật "Require a pull request before merging". Trước khi hứa với team, bạn kiểm tra gì?
Xem gợi ý
Kiểm tra plan của repo (Settings → General) xem có bật được branch protection/ruleset cho repo private không. Nếu không, chuyển repo sang public cho đồ án, hoặc nâng plan. Đừng hứa "mai có rule" rồi phát hiện không bật được.
Bài 2. Bạn tạo feature/them-trang-lien-he từ main, nhưng 4 ngày sau vẫn chưa xong vì vừa làm trang liên hệ vừa đổi theme. Xử lý thế nào?
Xem gợi ý
Tách đôi: git switch main && git pull, tạo feature/doi-theme, cherry-pick hoặc copy các commit liên quan theme sang. Nhánh cũ quay về đúng một mục tiêu là trang liên hệ, mở PR sớm. Nhánh sống quá 3 ngày là dấu hiệu phạm vi quá rộng.
Bài 3. Một bạn trong team bảo "bật Require linear history đi, nhưng tớ vẫn cần merge commit để giữ nhánh". Điều gì sẽ xảy ra?
Xem gợi ý
GitHub sẽ chặn nút "Create a merge commit", chỉ còn Squash và Rebase. Nếu team muốn merge commit thì đừng bật rule này, nhưng lịch sử main sẽ có nhiều ngã ba. Với team nhỏ, mình chọn Squash và bật linear history.
Góc chuyên gia
- Lỗi thường gặp #1: bật rule xong vẫn tự push vì tài khoản admin được bypass. Bật "Do not allow bypassing" nếu bạn muốn kỷ luật thật.
- Lỗi thường gặp #2: nhánh sống 2 tuần, rebase một lần ra 40 conflict. Rebase lên
mainmỗi ngày, conflict sẽ nhỏ như vết xước. - Lỗi thường gặp #3: quên xoá nhánh sau merge. Remote còn 30 nhánh chết, không ai biết nhánh nào còn sống. Bật "Automatically delete head branches" trong Settings.
- Lỗi thường gặp #4: review xong, tác giả đẩy thêm commit mà approval vẫn còn hiệu lực. Bật "Dismiss stale approvals".
- Rule of thumb: nếu một PR không review xong trong một ngày làm việc, PR đó đang quá to.
Tóm tắt và bước tiếp theo
Bốn câu chốt:
mainđược bảo vệ, mọi thay đổi đi qua pull request có ít nhất 1 approval.- Nhánh feature ngắn ngày, một mục tiêu, sống 1 đến 3 ngày, tạo từ
mainmới nhất. - Bật rule vừa đủ, tăng dần; đừng bật hết rồi tắt vội.
- Team nhỏ dùng GitHub Flow, để Git Flow cho sản phẩm đóng gói theo quý.
Ôn tập nhanh:
Rule chặn push trực tiếp vào main trên GitHub là {{c1::Require a pull request before merging}}.
Nhánh feature cho team nhỏ nên sống khoảng {{c2::1 đến 3 ngày}}.
Bật {{c3::Require linear history}} thì main chỉ nhận commit từ squash hoặc rebase.
Rule {{c4::Restrict deletions}} ngăn ai đó xoá nhánh main.
Tên nhánh nên theo quy ước {{c5::feature/ + mô tả ngắn có gạch nối}}.
Thẻ ôn tập:
[
{ "front": "Mục tiêu chính của việc bảo vệ main là gì?", "back": "Giữ main luôn ở trạng thái chạy được, mọi thay đổi phải qua pull request." },
{ "front": "Rule nào chặn push trực tiếp vào main?", "back": "Require a pull request before merging." },
{ "front": "Team nhỏ nên đặt Required approvals bao nhiêu?", "back": "1 approval, miễn người review không phải tác giả." },
{ "front": "Dismiss stale approvals làm gì?", "back": "Huỷ approval cũ khi có commit mới đẩy lên PR." },
{ "front": "Require linear history phù hợp với thao tác nào đã học?", "back": "Rebase hoặc squash merge, tránh merge commit rối lịch sử." },
{ "front": "Nhánh feature ngắn ngày nên sống bao lâu?", "back": "Khoảng 1 đến 3 ngày, dài hơn thì tách nhỏ." },
{ "front": "Nhánh feature luôn được tạo từ đâu?", "back": "Từ main mới nhất, sau khi git pull." },
{ "front": "Quy ước đặt tên branch cho team nhỏ?", "back": "Tiền tố feature/, fix/, chore/ cộng mô tả ngắn có gạch nối." },
{ "front": "Merge PR xong thì làm gì với branch?", "back": "Xoá cả remote lẫn local, hoặc bật tự động xoá head branch." },
{ "front": "Vì sao không nên dùng Git Flow cho team 3 người?", "back": "Nhiều nhánh dài hạn (develop, release, hotfix), overhead merge cao, dễ lệch main." },
{ "front": "Do not allow bypassing có tác dụng gì?", "back": "Admin cũng phải theo rule, không push tắt khi gấp." },
{ "front": "Rule nào ngăn xoá nhánh main?", "back": "Restrict deletions." },
{ "front": "Vì sao nên rebase nhánh feature lên main mỗi ngày?", "back": "Giữ conflict nhỏ, PR dễ review, tránh merge muộn." }
]
Bài tiếp theo: bạn đã có luật cho main và nhánh feature. Giờ là phần con người: cách viết pull request, gắn issue, và review sao cho đồng nghiệp vui vẻ sửa code thay vì phòng thủ.