본문 바로가기
TYLER SONGBlog
← Blog index
CS학습5 min read

Conventional Commits: 커밋 메시지를 기계가 읽을 수 있는 규칙으로 만드는 약속

Conventional Commits는 `type(scope): description` 형태로 커밋 메시지를 작성하도록 정한 규약이다. 변경 의도를 일관된 형식으로 남기면 이력 파악, 변경 로그(CHANGELOG) 자동 생성, 유의적 버전(SemVer) 결정을 자동화할 수 있다. 이 글에서는 형식과 규칙, 검증 스크립트, 실무 도구 연계 방법을 다룬다.

By 송민성

1. 개념

Conventional Commits는 커밋 메시지의 구조를 정의한 경량 규약이다. 기본 형식은 다음과 같다.

text
<type>[optional scope][!]: <description>

[optional body]

[optional footer(s)]
  • type: 변경의 종류. feat(기능 추가)와 fix(버그 수정)는 규약이 명시한 필수 타입이며, 그 외 docs, refactor, test, chore, perf, build, ci, style 등은 Angular 컨벤션에서 널리 쓰이는 관례다.
  • scope: 변경이 일어난 영역. 괄호 안에 적는다. 예: feat(auth): ...
  • description: 변경 내용을 한 줄로 요약한 문장.
  • body: 변경 배경과 상세 설명. 헤더와 빈 줄로 구분한다.
  • footer: BREAKING CHANGE: ..., Refs: #123 같은 부가 정보.
  • `!`: 타입 또는 스코프 뒤에 붙여 호환성을 깨는 변경(breaking change)임을 표시한다.

2. 왜 사용하는가

  • 이력 가독성: git log --oneline만 봐도 변경의 성격이 구분된다.
  • 자동화: 도구가 메시지를 파싱해 CHANGELOG를 만들고 버전을 올린다.
  • 유의적 버전(Semantic Versioning, SemVer)과의 대응: 커밋 타입이 버전 증가 규칙과 직접 연결된다.
  • 리뷰와 협업 효율: 팀원이 변경 의도를 빠르게 파악하고, 릴리스 노트를 수작업으로 쓰지 않아도 된다.

3. 동작 원리

Conventional Commits는 문법 규칙일 뿐이며, 실제 동작은 메시지를 파싱하는 도구가 담당한다. 도구는 헤더의 type과 footer를 읽어 다음 규칙으로 버전을 결정한다.

커밋 내용SemVer 증가
fix:PATCH (1.0.0 → 1.0.1)
feat:MINOR (1.0.0 → 1.1.0)
! 또는 BREAKING CHANGE: footerMAJOR (1.0.0 → 2.0.0)

그 외 타입(docs, chore 등)은 규약상 버전에 직접 영향을 주지 않는다. 처리 흐름은 다음과 같다.

  1. 개발자가 규약에 맞춰 커밋한다.
  2. 훅(hook)이나 CI가 형식을 검증한다.
  3. 릴리스 도구가 마지막 태그 이후의 커밋을 읽어 다음 버전을 계산한다.
  4. 타입별로 묶어 CHANGELOG를 생성한다.

4. 코드 예제

4-1. 커밋 메시지 예시

shell
# 기능 추가 git commit -m "feat(cart): 장바구니 수량 변경 API 추가" # 버그 수정 + 이슈 연결 git commit -m "fix(auth): 토큰 만료 시 무한 리다이렉트 수정" -m "Refs: #142" # 호환성을 깨는 변경 (! 사용) git commit -m "feat(api)!: 응답 필드 userName을 username으로 변경" # 호환성을 깨는 변경 (footer 사용) git commit -m "refactor(api): 응답 구조 정리" -m "BREAKING CHANGE: data 필드가 items로 변경되었습니다."

4-2. 형식 검증 스크립트 (TypeScript)

commit-msg 훅에서 사용할 수 있는 간단한 검증기다. Node.js 환경에서 실행한다.

typescript
import { readFileSync } from "node:fs"; const HEADER_PATTERN = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([\w\-./]+\))?!?: .+$/; export function validateHeader(message: string): boolean { const header = message.split("\n")[0]; return HEADER_PATTERN.test(header); } const samples = [ "feat(cart): 수량 변경 API 추가", "fix: 로그인 오류 수정", "feat(api)!: 응답 필드 변경", "버그 고침", "Feat: 대문자 타입", ]; for (const s of samples) { console.log(validateHeader(s) ? "OK " : "FAIL", s); } // 훅으로 쓸 때: 첫 번째 인자로 커밋 메시지 파일 경로를 받는다. if (process.argv[2]) { const msg = readFileSync(process.argv[2], "utf-8"); if (!validateHeader(msg)) { console.error("커밋 메시지가 Conventional Commits 형식이 아닙니다."); process.exit(1); } }

실행 결과(샘플 부분)는 앞의 세 개가 OK, 뒤의 두 개가 FAIL이다.

4-3. commitlint 설정

shell
npm install --save-dev @commitlint/cli @commitlint/config-conventional echo "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js # 직접 검사해 보기 echo "feat: 로그인 추가" | npx commitlint

5. 시간 복잡도 또는 성능 특성

알고리즘적 성능이 문제되는 주제는 아니다. 실무에서 고려할 비용은 다음과 같다.

  • 헤더 검증은 한 줄에 대한 정규식 검사이므로 커밋당 부담이 사실상 없다.
  • 릴리스 도구는 마지막 태그 이후의 커밋 수에 비례해 메시지를 파싱한다. 저장소 규모가 커도 릴리스 구간 단위로 읽기 때문에 일반적으로 문제되지 않는다.
  • 진짜 비용은 팀의 학습과 습관 형성이다. 훅과 CI로 자동 검증하면 이 비용을 줄일 수 있다.

6. 실무 사용 사례

  • 자동 버전·릴리스: semantic-release, release-please 같은 도구가 커밋 이력으로 버전, 태그, CHANGELOG를 만든다.
  • PR 제목 검증: 스쿼시 머지(squash merge)를 쓰는 팀은 PR 제목이 최종 커밋 메시지가 되므로, CI에서 PR 제목을 검사한다.
  • 모노레포 scope 활용: feat(web), fix(server)처럼 패키지명을 scope로 써서 영향 범위를 표시한다.
  • 훅 연동: husky로 commit-msg 훅에 commitlint를 연결해 잘못된 메시지를 커밋 시점에 막는다.

7. 주의할 점

  • 타입은 소문자로 쓴다. 규약은 대소문자 구분을 권장하지 않지만 도구 설정에 따라 Feat가 거부될 수 있어 소문자로 통일하는 편이 안전하다.
  • 콜론 뒤에 공백이 필요하다. feat:추가는 형식 위반이다.
  • 하나의 커밋에는 하나의 의도만 담는다. feat와 fix가 섞이면 버전 계산과 CHANGELOG가 부정확해진다.
  • breaking change를 빠뜨리지 않는다. !나 footer가 없으면 MAJOR 버전이 올라가지 않는다.
  • 타입 목록은 팀 합의 사항이다. feat, fix 외 타입은 규약 필수가 아니므로 팀 문서에 허용 목록을 명시한다.
  • description은 변경 내용을 설명한다. fix: 수정처럼 내용 없는 메시지는 형식만 맞고 가치가 없다.

8. 핵심 정리

  • 형식은 type(scope)!: description + 선택적 body/footer다.
  • fix는 PATCH, feat는 MINOR, breaking change는 MAJOR에 대응한다.
  • 규약 자체는 문법이고, 가치는 commitlint, 훅, CI, 릴리스 도구와 결합할 때 나온다.
  • 한 커밋에 한 의도를 담고, breaking change는 반드시 ! 또는 footer로 표시한다.
  • 스쿼시 머지를 쓴다면 PR 제목을 검증 대상으로 삼는다.