19. Git 훅(Hooks) — 자동 검사 걸기

🎯 이 장의 목표
  • Git 훅이 무엇이고 언제 실행되는지 이해한다
  • 네이티브 훅(.git/hooks)으로 pre-commit을 만들어 본다
  • 훅이 push되지 않는다는 한계와 그 해결책을 안다
  • husky + lint-staged + commitlint로 실무 자동화를 구성한다

19.1 Git 훅이란?

Git 훅은 커밋·푸시 같은 특정 시점에 자동으로 실행되는 스크립트입니다. "이 시점에 이 검사를 꼭 거쳐라"라고 Git에 자동 검문소를 설치하는 것입니다.

예를 들면:

  • 커밋하기 직전 → 코드 스타일 검사(린트)·테스트를 돌려, 통과 못 하면 커밋 차단
  • 커밋 메시지 작성 시 → 메시지 형식이 규칙에 맞는지 검사
  • 푸시하기 직전 → 전체 테스트 실행
flowchart LR
    C["git commit"]
    H["🔍 pre-commit 훅<br/>린트 / 테스트"]
    OK["✅ 커밋 완료"]
    NO["❌ 커밋 거부"]
    C --> H
    H -- "통과" --> OK
    H -- "실패" --> NO
    classDef cmd fill:#dbeafe,stroke:#2563eb,color:#1e40af
    classDef hook fill:#fef3c7,stroke:#d97706,color:#92400e
    classDef ok fill:#dcfce7,stroke:#16a34a,color:#166534
    classDef no fill:#fee2e2,stroke:#dc2626,color:#991b1b
    class C cmd
    class H hook
    class OK ok
    class NO no
💡 팁
왜 필요한가? "커밋 전에 린트 돌리는 걸 자꾸 까먹어요" 같은 문제를 자동으로 해결합니다. 사람의 기억력에 의존하지 않고, 규칙을 기계가 강제합니다.

19.2 훅은 어디에 있나? — .git/hooks

모든 저장소에는 .git/hooks 폴더가 있고, 샘플 훅들이 들어있습니다.

BASH
ls .git/hooks
CODE
applypatch-msg.sample      pre-commit.sample
commit-msg.sample          pre-push.sample
post-update.sample         pre-rebase.sample
pre-applypatch.sample      prepare-commit-msg.sample
...

.sample 확장자가 붙어 있으면 비활성입니다. 확장자를 떼면 활성화됩니다.

주요 훅 종류

훅 이름실행 시점용도
pre-commit커밋 직전린트, 테스트, 포맷 검사
prepare-commit-msg메시지 편집기 열리기 전메시지 템플릿 자동 삽입
commit-msg메시지 작성 후메시지 형식 검증
post-commit커밋 완료 후알림 등
pre-push푸시 직전전체 테스트
pre-rebase리베이스 직전특정 브랜치 리베이스 금지
💡 팁
훅 이름은 client-side(로컬)server-side(서버)로 나뉘는데, 입문 단계에서는 로컬 훅(pre-commit, commit-msg 등)만 알면 충분합니다.

19.3 네이티브 pre-commit 훅 만들어보기

직접 간단한 pre-commit 훅을 만들어봅시다.

BASH
# .git/hooks/pre-commit 파일 생성
touch .git/hooks/pre-commit

내용 작성 (pre-commit 파일):

BASH
#!/bin/sh
echo "🔍 커밋 전 검사 실행 중..."

# 예: 'console.log'가 있으면 커밋 거부
if git diff --cached | grep -q "console.log"; then
  echo "❌ console.log 가 포함되어 있습니다. 제거 후 커밋하세요."
  exit 1            # 0이 아닌 값을 반환하면 커밋이 중단됨
fi

echo "✅ 검사 통과!"
exit 0

실행 권한을 줍니다 (안 주면 동작 안 함).

BASH
chmod +x .git/hooks/pre-commit

이제 console.log가 든 변경을 커밋하려 하면 거부됩니다.

📌 핵심
핵심 규칙: 훅 스크립트가 exit 0이면 통과, 0이 아니면 작업 중단입니다. 이것으로 "검사 실패 시 커밋 차단"을 구현합니다.

정규표현식으로 작성자 이메일 검증 (예)

회사 이메일로만 커밋하도록 강제하는 pre-commit 훅 예시:

💡 팁
정규표현식(Regular Expression, 줄여서 정규식/regex)은 "문자열의 패턴"을 기호로 표현하는 작은 언어입니다. 예를 들어 "@mycompany.com으로 끝나는가"처럼 글자 하나하나가 아니라 규칙으로 검사할 수 있게 해줍니다. 아래 @mycompany\.com$에서 $는 "문자열의 끝", \.은 "마침표 글자 그대로"를 뜻합니다.
BASH
#!/bin/sh
EMAIL=$(git config user.email)
if ! echo "$EMAIL" | grep -qE "@mycompany\.com$"; then
  echo "❌ 회사 이메일(@mycompany.com)로만 커밋할 수 있습니다."
  exit 1
fi

19.4 네이티브 훅의 치명적 한계

⚠️ 흔한 실수
로컬 훅은 원격에 push되지 않습니다!

.git/hooks.git 폴더 안에 있고, .git은 절대 push되지 않습니다(14장). 즉 내가 만든 훅은 내 컴퓨터에만 있고, 팀원들에게는 전달되지 않습니다.

CODE
나:    .git/hooks/pre-commit  ✅ 있음
동료:  .git/hooks/pre-commit  ❌ 없음 (push 안 되니까)

팀 전체가 같은 훅을 쓰게 하려면 다른 방법이 필요합니다. 여기서 husky 같은 도구가 등장합니다.

19.5 husky — 팀과 공유되는 훅

husky는 훅 스크립트를 프로젝트 폴더(.husky/)에 두고 버전 관리에 포함시켜, npm install 한 번이면 팀원 모두에게 훅이 설치되게 해주는 도구입니다. Node.js 프로젝트에서 표준처럼 쓰입니다.

📌 핵심
이 책 기준 husky 최신 버전은 v9입니다. (v4 등 옛 버전은 설정 방식이 달라 문서를 꼭 확인하세요.)

설정 절차

BASH
# 1. Node.js 프로젝트 준비
npm init -y

# 2. husky 설치 (개발 의존성)
npm install --save-dev husky

# 3. husky 초기화 (.husky/ 폴더 + prepare 스크립트 생성)
npx husky init

husky init.husky/ 폴더를 만들고, package.json에 다음을 추가합니다.

JSON
{
  "scripts": {
    "prepare": "husky"
  }
}
💡 팁
preparenpm install자동 실행되는 npm 스크립트입니다. 그래서 팀원이 프로젝트를 받아 npm install만 하면 훅이 자동 설치됩니다. 이것이 네이티브 훅과의 결정적 차이입니다.

pre-commit 훅 추가

.husky/pre-commit 파일을 만들고 실행할 명령을 적습니다.

BASH
# .husky/pre-commit
npm test

이제 .husky/pre-commit은 일반 파일이라 Git에 커밋되어 팀과 공유됩니다. 누구나 커밋 시 자동으로 테스트가 돌아갑니다.

19.6 실전 구성: 테스트 + 린트 자동화

📌 핵심
이 절에 나오는 도구들 (모두 JavaScript 생태계의 대표 도구)
  • Jest: 코드가 의도대로 동작하는지 자동으로 확인하는 테스트 도구.
  • ESLint: 코드의 문법·스타일 문제를 잡아내는 린터(앞서 설명한 린트 도구).
  • Prettier: 코드의 들여쓰기·줄바꿈 등 서식을 자동으로 정리해주는 포매터.

다른 언어에도 비슷한 역할의 도구가 있으며, 훅에 거는 방식은 동일합니다.

테스트 도구(Jest) 준비

BASH
npm install --save-dev jest

package.json:

JSON
{
  "scripts": {
    "test": "jest",
    "lint": "eslint ."
  }
}

간단한 함수와 테스트 예:

JAVASCRIPT
// sum.js
function sum(a, b) { return a + b; }
module.exports = sum;

// sum.test.js
const sum = require('./sum');
test('1 + 2 = 3', () => {
  expect(sum(1, 2)).toBe(3);
});

.husky/pre-commit:

BASH
npm run lint
npm test

이제 커밋할 때마다 린트와 테스트가 자동으로 돌고, 실패하면 커밋이 막힙니다.

19.7 lint-staged — 바뀐 파일만 검사하기

프로젝트가 커지면 매 커밋마다 모든 파일을 린트하는 건 느립니다. lint-staged스테이지에 올라간(변경된) 파일만 검사해 속도를 크게 높입니다.

BASH
npm install --save-dev lint-staged

package.json에 설정 추가:

JSON
{
  "lint-staged": {
    "*.js": ["eslint --fix", "prettier --write"],
    "*.{json,md}": ["prettier --write"]
  }
}

.husky/pre-commit을 lint-staged 호출로 변경:

BASH
npx lint-staged

이제 커밋 시 방금 수정한 .js 파일만 ESLint·Prettier가 검사·자동수정합니다. 전체 검사보다 훨씬 빠릅니다.

CODE
git commit  ─►  lint-staged  ─►  스테이지된 .js만 eslint --fix
                                  통과하면 커밋, 아니면 차단

19.8 commitlint — 커밋 메시지 규칙 강제

팀의 커밋 메시지를 일관된 형식(예: Conventional Commits)으로 강제하려면 commitlintcommit-msg 훅에 연결합니다.

BASH
npm install --save-dev @commitlint/cli @commitlint/config-conventional

설정 파일 commitlint.config.js:

JAVASCRIPT
module.exports = { extends: ['@commitlint/config-conventional'] };

.husky/commit-msg 훅:

BASH
npx --no-install commitlint --edit "$1"

이제 커밋 메시지는 다음 형식을 따라야 합니다.

CODE
feat: add login button       ✅ (type: subject 형식)
fix: resolve cart crash      ✅
update stuff                 ❌ (type 없음 → 거부)
type의미
feat새 기능
fix버그 수정
docs문서
refactor리팩터링
test테스트
chore잡무(빌드/설정 등)

19.9 훅 건너뛰기 — --no-verify

급할 때 훅을 일시적으로 건너뛰려면:

BASH
git commit -m "WIP" --no-verify     # pre-commit, commit-msg 훅 생략
git push --no-verify                # pre-push 훅 생략
⚠️ 흔한 실수
--no-verify는 비상용입니다. 남용하면 훅을 설치한 의미가 없어집니다. 정말 필요한 경우에만 쓰세요. (husky는 HUSKY=0 git commit ...으로 전체 비활성화도 가능합니다.)

19.10 정리: 네이티브 훅 vs husky

네이티브 훅husky
위치.git/hooks/.husky/ (프로젝트 폴더)
공유❌ push 안 됨✅ Git에 커밋되어 공유
설치수동npm install 시 자동
적합개인용 빠른 검사팀 협업 표준
💡 팁
개념을 배울 땐 네이티브 훅으로 원리를 이해하고, 실무 팀 프로젝트에선 husky + lint-staged + commitlint 조합이 사실상 표준입니다. (참고로 Go로 만든 더 빠른 대안인 lefthook도 최근 인기입니다.)

19.11 이 장에서 배운 것 (요약)

  • Git 훅 = 특정 시점에 자동 실행되는 스크립트 (검문소)
  • 네이티브 훅은 .git/hooks/에 두고 실행권한 부여, exit 0=통과 / 비0=중단
  • 핵심 한계: 로컬 훅은 push되지 않아 팀과 공유 불가
  • husky(v9)는 .husky/에 훅을 두고 npm install로 자동 설치 → 팀 공유 해결
  • lint-staged로 변경된 파일만 빠르게 검사
  • commitlint로 커밋 메시지 형식 강제
  • 비상시 --no-verify로 건너뛰기 (남용 금지)

✍️ 확인 문제

  1. 네이티브 훅(.git/hooks)을 팀원과 공유할 수 없는 이유는?
  2. 커밋 시 변경된 파일만 린트하려면 어떤 도구를 쓰나요?
  3. 훅 스크립트가 커밋을 중단시키려면 무엇을 반환해야 하나요?
다음 장에서는 코드를 무료로 웹에 배포하는 GitHub Pages를 배웁니다. → 20_GitHubPages.md