Git commit_msg 훅 사용해보기

buchu·2023년 11월 11일

참고자료

Git hooks 이란? (+ Husky + lint-staged)

Git Hook을 이용해 코드 포맷팅 체크와 커밋 메시지 검증하기

git hook이란

.git/hooks 폴더에 위치한 shell 스크립트

연동하여 사용할 코드는 동일한 폴더(.git/hooks)에 저장

git hook 종류

클라이언트 훅

pre-commit

commit 시 가장 먼저 호출 (commit 메시지 작성 전에 호출됨)

exit code가 0이 아니면 커밋이 취소됨

코드 스타일 검사, 유닛 테스트 실행 등

prepare-commit-msg

commit이 생성된 이후, 편집기를 실행하기 전

입력으로 커밋 메시지 경로와 커밋 종류를 입력받음

commit 메시지를 자동으로 생성하는 용도

commit-msg

최종적으로 커밋이 완료되기 직전

입력으로 커밋 메시지 경로를 입력받음

exit code가 0이 아니면 커밋이 취소됨

생성된 커밋 메시지 템플릿 검증하는 용도

서버 훅

push 전후에 실행되어 복잡한 push 정책을 만드는 용도

push 전에 실행되는 훅은 0이 아닌 값을 반환시 push를 거절하고 클라이언트로 에러 메시지 전달

commit-msg 훅으로 커밋 메시지 템플릿 검증하기

💡 python 스크립트로 커밋 메시지를 읽어 커밋 메시지 규칙에 부합하는지 확인하여 오류 여부 체크

작업 순서

  1. 커밋 메시지 규칙 정하기
  2. 커밋 메시지 규칙을 체크하는 스크립트 작성
  3. 코드를 실행하는 commit-msg 스크립트 작성
  4. 커밋 테스트

1. 커밋 메시지 규칙 정하기

  • .git/gitmessage
    # <타입>: <제목> 의 형식으로 커밋 제목을 아래 공백줄에 작성
    # 제목은 50자 이내 / 변경사항이 "무엇"인지 명확히 작성 / 끝에 마침표 금지
    #### 제목 작성 시작 ####
    
    ####  제목 작성 끝  #### (아랫 줄 공백은 지우지 말 것)
    
    # 본문(구체적인 커밋 내용)을 아랫줄에 작성
    # 한 줄은 72자 이내 / 여러 줄의 메시지를 작성할 땐 "-"로 구분
    #### 본문 작성 시작 ####
    
    ####  본문 작성 끝  ####
    
    # 꼬릿말을 아랫줄에 작성 (현재 커밋과 관련된 이슈 번호 추가 등)
    #### 꼬릿말 작성 시작 ####
    
    ####  꼬릿말 작성 끝  ####
    
    #############################
    # 커밋 메시지 예시
    # feat: 로그인 기능 추가
    #
    # - login_by_email() 함수 생성
    # - login_by_sns() 함수 생성
    #
    # Close #7
    
    # 타입 종류
    # feat: 새로운 기능 추가
    # fix: 버그 수정
    # docs: 문서 수정
    # test: 테스트 코드 추가
    # refact: 코드 리팩토링
    # style: 코드 의미에 영향을 주지 않는 변경사항
    # chore: 빌드 부분 혹은 패키지 매니저 수정사항

2. 커밋 메시지 규칙을 체크하는 스크립트 작성

  • .git/hooks/verify_commit_msg.py
    #!/usr/bin/python3
    
    import re
    import sys
    import io
    
    type_list = [
        "feat",
        "fix",
        "refactor",
        "style",
        "docs",
        "test",
        "chore",
        "ci",
        "perf",
    ]
    
    type_regex = (
        r"^(feat|fix|refactor|style|docs|test|chore|ci|perf)(\(.+\))?\:\s(.{3,})"
    )
    
    class bcolors:
        FAIL = "\033[91m"
        ENDC = "\033[0m"
    
    def verify_commit_message():
        commit_msg_path = sys.argv[1]
    
        try:
            # Open the COMMIT_EDITMSG file with utf-8 encoding
            with io.open(commit_msg_path, "r", encoding="utf-8") as commit:
                lines = commit.readlines()
                
                # Remove comments
                lines = [line for line in lines if not line.startswith("#")]
    
                # If the last line is whitespace, remove it
                while lines[-1] == "\n":
                    lines = lines[:-1]
                    if len(lines) == 0:
                        break
    
                # Empty commit message
                if len(lines) == 0:
                    sys.stderr.write(
                        f"\n{bcolors.FAIL} Commit failed: {bcolors.ENDC}커밋 메시지가 비어있습니다.\n"
                    )
                    sys.exit(1)
                # Subject line should be less than 50 characters.
                if len(lines[0]) > 50:
                    sys.stderr.write(
                        f"\n{bcolors.FAIL} Commit failed: {bcolors.ENDC}커밋 메시지 제목을 50자 이내로 작성하세요.\n"
                    )
                    sys.exit(1)
                # Subject line should follow the rule.
                if re.match(f"({type_regex})", lines[0]) is None:
                    sys.stderr.write(
                        f"\n{bcolors.FAIL} Commit failed: {bcolors.ENDC}커밋 메시지 제목 규칙을 확인하세요."
                    )
                    sys.stderr.write("\n<type>: <subject> is required.\n")
                    sys.exit(1)
                # The subject should be a title-case.
                # if not lines[0].split(":")[1].strip()[0].istitle():
                #     sys.stderr.write(
                #         f"\n{bcolors.FAIL} Commit failed: {bcolors.ENDC}The subject should be title-cased.\n"
                #     )
                #     sys.exit(1)
                # If commit message has single line, description might be missing.
                if len(lines) == 1:
                    sys.stderr.write(
                        f"\n{bcolors.FAIL} Commit failed: {bcolors.ENDC}커밋 설명을 추가하세요.\n"
                    )
                    sys.exit(1)
                # After subject line, line space is required.
                if lines[1] != "\n":
                    sys.stderr.write(
                        "\n제목 뒤 빈 칸을 한 줄 추가하세요.\n"
                    )
                    sys.exit(1)
                for line in lines[2:]:
                    # Every single description should be less than 72 characters.
                    if len(line) > 72:
                        sys.stderr.write(
                            "\n커밋 설명의 각 행은 72자 이내로 작성하세요.\n"
                        )
                        sys.exit(1)
                    # Description starts with "-".
                    if len(lines) > 3 and not line.startswith("-"):
                        sys.stderr.write(line)
                        sys.stderr.write(
                            f"\n{bcolors.FAIL} Commit failed: {bcolors.ENDC}커밋 설명이 2줄 이상인 경우 각 항목을 '-'로 시작하세요.\n"
                        )
                        sys.exit(1)
                
        except Exception as e:
            print(f"\n{bcolors.FAIL} Commit failed: {bcolors.ENDC}An error occurred: {e}\n")
            sys.exit(1)
    
        sys.exit(0)
    
    if __name__ == "__main__":
        verify_commit_message()

3. 코드를 실행하는 commit-msg 스크립트 작성

  • .git/hooks/commit-msg
    #!/bin/sh
    
    python .git/hooks/verify_commit_msg.py "$1"
    
    if [ $? -ne 0 ]; then
        echo "Error: Commit message verification failed."
        exit 1
    fi
    
    echo "Commit message verification successful."
    exit 0

4. 커밋 테스트

실패 케이스

Untitled

Untitled

성공 케이스

Untitled

Untitled

0개의 댓글