Husky 사용법: Git Hook으로 커밋 메시지 규칙 자동화하기

백종환
∙∙조회 29
Husky 사용법: Git Hook으로 커밋 메시지 규칙 자동화하기

우리 팀의 커밋 메시지는 얼마나 통일되어 있을까?

Git Log를 열어보면 생각보다 다양한 메시지를 만날 때가 있다.

text
feat: 로그인 기능 추가
fix: 로그인 오류 수정
로그인 수정
버그 수정
작업중

팀에서 feat, fix, refactor 같은 규칙을 정해두더라도 결국 커밋하는 사람이 매번 기억해야 한다.

사람이 기억해야만 지켜지는 규칙이라면 언젠가는 빠질 수밖에 없다.

커밋 규칙을 잘 지켜달라고 말하는 대신, 커밋하는 순간 자동으로 검사하게 만들 수는 없을까?

이 문제를 해결할 때 사용할 수 있는 것이 Git Hook과 Husky다.


Git의 특정 순간에 끼어드는 Git Hook

Git Hook은 Git에서 특정 동작이 발생했을 때 원하는 스크립트를 실행할 수 있는 기능이다.

Hook실행 시점활용 예시
pre-commit커밋이 생성되기 전ESLint, Prettier
commit-msg커밋 메시지가 작성된 후커밋 메시지 검사
pre-pushPush 직전테스트, 타입 검사

Husky는 이런 Git Hook을 프로젝트 안에서 관리할 수 있도록 도와주는 도구다.

즉 Husky가 직접 코드를 검사하거나 커밋 메시지를 검사하는 것은 아니다.

Husky의 역할은 Git의 특정 시점에 원하는 명령어를 실행하는 것이다.


Husky를 프로젝트에 연결하기

먼저 Husky를 설치한다.

bash
npm install --save-dev husky

Husky 공식 문서에서 권장하는 초기화 방식은 husky init이다.

bash
npx husky init

초기화하면 .husky/pre-commit 파일이 생성되고 package.json에는 Husky 설정을 위한 prepare 스크립트가 추가된다.

대략 이런 구조가 된다.

text
project/
├── .husky/
│   └── pre-commit
├── package.json
└── ...

예를 들어 .husky/pre-commit에 다음 명령을 넣을 수 있다.

bash
npm run lint

이제 git commit을 실행하면 바로 커밋되는 것이 아니라 먼저 lint가 실행된다.

text
git commit
  ↓
pre-commit
  ↓
npm run lint
  ↓
Commit

여기까지가 Husky의 기본 역할이다.


커밋 메시지는 commitlint로 검사하기

처음 이야기했던 문제는 커밋 메시지의 통일성이었다.

Husky는 Hook을 실행할 뿐, 메시지가 올바른지는 판단하지 않는다. 이 역할은 commitlint에게 맡길 수 있다.

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

프로젝트 루트에 설정 파일을 만든다.

javascript
// commitlint.config.mjs
export default {
  extends: ['@commitlint/config-conventional'],
}

그리고 .husky/commit-msg에서 commitlint를 실행한다.

bash
npx --no -- commitlint --edit "$1"

이제 다음과 같은 메시지는 규칙을 통과한다.

text
feat: 로그인 기능 추가
fix: 로그인 오류 수정
refactor: 인증 로직 분리

반대로 이런 메시지는 커밋 단계에서 막을 수 있다.

text
로그인 수정
작업중
fix

결국 팀원이 컨벤션 문서를 매번 기억하는 대신, 잘못된 커밋 메시지가 만들어지는 순간 바로 피드백을 받게 된다.


변경한 파일만 검사하려면 lint-staged

커밋 메시지를 검사했다면 코드도 커밋 전에 검사할 수 있다.

가장 단순한 방법은 pre-commit에서 프로젝트 전체 lint를 실행하는 것이다.

하지만 파일 하나만 수정했는데 전체 프로젝트를 검사할 필요는 없다.

이때 lint-staged를 사용할 수 있다.

bash
npm install --save-dev lint-staged

package.json에 검사 대상을 지정한다.

json
{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": "eslint --fix",
    "*.{json,md,css}": "prettier --write"
  }
}

그리고 .husky/pre-commit에서는 lint-staged를 실행한다.

bash
npx lint-staged

그러면 이번 커밋에 포함되는 staged 파일만 검사한다.

text
git add
  ↓
git commit
  ↓
pre-commit
  ↓
lint-staged
  ↓
ESLint / Prettier

프로젝트 전체를 검사하는 것보다 커밋 흐름을 가볍게 유지할 수 있다.


각각의 역할을 구분해보면

Husky를 처음 사용하면 commitlint, lint-staged, ESLint의 역할이 섞여 보일 수 있다.

도구역할
HuskyGit Hook에서 명령 실행
commitlint커밋 메시지 규칙 검사
lint-stagedstaged 파일에 명령 실행
ESLint코드 규칙 검사
Prettier코드 포맷 정리

전체 흐름은 다음과 같다.

text
git commit
  |
  +-- pre-commit
  |     +-- lint-staged
  |           +-- ESLint
  |           +-- Prettier
  |
  +-- commit-msg
        +-- commitlint

Husky가 모든 것을 검사하는 것이 아니다.

Git과 각각의 검사 도구를 연결하는 역할이라고 이해하면 훨씬 쉽다.


모든 검사를 Hook에 넣을 필요는 없다

Husky를 사용한다고 해서 테스트, 빌드, 타입 검사까지 모두 pre-commit에 넣는 것이 좋은 것은 아니다.

커밋할 때마다 오래 기다려야 한다면 오히려 개발 흐름을 방해한다.

나는 다음 정도로 역할을 나누는 편이 좋다고 생각한다.

text
pre-commit
→ 변경 파일 Lint / Format

commit-msg
→ Commit Convention

CI
→ 전체 Lint / Type Check / Test / Build

로컬 Hook의 목적은 모든 것을 보장하는 것이 아니라 실수를 최대한 빠르게 발견하는 것에 가깝다.


규칙을 기억하는 것보다 자동화하기

팀에서 규칙을 만드는 것은 어렵지 않다.

어려운 것은 그 규칙을 모든 사람이 계속 기억하고 지키는 것이다.

Husky를 사용하면 “커밋 메시지를 규칙에 맞게 작성해주세요”라고 반복해서 말하는 대신, 커밋이 만들어지는 순간 규칙을 확인할 수 있다.

여기에 commitlint와 lint-staged를 연결하면 커밋 메시지뿐 아니라 실제 커밋될 코드도 같은 흐름에서 검사할 수 있다.

반복해서 지켜야 하는 규칙이라면, 사람에게 기억시키기 전에 자동화할 수 있는지 먼저 확인해보자.

지금 작업 중인 프로젝트의 git log를 한번 열어보자. 팀에서 정한 규칙과 실제 커밋 메시지가 얼마나 닮아 있는지 확인하는 것부터 시작하면 된다.


참고자료