---
id: git-hooks
title: "Git hooks"
shortTitle: "Git hooks"
track: git
trackTitle: "Git & VCS Hosting"
order: 6
level: important
status: published
estimatedMinutes: 30
prerequisites:
  - git-recovery
next: git-gitlab
reviewedAgainst: 2026-10-15
sources:
  - title: "KodeKloud Engineer — 100 Days of DevOps"
    url: https://engineer.kodekloud.com/practice
  - title: "githooks"
    url: https://git-scm.com/docs/githooks
  - title: "git-config core.hooksPath"
    url: https://git-scm.com/docs/git-config#Documentation/git-config.txt-corehooksPath
---

# Git hooks

Hook là script mà Git tự chạy trước hoặc sau một thao tác. Hook nằm trên máy người commit, trừ khi server cũng cài hook riêng.

## Để làm gì

Bạn dùng hook để chặn commit ngay trên máy khi diff có thứ không được phép, và đặt hook trong repo để cả team dùng chung một bản. Hook local giúp phát hiện lỗi sớm, nhưng không thay được CI.

## Gồm những gì

- `pre-commit`: nếu script exit khác 0 thì commit dừng.
- `commit-msg`, `pre-push`.
- File mẫu `.sample` trong `.git/hooks` sau `git init`. Bật bằng cách bỏ đuôi `.sample`.
- `core.hooksPath`: trỏ Git tới thư mục hook được commit trong repo.
- Giới hạn của hook local: đồng nghiệp chưa cấu hình thì hook không chạy trên máy họ.

Bài này không nói về server hook `pre-receive` trên GitHub, vì GitHub không cho bạn sửa hook trên server của họ. Check bắt buộc phải đặt ở Actions hoặc branch protection.

## Dùng khi nào

Dùng hook khi bạn muốn biết lỗi ngay trước lúc commit: file quá lớn, còn sót chuỗi `NOCOMMIT`, hoặc sai format. Còn rule mà cả team bắt buộc phải tuân thì đặt trên CI, để dù ai đó tắt hook thì CI vẫn bắt được.

Đừng để `pre-commit` là lớp secret scan duy nhất. Chỉ cần thêm `--no-verify` là người commit bỏ qua được nó.

## Kiến thức cốt lõi

Mặc định Git tìm hook trong `.git/hooks`, và thư mục này không được commit. `git init` copy file mẫu từ template, tên kết thúc bằng `.sample`, nên không chạy. Bật một mẫu: copy bỏ đuôi `.sample`, rồi `chmod +x` trên Linux và macOS. Docs `git-init` nói rõ: bật hook mẫu bằng cách bỏ `.sample`.

`core.hooksPath` đổi thư mục hook. Bạn tạo `.githooks` trong repo, commit script vào đó, rồi mỗi máy chạy `git config core.hooksPath .githooks`. Config này là local nên không đi theo clone; muốn tự động thì phải dùng template hoặc một script setup.

`pre-commit` chạy trước khi tạo commit. Nếu nó exit khác 0, Git hủy commit và không tạo object nào. `commit-msg` nhận đường dẫn tới file chứa message. `pre-push` chạy trước khi gửi lên remote.

`--no-verify` bỏ qua hook phía client. Vì thế đừng coi hook là cổng bảo mật.

## Lệnh và code mẫu

```bash
mkdir -p ~/devops-lab/hooks-demo && cd ~/devops-lab/hooks-demo
git init -b main
mkdir -p .githooks
```

`.githooks/pre-commit`:

```bash
#!/bin/sh
if git diff --cached --name-only | grep -qx 'secrets.env'; then
  echo "pre-commit: khong commit secrets.env" >&2
  exit 1
fi
```

```bash
git config core.hooksPath .githooks
printf 'token=lab\n' > secrets.env
git add secrets.env
git commit -m "lab"
```

Trên Git for Windows, shebang `#!/bin/sh` là đủ để Git chạy hook. Trên Linux và macOS, bạn cần thêm quyền chạy bằng `chmod +x .githooks/pre-commit`.

## Thực hành

1. Tạo repo như trên và cấu hình `core.hooksPath`.
2. Thử commit `secrets.env`. Commit phải bị từ chối.
3. Dùng `git reset` gỡ file đó khỏi index, thêm một file `README` một dòng, rồi commit lại. Lần này commit đi qua.
4. Xóa thư mục lab khi xong.

## Kết quả mong đợi

Lần commit `secrets.env` in ra dòng `khong commit secrets.env` và không tạo commit. Lần commit README thì tạo được commit.

## Checklist

- [ ] Đặt hook trong thư mục được commit, rồi trỏ `core.hooksPath`.
- [ ] Để check bắt buộc trên CI, hook chỉ để phản hồi sớm.
- [ ] Không coi `--no-verify` là đường tắt được phép trên nhánh `main`.

## Thẻ ôn

### git-hooks-path
kind: trap
cue: Sửa hook trong `.git/hooks` không đi theo repo. Đặt script trong thư mục được commit, rồi `git config core.hooksPath` trỏ vào đó.

Q: Bạn viết một hook chặn commit thiếu message, để trong `.git/hooks`, và nghĩ cả team cùng CI sẽ chạy cùng rule. Clone mới trên máy đồng nghiệp có hook đó không? Làm sao để hook đi theo repo?

A: Không có. `.git` là dữ liệu local của một bản clone, và `git push` không gửi thư mục hooks. Mỗi máy một bản, CI clone sạch cũng không có file bạn vừa sửa. Đặt script hook trong một thư mục nằm trong commit, ví dụ `.githooks`, rồi trên mỗi máy `git config core.hooksPath` trỏ vào thư mục đó. Lúc ấy Git chạy bản đã được commit, không chạy bản chỉ có trên máy bạn.

### git-hooks-ci
kind: decide
cue: Hook local chỉ để phản hồi trong vài giây. Test đầy đủ là required check trên CI. `--no-verify` không phải đường tắt trên `main`.

Q: Pre-commit chạy full `npm test` và mất khoảng một phút mỗi lần commit. Người trong team bắt đầu commit với `--no-verify` cho đỡ chờ. Check bắt buộc nên đặt ở đâu, và hook local nên làm việc gì?

A: Đặt trên CI, thành required check của pull request. Hook trên laptop có thể bị tắt từng lần commit, và máy CI không dùng hook đó làm cửa duy nhất. Hook local hợp để bắt lỗi nhanh, trong vài giây, ví dụ format hoặc một kiểm tra nhỏ, trước khi bạn đẩy lên. Test đầy đủ để job CI chạy. `--no-verify` không được coi là cách hợp lệ để đưa code vào `main`.

## Lỗi thường gặp

**Sửa file trong `.git/hooks` rồi tưởng cả team có cùng rule.** Thư mục đó không được push lên remote, nên đồng nghiệp clone về sẽ không có hook. Dùng `core.hooksPath`.

**Hook gọi `npm test` chạy mất cả phút.** Commit nào cũng phải chờ thì người ta sẽ tắt hook bằng `--no-verify`. Hook local chỉ nên làm việc nhẹ, xong trong vài giây. Test đầy đủ để cho job CI.

## Nguồn

1. [KodeKloud Engineer — 100 Days of DevOps](https://engineer.kodekloud.com/practice) — đề cương ngày Git hook. Trang practice cần đăng nhập. Bài này viết lại lệnh, không chép lab.
2. [githooks](https://git-scm.com/docs/githooks) — thời điểm từng hook chạy và exit code.
3. [core.hooksPath](https://git-scm.com/docs/git-config#Documentation/git-config.txt-corehooksPath) — đổi thư mục hook khỏi `.git/hooks`.
4. [git-init](https://git-scm.com/docs/git-init) — file hook mẫu đuôi `.sample`.

## Tiếp theo

[GitLab](./07-gitlab.md)
