Git 커밋 메시지 잘 쓰는 법과 실전 예제

버그가 하나 터져서 git blame으로 범인을 찾았습니다. 해당 줄을 마지막으로 건드린 커밋이 나왔는데, 메시지가 “수정”입니다. 그 앞 커밋은 “ㅇㅇ”, 그 앞은 “asdf”. 결국 diff를 한 줄씩 읽어가며 6개월 전의 내가 무슨 생각이었는지 추측하게 됩니다. git 커밋 컨벤션은 이 추측 시간을 없애기 위한 약속입니다. 이 글에서는 좋은 커밋 메시지의 기준, 실무에서 가장 많이 쓰는 Conventional Commits 형식, 그리고 템플릿과 훅으로 형식을 자동으로 강제하는 방법까지 다룹니다.

커밋 메시지는 “무엇”이 아니라 “왜”를 적는 곳

가장 흔한 실수는 코드를 요약해서 적는 것입니다. “UserService.java 수정”, “로그인 함수 변경” 같은 메시지가 여기 해당합니다. 그런데 무엇을 바꿨는지는 이미 diff에 전부 들어 있습니다. diff가 절대 알려주지 않는 건 왜 바꿨는가입니다.

세 달 뒤에 그 커밋을 열어보는 사람(대부분 본인입니다)이 궁금한 건 “이 조건문 왜 넣었지? 지워도 되나?”입니다. 커밋 메시지에 “결제 취소 요청이 중복으로 들어오는 케이스가 있어서 방어” 한 줄만 있었다면 5분이면 끝날 일이, 없으면 반나절 코드 추적으로 늘어납니다. 커밋 하나당 5분씩만 아껴도 하루 열 커밋이면 한 달에 열여덟 시간입니다.

Conventional Commits: 사실상의 표준 형식

형식을 매번 새로 고민하지 않으려고 업계가 모여 만든 규칙이 Conventional Commits입니다. 리액트, 앵귤러 등 대형 오픈소스가 쓰면서 국내 실무에도 거의 기본값으로 자리 잡았습니다. 구조는 간단합니다.

<타입>(<범위>): <제목>

<본문>

<꼬리말>

제목 줄만 필수이고 나머지는 선택입니다. 범위(scope)는 어느 영역을 건드렸는지를 적는 자리로, 프로젝트가 작다면 생략해도 됩니다.

타입 8가지만 외우면 끝

타입은 이 정도면 충분합니다. 팀에서 더 늘리는 경우도 있지만, 종류가 많아질수록 “이건 refactor인가 chore인가” 고민만 늘어납니다.

타입언제 쓰나예시
feat사용자가 체감하는 기능 추가feat(search): 검색어 자동완성 추가
fix버그 수정fix(cart): 수량 0일 때 결제 진행되던 문제
docs문서만 변경docs: README에 로컬 실행 절차 추가
style동작 변화 없는 포맷·세미콜론·들여쓰기style: prettier 규칙 일괄 적용
refactor동작은 같고 구조만 개선refactor(auth): 토큰 검증 로직 분리
perf성능 개선perf(list): 상품 목록 N+1 쿼리 제거
test테스트 추가·수정test(order): 부분 취소 케이스 추가
chore빌드·설정·의존성 등 잡무chore: eslint 9로 업그레이드

나쁜 메시지를 고쳐보기

실제로 자주 보이는 메시지들을 하나씩 고쳐봤습니다. 오른쪽 열을 읽었을 때 커밋을 열어보지 않고도 내용이 짐작되는지가 기준입니다.

흔한 메시지무엇이 문제인가고친 메시지
수정무엇을, 왜 고쳤는지 전혀 없음fix(login): 비밀번호 특수문자 입력 시 인증 실패
기능 추가어떤 기능인지 알 수 없음feat(export): 주문 내역 엑셀 다운로드 추가
리뷰 반영리뷰에서 뭘 지적받았는지 모름refactor(api): 응답 상태코드를 4xx로 통일
버그 픽스 완료했습니다.마침표·경어체로 길어짐, 타입 없음fix(push): 안드로이드 알림 중복 발송 차단
final_최종_v2커밋을 파일 저장처럼 씀chore(deps): axios 1.7.2로 고정

본문(body)은 언제 쓰나

제목 한 줄로 충분한 커밋이 대부분입니다. 본문은 “왜”가 제목에 안 들어갈 때만 씁니다. 판단 기준은 “나중에 누가 이 커밋을 보고 되돌리려 할 수 있는가”입니다. 되돌리면 안 되는 이유가 있다면 반드시 적어야 합니다.

feat(auth): 카카오 소셜 로그인 추가

이메일 가입만 지원해서 모바일 신규 가입 이탈률이 40%대였음.
카카오 OAuth2 인증 흐름을 추가하고, 같은 이메일로 가입한
기존 계정이 있으면 자동으로 연결되도록 처리했다.

Closes #142

제목과 본문 사이의 빈 줄은 형식상 필수입니다. 이 줄이 없으면 Git이 전체를 제목으로 취급해서 git log --oneline이 지저분해집니다. 본문은 72자쯤에서 줄바꿈하는 게 관례인데, 터미널에서 git log가 자동 줄바꿈 없이 읽히는 폭이기 때문입니다.

커밋 템플릿으로 형식을 몸에 익히기

규칙을 외우는 것보다 매번 눈앞에 띄우는 편이 빠릅니다. Git의 커밋 템플릿 기능을 쓰면 git commit을 실행할 때마다 편집기에 안내문이 뜹니다. #로 시작하는 줄은 저장 시 자동으로 제거되니 그대로 두어도 됩니다.

# <타입>(<범위>): <제목>  ← 50자 이내, 마침표 없이
#
# 왜 이 변경이 필요했는가? (무엇을 바꿨는지는 diff에 이미 있다)
#
#
# 타입: feat fix docs style refactor perf test chore
# 꼬리말: Closes #이슈번호 / BREAKING CHANGE: 설명
# 홈 디렉터리에 템플릿 파일을 저장한 뒤 등록
git config --global commit.template ~/.gitmessage.txt

# 이제 git commit 을 옵션 없이 실행하면 편집기에 템플릿이 뜬다
git commit

여기서 한 가지 습관이 따라옵니다. git commit -m "..." 대신 옵션 없는 git commit을 쓰는 것입니다. 편집기가 열리면 본문을 적기 쉬워지고, 자연히 메시지가 좋아집니다.

훅으로 팀 전체에 강제하기

혼자 쓰는 규칙은 지켜지지만, 팀 규칙은 대개 2주쯤 뒤에 무너집니다. 형식이 틀리면 아예 커밋이 안 되게 막는 편이 확실합니다. commit-msg 훅은 메시지가 확정되는 순간 실행되며, 종료 코드가 0이 아니면 커밋을 취소시킵니다.

#!/bin/sh
# .githooks/commit-msg — Conventional Commits 형식 검사

pattern="^(feat|fix|docs|style|refactor|perf|test|chore)(\(.+\))?: .{1,50}$"

if ! head -n 1 "$1" | grep -qE "$pattern"; then
  echo "커밋 메시지 형식이 올바르지 않습니다."
  echo "예시: feat(auth): 카카오 소셜 로그인 추가"
  echo "타입: feat fix docs style refactor perf test chore"
  exit 1
fi
commit-msg 훅이 규칙에 안 맞는 커밋을 거부하는 실제 화면
실제로 훅을 걸고 돌린 결과입니다. “수정함”은 거부되고, 형식에 맞춘 메시지만 커밋됩니다.
mkdir -p .githooks
# 위 스크립트를 .githooks/commit-msg 로 저장한 뒤
chmod +x .githooks/commit-msg

# 저장소 전체가 이 폴더를 훅 경로로 쓰도록 지정 (Git 2.9 이상)
git config core.hooksPath .githooks

.git/hooks/에 직접 넣은 훅은 저장소에 커밋되지 않아서 다른 팀원에게 전달되지 않습니다. 위처럼 .githooks/ 폴더에 두고 core.hooksPath로 지정해야 훅 파일이 버전 관리에 포함됩니다. 다만 core.hooksPath 설정 자체는 각자 한 번씩 실행해야 하므로, README나 설치 스크립트에 넣어두세요.

메시지보다 먼저인 것: 커밋 단위 쪼개기

사실 메시지가 “수정”이 되는 진짜 원인은 따로 있습니다. 커밋 하나에 버그 수정, 리팩터링, 오타 고침이 전부 들어 있으면 한 문장으로 요약할 방법이 없기 때문입니다. 좋은 메시지는 좋은 커밋 단위에서 나옵니다.

작업을 다 해놓고 나서야 쪼개야 하는 상황이라면 git add -p가 답입니다. 파일 전체가 아니라 변경 덩어리 단위로 골라 담을 수 있습니다.

# 변경 덩어리(hunk) 단위로 골라 담기
git add -p
#   y: 이 덩어리 담기   n: 건너뛰기
#   s: 더 잘게 쪼개기   q: 종료

git commit -m "fix(order): 주문 조회 시 page=0 요청이 500 내던 문제"

# 남은 변경만 다시 담아 두 번째 커밋으로
git add -p
git commit -m "refactor(order): 주문 응답 DTO를 조회/상세로 분리"

기준은 하나입니다. “이 커밋만 되돌려도 프로젝트가 정상인가?” 그렇다면 적절한 단위입니다.

주의할 점

  • 이미 원격에 푸시한 커밋의 메시지를 git commit --amendrebase로 고치면 커밋 해시가 바뀝니다. 혼자 쓰는 브랜치가 아니라면 다른 사람의 히스토리가 꼬이니, 푸시 이후에는 그냥 두는 편이 낫습니다.
  • 한글 메시지 자체는 문제없지만, 터미널 인코딩이 어긋나면 git log에서 깨져 보입니다. 윈도우에서는 git config --global i18n.logOutputEncoding utf-8i18n.commitEncoding utf-8을 함께 설정해 두세요.
  • 타입을 잘게 나눌수록 좋아 보이지만 실제로는 반대입니다. 고민하는 시간이 늘면 사람들은 결국 전부 chore로 적기 시작합니다.
  • 훅을 도입할 때는 기존 커밋까지 소급해서 검사하지 않습니다. 앞으로의 커밋만 대상이어야 팀의 반발이 없습니다.
  • BREAKING CHANGE: 꼬리말은 API 호환성이 깨질 때만 씁니다. 남용하면 진짜 중요한 변경이 묻힙니다.

마무리

오늘 당장 할 수 있는 가장 작은 첫걸음은 템플릿 파일 하나를 만들고 git config --global commit.template 한 줄을 실행하는 것입니다. 다음 커밋부터 편집기에 안내문이 뜨고, 그것만으로도 “수정” 같은 메시지는 거의 사라집니다. 타입과 훅은 그 습관이 붙은 다음에 도입해도 늦지 않습니다.

다음 글에서는 Claude Code로 간단한 자동화 스크립트를 만드는 법을 다룹니다. 반복되는 파일 작업을 자연어로 설명하면 실행 가능한 스크립트가 나오는 과정을, 직접 돌려본 예제로 정리하겠습니다.

댓글 남기기