본문 바로가기
4주차

커밋 메시지 컨벤션을 지키는 방법

by 한수정1 2025. 5. 14.

 

 

안녕하세요 웹 OB 한수정입니다.

저는 종종 커밋메시지를 잘못 올리곤 합니다... 종종? 자주? 항상? 

 

그래서 하루 건너 하루 지피티에게 커밋 메시지 수정하는 법에 대해 물어보는 삶을 살고 있었습니다. 

 

 

이거 하면 수정은 되더라구요 ㅎㅎ

git commit --amend

 

 

아무튼, 그런 저에게 한 줄기 빛이 내려왔습니다. 

 

그건 바로 husky

 

4주차 공유과제에서 husky에 대해 이야기 해보려고 합니다. 

 

husky는 협업에서 코드 스타일, 커밋 메시지 등을 통일해주는 자동화 도구 중 하나입니다. 

이번 공유과제에서는 pnpm을 기반으로 husky와 lint-staged, commitlint를 이용해 커밋 전에 린트 자동 실행 및 커밋 메시지 형식 검사에 대해 간단히 알아보겠습니다. 

 

🐶 Husky란?

Husky는 Git 훅(Git hook)을 쉽게 설정하고 관리할 수 있게 도와주는 도구입니다. 예를 들어 pre-commit, commit-msg 훅을 이용해 커밋 전에 자동으로 린트 검사를 실행하거나 커밋 메시지 형식을 검사할 수 있습니다.

 

📦 pnpm으로 Husky 설치 및 설정

pnpm add -D husky lint-staged @commitlint/cli @commitlint/config-conventional

 

husky 초기화해줍니다.

pnpm husky install

 

그 후, package.json을 확인해줍니다. 

 

  "devDependencies": {
    "@commitlint/cli": "^19.8.1",
    "@commitlint/config-conventional": "^19.8.1",
},

 

해당 내용이 잘 포함되었는지 확인하고, 없는 경우 추가 후 pnpm install을 진행해줍니다.

 

 

🛠 Git 훅 추가

.husky 폴더가 프로젝트의 루트에 생기고 여기에 훅 스크립트를 추가합니다. 

 

✅ pre-commit 훅, 터미널을 사용하지 않고 .husky 폴더 바로 아래에 pre-commit이라는 파일을 추가해주어도 문제 없습니다. 

npx husky add .husky/pre-commit "pnpm exec lint-staged"

 

✅ .husky/pre-commit 파일에 아래 내용을 추가해줍니다.

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

pnpm exec lint-staged

 

✅ commit-msg 훅, 터미널을 사용하지 않고 .husky 폴더 바로 아래에 commit-msg이라는 파일을 추가해주어도 문제 없습니다. 

npx husky add .husky/commit-msg "pnpm exec commitlint --edit \$1"

 

✅ .husky/commit-msg 파일에 아래 내용을 추가해줍니다.

아래 내용은 팀 별로 정한 커밋 컨벤션을 지정해줍니다. 

 

저는 합동세미나 모바일 웹 2조를 기준으로 feat: 진행한 작업 (#이슈번호) 로 지정했습니다. 

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

echo "[커밋 메시지 규칙 안내]"
echo "예시: feat: 로그인 기능 추가 (#1)"
echo ""

pnpm exec commitlint --edit $1

 

 

🛠 커밋 메시지 규칙 설정

위 세팅이 끝난 후 루트에 commitlint.config.cjs 파일을 생성하고 아래 내용을 추가합니다.

아래 내용도 예시: feat: 진행한 작업 (#이슈번호) 를 기준으로 작성되었습니다. 

module.exports = {
  // 기본 커밋 메시지 규칙을 정의한 설정을 확장합니다.
  extends: ['@commitlint/config-conventional'],

  rules: {
    // type은 반드시 아래 목록 중 하나여야 함
    'type-enum': [
      2, // severity: 0 = off, 1 = warning, 2 = error
      'always',
      [
        'feat',     // 새로운 기능
        'chore',    // 빌드/도구 설정 등 기타 작업
        'style',    // 코드 포맷팅 (기능 변경 없음)
        'fix',      // 버그 수정
        'hotfix',   // 긴급 수정
        'docs',     // 문서 관련 수정
        'refactor', // 코드 리팩토링 (기능 변화 없음)
        'test',     // 테스트 코드 추가/수정
        'design',   // UI/UX 관련 변경
        'build',    // 빌드 시스템 수정
        'deploy',   // 배포 관련 변경
      ],
    ],
    // subject에 대해 대소문자 등 케이스는 신경 쓰지 않음
    'subject-case': [0],
    
    // 커밋 메시지 전체 길이는 100자 이하 권장
    'header-max-length': [2, 'always', 100],

    // 커밋 메시지 끝에 반드시 이슈 번호를 포함해야 함 (#123 형태)
    'header-issue-suffix': [2, 'always'],
  },

  plugins: [
    {
      rules: {
        // 커밋 메시지 헤더 끝에 (#숫자) 형식의 이슈 번호가 있는지 확인
        'header-issue-suffix': (parsed) => {
          const { header } = parsed;

          const issuePattern = /\(#\d+\)$/; // 예: (#12)
          const typePattern = /^(feat|fix|chore|style|docs|refactor|test|design|build|deploy)/;

          // 타입이 올바른지 확인
          if (!typePattern.test(header)) {
            return [
              false,
              '❌ 커밋 메시지는 feat, fix, chore 등 올바른 타입을 포함해야 합니다.',
            ];
          }

          // 이슈 번호가 포함되어 있는지 확인
          if (!issuePattern.test(header)) {
            return [
              false,
              '❌ 커밋 메시지는 반드시 이슈 번호로 끝나야 합니다.',
            ];
          }

          return [true]; // 통과!
        },
      },
    },
  ],
};

 

 

아래는 주석 없는 코드를 .. 토글로 올리고 싶은데 접은 글로 올려보겠습니다. 

더보기
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'feat',
        'chore',
        'style',
        'fix',
        'hotfix',
        'docs',
        'refactor',
        'test',
        'design',
        'build',
        'deploy',
      ],
    ],
    'subject-case': [0],
    'header-max-length': [2, 'always', 100],
    'header-issue-suffix': [2, 'always'],
  },
  plugins: [
    {
      rules: {
        'header-issue-suffix': (parsed) => {
          const { header } = parsed;
          const issuePattern = /\(#\d+\)$/;
          const typePattern = /^(feat|fix|chore|style|docs|refactor|test|design|build|deploy)/;  // 커밋 타입 체크

 

          if (!typePattern.test(header)) {
            return [
              false,
              '❌ 커밋 메시지는 feat, fix, chore 등 올바른 타입을 포함해야 합니다.',
            ];
          }

 

          if (!issuePattern.test(header)) {
            return [
              false,
              '❌ 커밋 메시지는 반드시 이슈 번호로 끝나야 합니다.',
            ];
          }

 

          return [true];
        },
      },
    },
  ],
};

🔧 lint-staged 설정

✅ package.json에 다음과 같이 추가합니다. 저는 구분이 쉽게 가장 아래 적어주었습니다. 

{
  "lint-staged": {
    "*.{ts,tsx}": [
      "eslint --fix"
    ]
  }
}

 

 

❔ 만약 커밋 메시지를 컨벤션과 다르게 올린다면

아래와 같은 결과를 확인할 수 있습니다. 

 

- 커밋 메시지에 이슈번호가 포함되지 않은 경우

 

- 커밋 메시지에 feat, chore 등 type이 포함되지 않은 경우

 

- 터미널이 아닌 git 탭에서 커밋을 올리는데, 커밋 컨벤션에 맞춰서 올리지 않은 경우

 

✅ 마무리

오늘 제가 진행한 husky 설정은 앞으로 커밋할 때마다

  • eslint --fix가 자동 실행되고,
  • 커밋 메시지가 규칙에 맞지 않으면 커밋이 거부됩니다.

husky는 협업 시 실수를 줄이고, 코드 품질과 기록을 깔끔하게 유지할 수 있는 훌륭한 설정입니다 !!

 

 

'4주차' 카테고리의 다른 글

GIT 브랜치 전략  (1) 2025.06.13
API 에러 처리: 사용자 경험 향상을 위해서.  (0) 2025.05.15
대중적인 API instance 세팅과 고려해야할 점!  (0) 2025.05.13
네이밍 규칙  (1) 2025.05.13
CommonJS와 ES Modules  (0) 2025.05.13