iBetter Books
수정

커밋 메시지 검증

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 실행 시 편집기가 이 템플릿과 함께 열립니다.