커밋 메시지 컨벤션: 좋은 커밋 메시지의 조건
Conventional Commits 규격의 type·scope·subject·body·footer 구조와 팀 히스토리를 읽기 쉽게 만드는 원칙을 정리한다.
지난 글에서 커밋 객체의 구조를 뜯어봤다. 이번에는 그 객체의 핵심 데이터, 커밋 메시지에 집중한다. 메시지를 어떻게 쓰느냐에 따라 6개월 후 git log가 읽을 수 있는 문장이 될 수도 있고, 해석 불가능한 암호가 될 수도 있다.
왜 커밋 메시지가 중요한가
버그를 추적하다 보면 문제 코드가 언제 왜 들어왔는지 git log로 확인하는 경우가 많다. 이때 커밋 메시지가 “fix bug”라면 아무 정보도 얻을 수 없다. 반면 “fix(auth): resolve token expiry causing infinite refresh loop”라면 원인을 즉시 파악할 수 있다.
코드는 무엇을 하는지 스스로 설명하지만, 왜 그렇게 바꿨는지는 커밋 메시지만이 설명할 수 있다.
Conventional Commits 규격
현재 업계에서 가장 널리 쓰이는 커밋 메시지 규격이다. conventionalcommits.org에서 공식 사양을 확인할 수 있다.
type(scope): subject
body
footer
이 세 영역이 각각 한 단락이며, 영역 사이에 반드시 빈 줄이 들어간다.
subject: 50자, 명령형
subject(첫 번째 줄)는 50자 이하로 쓰는 것이 권장된다. GitHub 같은 플랫폼은 72자까지는 표시하지만, 50자를 넘으면 목록에서 잘려 보이는 경우가 많다.
동사는 명령형(imperative mood)을 사용한다.
# 올바른 형태
add user authentication
fix null pointer in parser
remove deprecated endpoint
# 잘못된 형태
added user authentication
fixing null pointer
I removed the deprecated endpoint
“이 커밋은 ___한다(This commit ___s)“는 문장에 자연스럽게 들어맞아야 한다.
type: 변경의 성격을 분류
feat과 fix가 가장 빈번하게 사용된다. 그 외:
docs: 코드 없이 문서만 수정style: 들여쓰기, 세미콜론 등 의미 없는 형식 변경refactor: 기능 변경 없이 내부 구조를 개선perf: 성능 개선 (동작은 그대로)test: 테스트 추가 또는 수정chore: 빌드 시스템, 패키지 설정 등ci: CI 파이프라인 변경revert: 이전 커밋을 되돌릴 때
scope: 영향 범위 명시 (선택)
scope는 어떤 모듈이나 파일이 바뀌었는지를 괄호 안에 표시한다.
feat(auth): add OAuth2 login
fix(api): handle empty response body
chore(deps): bump eslint to 9.0
scope를 사용할지 여부와 어떤 단어를 쓸지는 팀에서 사전에 정한다. 일관성이 핵심이다.
body: WHY를 설명
body는 의무가 아니다. 그러나 왜 이 변경이 필요했는지 설명이 필요한 경우에는 반드시 써야 한다. 뭘 했는지(코드를 보면 알 수 있다)가 아니라 왜를 설명한다.
git commit -m "refactor(db): extract connection pool to singleton
Previous implementation created a new connection per request,
leading to exhaustion under heavy load (> 500 rps).
Singleton pool reduces connection count from ~500 to ~20."
footer: 이슈 연결과 BREAKING CHANGE
feat(api)!: remove v1 endpoints
BREAKING CHANGE: /api/v1/* endpoints removed.
Migrate to /api/v2/. See docs/migration-v2.md.
Closes #412
Co-authored-by: Alice <alice@example.com>
type!: 또는 footer의 BREAKING CHANGE: 중 하나라도 있으면 이 커밋은 semver의 major 버전을 올려야 하는 변경으로 간주된다. semantic-release 같은 도구가 이를 자동으로 감지해 릴리스를 생성한다.
좋은 메시지와 나쁜 메시지
실무 팁
멀티라인 메시지를 터미널에서 쓸 때:
git commit # 편집기가 열림
또는 -m을 두 번:
git commit -m "feat(login): add remember-me option" \
-m "Stores encrypted token in localStorage.
Expires after 30 days per security policy."
팀 전체에 컨벤션을 강제하려면 commitlint와 husky를 조합한다. 이 조합은 이후 글에서 별도로 다룬다.
지난 글: 커밋 객체 해부: 내부 구조를 손으로 뜯어보기
다음 글: git status 읽기: 저장소 상태를 한눈에 파악하기
읽어주셔서 감사합니다. 😊