프로젝트마다 커밋 컨벤션을 정해두고도, 프로젝트가 끝나고 커밋 로그를 보면 항상 띄어쓰기나 기호 같은 작은 디테일이 무너져 있는 사람이 있다. 그게 바로 나다.
이를 고치기 위해 팀 노션을 항상 열어두고 커밋을 작성했지만, 솔직히 너무 번거로웠다.
누가 강제로 커밋 좀 체크해 줬으면 좋겠다!
그래서 개인 프로젝트지만 일관적으로 커밋하는 습관을 갖기 위해서 깃허스키를 사용해 커밋 메세지를 컨벤션을 강제하고자 한다.
왜 나는 깃 허스키를 선택했는가?
커밋 직전, 커밋 메시지 작성 직후, 푸시 직전 등 여러 시점에서 자동으로 검증 로직을 실행할 수 있기 때문이다.
개인 프로젝트이지만 일관된 기준을 강제로 지키는 환경을 만들어보고 싶었다!
npm install --save-dev husky
npx husky install
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
node .husky/validate-commit.mjs $1

한 줄씩 알아보기
#!/bin/sh
. "$(dirname "$0")/_/husky.sh”
node .husky/validate-commit.mjs $1
validate-commit.mjs를 실행해서 커밋 메시지가 규칙에 맞는지 검사한다.
$1 은 커밋 메시지가 저장된 파일 경로를 뜻한다.
Git은 커밋 메시지를 작성할 때 임시로 .git/COMMIT_EDITMSG 파일에 저장한다
따라서 깃은 커밋 메세지를 .git/COMMIT_EDITMSG 파일에 기록하고, commit-msg 훅 실행 시 해당 파일의 경로를 첫 번째 인자로 전달한다.
// 터미널에 입력해야된다
chmod +x .husky/commit-msg
Git은 훅(hook) 파일이 “실행 가능한 파일(executable)”일 때만 자동으로 실행해준다.
만약 실행 권한이 없으면 이 파일은 그냥 텍스트 파일이라서 아무것도 실행되지 않는다.
따라서 실행 권한을 부여해야 한다.
./validate-commit.mjs 파일 생성import fs from 'fs'
const msgFile = process.argv[2]
const msg = fs.readFileSync(msgFile, 'utf-8').trim()
const commitRegex = /^(feat|fix|docs|style|refactor|test|chore)(\(#\d+\)): .+$/
if (!commitRegex.test(msg)) {
console.error(`
❌ 커밋 메시지 규칙 위반!
형식:
작업타입(#이슈번호): 작업 내용
예시:
feat(#1): 로그인 로직 추가
fix(#2): API 오류 수정
`)
process.exit(1) // 실패 → 커밋 중단
}
한 줄씩 알아보기
import fs from 'fs'fs 는 Node.js 기본 모듈 "File System"로, 파일 읽기, 쓰기, 삭제가 가능하다.const msgFile = procress.argv[2]process.argv[0] → node 실행 경로 ⇒ (/usr/local/bin/node)
process.argv[1] → 실행 중인 스크립트 파일 경로 ⇒ (scripts/validate-commit.mjs)
process.argv[2] → git이 전달한 커밋 메세지 파일 경로 ⇒ (.git/COMMIT_EDITMSG)
→ 이미 commit-msg에서 node .husky/validate-commit.mjs $1 로 스크립트 파일 경로를 넘겼기 때문에 node.js로 넘어오면 argv[2]가 된다.
process , __dirname , __filename , Buffer , globalif (!commitRegex.test(msg)) 에서 test를 import 안 했는데 어떻게 바로 사용가능하지?commitRegex 가 RegExp 객체이다.RegExp객체란?자바스크립트에서 정규 표현식(Regular Expression)을 다루는 객체이다. 커밋 메세지가 컨벤션에 맞는지 확인하는 것처럼 문자열에서 특정 패턴을 찾거나 검증할 때 사용한다.
RegExp 는 new 생성자를 사용하지 않고 /.../ ← 리터럴 방식으로 생성이 가능하다.
따라서 commitRegex 는 리터럴 방식으로 변수값을 할당했기 때문에 RegExp 객체이다.
RegExp 에는 test 뿐만 아니라 다양한 메서드가 있으나 주로 test , exec() 를 사용한다.
test() → true/false → 간단한 패턴 검증 시 주로 사용exec() → 매치된 내용 배열 반환, 매치된 내용이 없으면 null 반환 → 더 상세한 정보 필요할 때 사용커밋 메세지 컨벤션을 “ 작업타입(#이슈번호): 작업내용”으로 설정한 이유
우선 깃허브에서 이슈번호를 커밋에 넣으면 추적이 가능하기 때문에 추후 코드 리뷰 등 유지 보수 하기에 적합할 것이라고 생각했다.
가장 큰 이유는 개인 프로젝트라도 “작업타입”을 명시하면서 내가 작업한 부분을 분류하고 일관된 커밋 습관을 형성하기 위해서이다.

규칙을 지키지 않으면 커밋 단계에서 에러가 발생하며, 커밋은 중단된다!
직접 스크립트를 만드는 방법도 있지만 라이브러리를 사용할 수 있다!
@commitlint/cli : 커밋 메시지 체크@commitlint/config-conventional : 기본 컨벤션 규칙https://www.notion.so/husky-233ec690dcc680c2a8b7de3ee1cb142a