Git có một cơ chế tự động hóa ít người khai thác đúng mức: hooks — các script Git tự gọi ở những thời điểm nhất định (trước khi commit, sau khi commit, trước khi push...). Chúng cho phép ép quy tắc mà không cần nhớ: chặn commit còn console.log/TODO, bắt buộc message theo định dạng, chạy linter/test trước khi push. Nhưng hooks cũng có một cái bẫy lớn khiến nhiều đội tưởng đã "bật" mà thực ra không. Đo thật bằng git 2.39.
Hook là script chạy ở sự kiện Git
Hooks nằm trong thư mục .git/hooks/, tên file = tên sự kiện, cần quyền thực thi (chmod +x). Git cài sẵn các file mẫu *.sample — đổi tên bỏ .sample là kích hoạt.
- pre-commit: chạy trước khi commit. Exit code khác 0 → hủy commit. Dùng để chặn code xấu.
- commit-msg: nhận đường dẫn file chứa message → kiểm định dạng, chặn nếu sai.
- pre-push: chạy trước push → chặn nếu test fail.
# .git/hooks/pre-commit (đã chmod +x)
#!/bin/bash
git diff --cached | grep -q "TODO_KHONG_COMMIT" && { echo "còn TODO!"; exit 1; }
exit 0

Hình 1: Hook là script ở .git/hooks/, tên = tên sự kiện, exit != 0 thì hủy hành động. Client hook (dev bỏ qua được bằng --no-verify) vs server hook (ép luật thật sự). Bẫy lớn: hook nằm trong .git nên không clone theo.
Đo thật: chặn commit theo luật

Hình 2: .git/hooks có các mẫu .sample. pre-commit chặn commit chứa TODO_KHONG_COMMIT (exit != 0 → hủy), sửa bỏ thì commit thành công (3f0152a). commit-msg bắt buộc mã ticket: "sua linh tinh" bị chặn, "[APP-42] ..." qua. Nhớ dùng core.hooksPath để chia sẻ.
Kết quả cho thấy hook hoạt động đúng:
- pre-commit chặn theo nội dung: commit code chứa
TODO_KHONG_COMMIT→ hook in cảnh báo và trả exit khác 0 → Git hủy commit. Sửa bỏ dòng đó, commit thành công (3f0152a). Đây là cách chặnconsole.log, secret,debugger, file lớn... lọt vào commit. - commit-msg ép định dạng: hook kiểm message qua regex
[A-Z]+-[0-9]+; message"sua linh tinh"(thiếu ticket) → bị chặn,"[APP-42] sua dung chuan"→ qua. Rất hợp để bắt buộc quy ước commit (Conventional Commits, mã ticket) trong đội.
Client hooks vs server hooks — và ai ép được luật
Đây là phân biệt cốt lõi:
- Client hooks (pre-commit, commit-msg, pre-push) chạy trên máy dev. Tiện, phản hồi nhanh — nhưng dev bỏ qua được bằng
git commit --no-verify. Nên chúng là trợ giúp, không phải hàng rào cứng. - Server hooks (pre-receive, update, post-receive) chạy trên máy chủ khi nhận push. Dev không bỏ qua được — đây mới là nơi ép luật thật sự (từ chối push không đúng chuẩn, chặn force-push vào nhánh chính, kích hoạt CI/CD). Trên GitHub/GitLab, vai trò này thường do "branch protection rules" và CI đảm nhận thay cho server hook thủ công.
Cái bẫy lớn: hook không clone theo
.git/hooks/ nằm trong thư mục .git — không được commit, không clone theo. Nghĩa là bạn viết pre-commit tuyệt vời trên máy mình, nhưng đồng đội clone repo về không có nó. Cả đội tưởng đã bật kiểm tra nhưng thực ra chỉ máy bạn có. Cách chia sẻ hook qua repo:
- Đặt hook trong một thư mục được commit (ví dụ
.githooks/), rồi mỗi người chạygit config core.hooksPath .githooks(hoặc script setup làm giúp). - Hoặc dùng công cụ quản hook: husky (JS), pre-commit (Python, đa ngôn ngữ), lefthook. Chúng cài hook từ file cấu hình trong repo, đồng bộ cho cả đội.
Đánh đổi và lưu ý
Hook phải nhanh. pre-commit chạy mỗi lần commit; nếu nó lint cả repo mất 30 giây, dev sẽ --no-verify cho nhanh — phản tác dụng. Chỉ kiểm file đang staged (git diff --cached --name-only), không quét toàn bộ. Việc nặng (test đầy đủ) để cho CI.
Đừng để hook chặn ẩu. Hook chặn sai (false positive) làm dev bực và học cách bỏ qua. Quy tắc phải rõ ràng, thông báo lỗi phải nói chính xác sai gì và sửa thế nào. Và luôn có đường thoát hợp lý cho trường hợp khẩn (nhưng đừng lạm dụng --no-verify).
Hook không thay CI. Client hook là "lưới đầu tiên" trên máy dev, nhanh và tiện; nhưng vì bỏ qua được và không chia sẻ chắc chắn, đừng coi nó là bảo đảm cuối cùng. Luật thật sự (test phải xanh, chuẩn code) phải ép ở CI/server, nơi không ai vượt được.
Ba ý mang về
- Hook là script Git tự chạy ở sự kiện, exit != 0 thì hủy hành động: đo thật,
pre-commitchặn commit chứaTODO_KHONG_COMMIT,commit-msgbắt buộc mã ticket[APP-42]— tự động ép quy tắc mà không cần nhớ. - Client hook bỏ qua được (
--no-verify), server hook mới ép luật thật: client là trợ giúp nhanh trên máy dev; luật cứng (chặn push sai, force-push) phải ở server/CI/branch protection. - Hook không clone theo (nằm trong
.git) — đồng đội không có; chia sẻ bằngcore.hooksPathtrỏ vào thư mục được commit, hoặc dùng husky/pre-commit; giữ hook nhanh và đừng coi nó thay CI.
Nguồn
- Pro Git — Customizing Git: Git Hooks: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
- Git Documentation — githooks: https://git-scm.com/docs/githooks
Phần sau ta dùng rebase ở chế độ mạnh nhất: git rebase -i (tương tác) — squash gộp commit, reword sửa message, reorder đổi thứ tự, dọn lịch sử nhánh cho sạch trước khi mở PR.