Git 실전 · 규칙을 훅에 걸었는데 한 낱말로 뚫렸다 · 이론
규칙을 즉시 알려 주는 훅과 끝내 막는 훅은 다른 자리에 있다
한 줄 요약
클라이언트 훅(commit-msg·pre-commit·pre-push)은 빠른 피드백을 주고,
서버 훅과 CI 는 강제를 합니다. 클라이언트 훅은 --no-verify 한 낱말로 뚫리고
clone 으로 따라오지도 않으므로, 강제력을 기대하고 여기에 규칙을 걸면 안 됩니다.
왜 이게 필요했나
팀에 규칙이 생깁니다. 커밋 제목은 정해진 형식으로, 100KB 넘는 파일은 커밋 금지,main 에는 직접 push 금지. 문서에 적어 두면 지켜지지 않고, 리뷰에서 지적하면 이미
커밋이 쌓인 뒤라 고치기 번거롭습니다.
.git/hooks/ 에 스크립트를 두면 git 이 특정 순간에 그것을 실행합니다. 커밋 직전,
메시지를 쓴 직후, push 직전 같은 자리입니다. 종료코드가 0이 아니면 그 동작이 멈춥니다.
그런데 .git/hooks/ 에는 큰 문제가 하나 있습니다. **.git 디렉터리는 clone 으로
따라오지 않습니다.** 내가 아무리 잘 만들어 두어도 다른 사람 기계에는 없습니다.
그래서 팀마다 "설치 스크립트를 돌리세요" 같은 안내를 붙였고, 아무도 돌리지 않았습니다.
어떻게 동작하나
git 2.9 부터 core.hooksPath 설정이 생겼습니다. 훅을 찾는 자리를 바꾸는 설정입니다.
git config core.hooksPath .githooks이제 .githooks/ 는 저장소가 추적하는 평범한 디렉터리이므로 **훅 스크립트가 커밋되고
공유됩니다.** 다만 core.hooksPath 설정 자체는 .git/config 에 있어서 여전히 각자
한 번은 켜야 합니다. 그 한 줄을 README 나 부트스트랩 스크립트에 넣어 두는 것이
지금의 관례입니다.
자주 쓰는 훅 셋은 이렇습니다.
| 훅 | 언제 | 받는 것 | 막으면 |
| --- | --- | --- | --- |
| pre-commit | 커밋이 만들어지기 직전 | 없음(인덱스를 직접 본다) | 커밋이 안 된다 |
| commit-msg | 메시지를 다 쓴 뒤 | 메시지 파일 경로($1) | 커밋이 안 된다 |
| pre-push | push 직전 | 원격 이름·주소($1,$2) + 표준 입력으로 참조 목록 | push 가 안 된다 |
pre-commit 에는 함정이 하나 있습니다. 작업 트리가 아니라 인덱스를 보아야 합니다.
커밋되는 것은 인덱스에 올라간 내용이고, 작업 트리는 그 뒤에 얼마든지 달라질 수
있습니다. 파일에 비밀을 써서 git add 한 다음 파일을 깨끗하게 고쳐 놓으면, 작업
트리를 읽는 훅은 아무것도 못 잡고 비밀은 그대로 커밋됩니다. 인덱스의 내용은git show :<경로> 나 git cat-file -p <blob> 로 읽습니다.
pre-push 는 표준 입력으로 줄마다 로컬참조 로컬해시 원격참조 원격해시 를 받습니다.원격참조 가 refs/heads/main 인 줄이 있으면 막는 식으로 씁니다.
클라이언트 훅으로 막히지 않는 것
세 가지가 있습니다.
첫째, --no-verify. git commit --no-verify 는 pre-commit 과 commit-msg 를,git push --no-verify 는 pre-push 를 건너뜁니다. 이것은 결함이 아니라 설계입니다 —
클라이언트 훅은 내 기계에서 내가 켜는 도구이고, 급할 때 끌 수 있어야 합니다.
둘째, 설정을 안 켠 사람. core.hooksPath 는 각자 켜야 합니다.
셋째, 다른 경로로 들어오는 커밋. 웹 화면에서 직접 고치거나, 다른 도구가 밀어
넣거나, 훅을 지원하지 않는 클라이언트를 쓰면 애초에 실행되지 않습니다.
그래서 규칙을 실제로 강제하는 자리는 서버입니다. 서버 쪽 pre-receive·update
훅이나 호스팅 서비스의 브랜치 보호, 그리고 CI 검사입니다. 그 자리에는 --no-verify
같은 것이 없습니다. 대신 피드백이 느립니다 — 이미 커밋을 쌓고 push 한 뒤에야 거절을
받습니다.
정리하면 같은 규칙을 두 자리에 겁니다. **클라이언트 훅은 30초 만에 알려 주는 쪽이고,
서버는 끝내 막는 쪽입니다.** 클라이언트에만 걸면 뚫리고, 서버에만 걸면 사람들이
매번 push 하고 나서야 알게 됩니다.
현장에서 만나는 모습
- 비밀 스캐너를
pre-commit에 걸어 두면 대부분의 사고를 커밋 전에 잡습니다. commit-msg로 제목 형식을 맞춰 두면 릴리스 노트를 이력에서 자동으로 만들 수 있습니다.- 훅이 느리면 사람들이
--no-verify를 습관으로 씁니다. 커밋마다 도는 검사는 - 훅을 저장소에 담을 때는 실행 권한(
chmod +x)을 꼭 확인합니다. 권한이 없으면
그래도 이력 청소 절차는 따로 준비해 둡니다 — 뚫리는 경로가 남아 있기 때문입니다.
변경된 파일만 보게 만들어 1초 안에 끝내야 합니다.
git 은 조용히 건너뜁니다 — 아무 오류 없이 규칙만 사라집니다.
다음 실습에서 할 것
core.hooksPath 로 훅을 저장소에 담고, 제목 규칙과 큰 파일·비밀 차단을 직접 만듭니다.
작업 트리만 보는 훅이 왜 뚫리는지를 인덱스에만 비밀이 있는 상태로 재현하고,--no-verify 한 낱말로 세 훅을 모두 지나가 봅니다. clone 을 떠서 훅 파일은 따라오는데
설정은 안 따라온다는 것을 확인하고, pre-push 로 main 직접 push 를 막아 봅니다.
공식 문서는 [githooks](https://git-scm.com/docs/githooks),
[git-config](https://git-scm.com/docs/git-config),
[Pro Git 8.3 Git Hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) 입니다.