커밋 메시지 검증
6개월 전에 작성한 코드의 커밋 히스토리를 봤더니 "수정", "고침", "ㅇㅇ", "asdf" 같은 메시지가 즐비합니다. 무엇을 왜 바꿨는지 전혀 알 수 없습니다. 오픈소스 프로젝트에서는 이런 커밋이 리뷰 단계에서 거절됩니다.
Conventional Commits는 커밋 메시지 형식을 표준화하는 규칙입니다. 많은 기업과 오픈소스 프로젝트에서 사용합니다. commit-msg Hook으로 이 형식을 강제하면 팀 전체의 히스토리 품질이 올라갑니다.
Conventional Commits 규칙
커밋 메시지는 다음 형식을 따릅니다.
<타입>[스코프 (선택)]: <설명>
[본문 (선택)]
[꼬리말 (선택)]
타입은 변경의 성격을 나타냅니다.
| 타입 | 의미 | 예시 |
|---|---|---|
feat |
새 기능 | feat: 로그인 기능 추가 |
fix |
버그 수정 | fix(auth): 토큰 만료 처리 오류 수정 |
docs |
문서 변경 | docs: README 설치 방법 업데이트 |
style |
코드 포맷팅 | style: 들여쓰기 통일 |
refactor |
리팩토링 | refactor: 사용자 조회 로직 분리 |
test |
테스트 추가/수정 | test: 결제 모듈 단위 테스트 추가 |
chore |
빌드, 패키지 변경 | chore: eslint 버전 업그레이드 |
ci |
CI 설정 변경 | ci: GitHub Actions 워크플로우 추가 |
perf |
성능 개선 | perf: 쿼리 인덱스 추가 |
revert |
커밋 되돌리기 | revert: feat: 로그인 기능 추가 |
commit-msg Hook
commit-msg Hook은 커밋 메시지가 담긴 임시 파일 경로를 첫 번째 인수로 받습니다.
# $1에 커밋 메시지 파일 경로가 담김COMMIT_MSG_FILE=$1COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
커밋 메시지를 읽고 형식을 검사한 뒤, 문제가 있으면 0이 아닌 값을 반환해 커밋을 취소합니다.
commit-msg Hook 스크립트
# 새 파일: .git/hooks/commit-msg#!/bin/bash# commit-msg: Conventional Commits 형식 검증COMMIT_MSG_FILE=$1COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")# 컬러 출력RED='\033[0;31m'GREEN='\033[0;32m'CYAN='\033[0;36m'NC='\033[0m'# ─── 머지/리베이스 커밋은 건너뜀 ──────────────────────────────────────────────# 머지 커밋 ("Merge branch ..." 형식)if echo "$COMMIT_MSG" | grep -qE "^Merge (branch|pull request|remote-tracking)"; then exit 0fi# 리베이스 중에는 검사 생략if [ -d "$(git rev-parse --git-dir)/rebase-merge" ] || \ [ -d "$(git rev-parse --git-dir)/rebase-apply" ]; then exit 0fi# ─── 형식 검증 ────────────────────────────────────────────────────────────────# 허용 타입 목록VALID_TYPES="feat|fix|docs|style|refactor|test|chore|ci|perf|revert|build"# Conventional Commits 패턴# 형식: <타입>[!][(<스코프>)]: <설명>PATTERN="^($VALID_TYPES)(\([a-zA-Z0-9_-]+\))?!?: .{1,72}$"# 첫 번째 줄만 검사FIRST_LINE=$(echo "$COMMIT_MSG" | head -1)if ! echo "$FIRST_LINE" | grep -qE "$PATTERN"; then echo "" echo -e "${RED}커밋 메시지 형식 오류${NC}" echo "─────────────────────────────────────────────────────────────────" echo " 입력된 메시지: $FIRST_LINE" echo "" echo -e "${CYAN}올바른 형식:${NC}" echo " <타입>[스코프]: <설명>" echo "" echo -e "${CYAN}허용 타입:${NC}" echo " feat — 새 기능" echo " fix — 버그 수정" echo " docs — 문서 변경" echo " style — 코드 포맷팅 (기능 변경 없음)" echo " refactor — 리팩토링" echo " test — 테스트 추가/수정" echo " chore — 빌드, 패키지 변경" echo " ci — CI 설정 변경" echo " perf — 성능 개선" echo " revert — 이전 커밋 되돌리기" echo "" echo -e "${CYAN}올바른 예시:${NC}" echo " feat: 로그인 기능 추가" echo " fix(auth): 토큰 만료 처리 오류 수정" echo " feat!: 기존 API와 호환 안 되는 변경 (! = breaking change)" echo "─────────────────────────────────────────────────────────────────" echo "" exit 1fi# ─── 추가 검사: 설명이 너무 짧지 않은지 ──────────────────────────────────────DESCRIPTION=$(echo "$FIRST_LINE" | sed 's/^[^:]*: //')DESC_LENGTH=${#DESCRIPTION}if [ "$DESC_LENGTH" -lt 5 ]; then echo "" echo -e "${RED}커밋 설명이 너무 짧습니다. (최소 5자)${NC}" echo " 입력된 설명: '$DESCRIPTION'" echo "" exit 1fi# ─── 검사 통과 ────────────────────────────────────────────────────────────────echo -e "${GREEN}커밋 메시지 형식이 올바릅니다.${NC}"exit 0
chmod +x .git/hooks/commit-msg
실습: 커밋 메시지 검증 테스트
# 실습 환경 준비mkdir ~/msg-test && cd ~/msg-testgit inittouch README.mdgit add README.md# 테스트 1: 잘못된 형식git commit -m "로그인 고침"
커밋 메시지 형식 오류
─────────────────────────────────────────────────────────────────
입력된 메시지: 로그인 고침
올바른 형식:
<타입>[스코프]: <설명>
허용 타입:
feat — 새 기능
fix — 버그 수정
...
올바른 예시:
feat: 로그인 기능 추가
fix(auth): 토큰 만료 처리 오류 수정
─────────────────────────────────────────────────────────────────
# 테스트 2: 올바른 형식git commit -m "feat: 초기 프로젝트 설정"
커밋 메시지 형식이 올바릅니다.
[main (root-commit) c3d4e5f] feat: 초기 프로젝트 설정
1 file changed, 0 insertions(+), 0 deletions(-)
# 테스트 3: 스코프 포함touch auth.pygit add auth.pygit commit -m "feat(auth): JWT 토큰 발급 기능 추가"
커밋 메시지 형식이 올바릅니다.
[main f4e5d6c] feat(auth): JWT 토큰 발급 기능 추가
# 테스트 4: Breaking change (!)git commit -m "feat!: v2 API 응답 형식 변경 (하위 호환 불가)"
커밋 메시지 형식이 올바릅니다.
[main g5f6e7d] feat!: v2 API 응답 형식 변경 (하위 호환 불가)
Conventional Commits의 장점
형식이 통일되면 도구를 활용할 수 있습니다.
# git log로 타입별 히스토리 조회git log --oneline | grep "^[a-f0-9]* feat:"
g5f6e7d feat!: v2 API 응답 형식 변경 (하위 호환 불가)
f4e5d6c feat(auth): JWT 토큰 발급 기능 추가
c3d4e5f feat: 초기 프로젝트 설정
conventional-changelog 같은 도구를 사용하면 커밋 히스토리에서 자동으로 CHANGELOG를 생성하고 버전을 결정할 수도 있습니다. feat이 있으면 마이너 버전 업, fix가 있으면 패치 버전 업, !(breaking change)가 있으면 메이저 버전 업이라는 규칙을 적용합니다.
커밋 메시지 템플릿 설정
자주 사용하는 형식을 템플릿으로 등록해두면 편리합니다.
# 템플릿 파일 생성cat > ~/.gitmessage << 'EOF'# <타입>[스코프]: <설명 — 72자 이내>## 본문 (선택): 변경한 이유와 방법## 꼬리말 (선택):# Closes #이슈번호# Breaking change: ...## 타입: feat fix docs style refactor test chore ci perf revertEOF# Git에 템플릿 등록git config --global commit.template ~/.gitmessage
이제 git commit 실행 시 편집기가 이 템플릿과 함께 열립니다.