Yêu cầu với lập trình viên
Quản lý branch và commit
Quy ước branch, commit, rebase và merge. Mục tiêu là branch main luôn release được, và lịch sử commit đọc được như một bản tường thuật.
Nội dung bài
Thuật ngữ trong bài (18)
- quality gate · cổng chất lượng
- spike · việc tìm hiểu có giới hạn
- artifact · sản phẩm dựng
- branch · nhánh
- build · bản dựng
- CI · tích hợp liên tục
- commit · điểm lưu thay đổi
- hotfix · bản vá nóng
- merge · gộp nhánh
- pipeline · dây chuyền tự động
- pull request · yêu cầu gộp mã
- rebase · đặt lại gốc nhánh
- refactor · sửa cấu trúc mã
- feature flag · công tắc tính năng
- release · bản phát hành
- secret · thông tin bí mật
- idempotency · gọi lại không nhân đôi
- token · khóa truy cập
Hai luật không đổi
- Branch
mainluôn ở trạng thái release được. Bất kỳ lúc nào cắt ra cũng phải build, test xanh và chạy được. Không có ngoại lệ "tạm thời hỏng vài tiếng". - Không ai push thẳng lên
main. Mọi thay đổi đi qua pull request, kể cả sửa một ký tự, kể cả chủ kỹ thuật. Branchmainđược bảo vệ ở cấu hình repo, không dựa vào trí nhớ.
Mô hình branch
Dùng mô hình đơn giản nhất còn dùng được: trunk-based, branch ngắn ngày.
main ────●────●────●────●────●────●──────>
\ / \ /
●──●──● ●──●──●
feature/chat-retry fix/order-dup
| Loại branch | Tiền tố | Vòng đời tối đa | Ghi chú |
|---|---|---|---|
| Tính năng | feature/ | 2 ngày | Dài hơn thì hạng mục quá to, chẻ nhỏ |
| Sửa lỗi | fix/ | 1 ngày | |
| Sửa gấp production | hotfix/ | Vài giờ | Cắt từ tag release, không cắt từ main |
| Dọn dẹp, nợ kỹ thuật | chore/ | 2 ngày | |
| Thử nghiệm, không merge | spike/ | 3 ngày | Xoá sau khi có kết luận |
Vòng đời tối đa 2 ngày là con số quan trọng nhất trong bảng. Branch sống lâu là nguồn
gốc của conflict lớn, review khổng lồ và merge đau đớn. Nếu tính năng chưa hoàn chỉnh mà đã
tới hạn, merge phần đã xong vào main phía sau một feature flag đang tắt.
Đặt tên branch
<loại>/<mô-tả-ngắn-không-dấu>
feature/chat-offline-queue
fix/order-total-rounding
hotfix/login-crash-android-14
Không dùng tên riêng (branch-cua-nam), không dùng số thứ tự vô nghĩa (test2, new-fix).
Commit
Kích thước
Một commit = một thay đổi logic hoàn chỉnh. Đọc tên commit là biết nó làm gì, và nếu revert riêng commit đó thì repo vẫn ở trạng thái hợp lệ.
- Sai: một commit khổng lồ cuối ngày tên là "update".
- Sai: mười commit tên "fix", "fix again", "final fix".
- Đúng:
fix(order): làm tròn tổng tiền theo đơn vị đồng thay vì theo float.
Định dạng thông điệp
Dùng conventional commit:
<type>(<scope>): <mô tả ngắn, thể mệnh lệnh, không dấu chấm cuối>
<thân bài: vì sao thay đổi, không phải thay đổi cái gì>
<footer: tham chiếu issue, ghi chú breaking change>
| Type | Dùng khi |
|---|---|
feat | Thêm hành vi người dùng thấy được |
fix | Sửa lỗi |
refactor | Đổi cấu trúc, không đổi hành vi |
perf | Cải thiện hiệu năng |
test | Thêm hoặc sửa test |
docs | Tài liệu |
build | Build, dependency, cấu hình đóng gói |
ci | Pipeline |
chore | Việc lặt vặt còn lại |
scope là module hoặc vùng: chat, task, sales, auth, core.
Ví dụ đủ:
fix(sales): chặn tạo đơn trùng khi người dùng bấm nút hai lần
Nút thanh toán không bị vô hiệu hoá trong lúc chờ server trả về, người dùng
bấm lại tạo ra đơn thứ hai. Thêm idempotency key sinh từ client và vô hiệu
hoá nút trong lúc chờ.
Refs: #318
Phần thân bài giải thích vì sao là phần có giá trị nhất và cũng là phần hay bị bỏ nhất.
Sáu tháng sau, git log là tài liệu duy nhất còn lại.
Cấm commit
- Secret, token, khoá riêng, file
.env, chuỗi kết nối database. - File build, thư mục
node_modules, artifact, file IDE cá nhân — đưa vào.gitignore. - Dữ liệu thật của người dùng, kể cả để làm dữ liệu test.
- File nhị phân lớn không cần thiết.
Repo có secret scanner trong pre-commit hook và trong CI. Nếu scanner chặn được thì tốt, nhưng một secret đã từng nằm trong lịch sử git coi như đã lộ — phải xoay vòng khoá đó ngay, không chỉ xoá commit.
Giữ branch cập nhật
Mỗi ngày ít nhất một lần, đồng bộ branch của bạn với main:
git fetch origin
git rebase origin/main
Dùng rebase cho branch cá nhân của mình để lịch sử thẳng và dễ đọc. Nếu branch đã được người khác lấy về, không rebase — dùng merge, vì rebase viết lại lịch sử sẽ làm hỏng bản sao của họ.
Làm hằng ngày thì conflict nhỏ và dễ. Để tới lúc mở PR thì conflict to và dễ giải sai. Đây lại là shift left: kéo lỗi tích hợp về sớm mỗi ngày thay vì dồn cục vào cuối.
Xử lý conflict
- Không bao giờ "chọn bên mình cho nhanh". Đọc cả hai bên và hiểu vì sao lệch.
- Conflict ở phần code bạn không hiểu thì gọi người viết phần đó, đừng đoán.
- Sau khi giải xong, chạy lại toàn bộ test trước khi push. Giải conflict là lúc dễ làm mất một thay đổi của người khác nhất, và test là thứ duy nhất phát hiện ra.
Force-push
- Được phép force-push lên branch cá nhân của mình khi chưa ai review, và luôn dùng
--force-with-lease, không dùng--force. - Cấm tuyệt đối force-push lên
main, lên branch release, và lên branch có người khác đang làm cùng.
Merge vào main
Điều kiện đủ để merge:
- Toàn bộ quality gate tự động xanh.
- Đủ số lượng approve theo chuẩn pull request.
- Đã rebase lên
mainmới nhất, không còn conflict. - Mọi comment review đã được xử lý hoặc trả lời.
Cách merge: squash thành một commit khi branch có nhiều commit vụn; giữ nguyên các commit khi từng commit đều sạch và có ý nghĩa riêng. Không dùng merge commit rỗng.
Sau khi merge: xoá branch ngay. Repo có 40 branch chết là repo không ai dám dọn.
Sau khi merge, việc chưa xong
Người merge có trách nhiệm theo dõi pipeline trên main. Nếu nó đỏ:
- Báo vào kênh chung của đội ngay, để người khác không merge chồng lên.
- Sửa trong 30 phút hoặc revert.
- Revert không phải thất bại. Để
mainđỏ qua đêm mới là thất bại — nó chặn toàn bộ đội.

