Post

Git hook (Git을 더 똑똑하게 쓰는 방법)

git commit 메시지 관리 및 테스트 수행

개요

Git을 쓰다 보면 이런 생각이 들 때가 있습니다. "커밋 전에 코드 포맷터를 자동으로 돌릴 수 없을까?", "push 전에 테스트를 자동으로 돌리고 싶다."

이럴 때 Git Hook을 활용하면 됩니다. 특히 프리랜서나 여러 조직의 사람이 급하게 모인 프로젝트에서 Git Hook을 사용하여 프로젝트 컨벤션과 코드 품질향상을 이룰 수 있었습니다. 이런 Git Hook에 대해서 설명합니다.

Git Hook

Git Hook은 Git의 특정 행동 시점(before/after) 에 실행되는 스크립트입니다.

예를 들어 commit, push, merge 같은 작업이 발생할 때 미리 지정한 동작을 자동으로 실행해줍니다. 쉽게 말해, Git의 자동화 트리거라고 볼 수 있습니다.

Hook 종류

git hook은 크게 실행되는 위치에 따라 client-side hooks, server-side hooks로 나뉩니다.

alt text

위의 그림과 같이 각 Hook은 client-side hooks, server-side hooks에서 차례대로 수행됩니다.

client-side hooks

로컬 개발 환경에서 실행되며, 커밋, 병합, 푸시 등의 작업에 연관된 이벤트에서 트리거됩니다.

  • pre-commit: 커밋을 실행하기 전에 호출되며, 커밋될 코드를 검토하고 유효성 검사를 할 수 있습니다. 예를 들어, 코드 스타일 검사를 수행하거나, 테스팅을 할 수 있습니다.
  • prepare-commit-msg: 커밋 메시지를 작성하기 전에 호출되며, 커밋 메시지를 미리 작성하거나 수정하는 데 사용할 수 있습니다. 예를 들어, 자동으로 이슈 번호를 추가할 때 유용합니다.
  • commit-msg: 커밋 메시지를 작성한 후에 호출되며, 메시지의 형식을 검증하거나 규칙을 강제할 때 유용합니다.
  • post-commit: 커밋이 완료된 후에 호출되며, 커밋 후 후속 작업을 할 수 있습니다. 예를 들어, 알림을 보낼 때 사용할 수 있습니다.
  • pre-push: 원격 저장소로 푸시하기 전에 호출되며, 푸시 전에 테스트를 실행하거나 코드를 검토할 수 있습니다.
  • pre-rebase: rebase 명령을 실행하기 전에 호출되며, 재정렬 전에 할 작업이 있을 때 사용합니다.
  • post-checkout: 새로운 브랜치로 체크아웃하거나 특정 커밋으로 이동한 후에 호출됩니다. 특정 브랜치 전환 시 필요한 작업을 처리할 수 있습니다.
  • post-merge: 병합이 완료된 후에 호출되며, 병합 후 충돌 해결이나 후속 처리를 자동화할 때 유용합니다.

server-side hooks

Git 서버에서 실행되며, 주로 서버 측에서 푸시된 변경 사항을 검증하거나 후속 작업을 처리하는 데 사용됩니다.

  • pre-receive: 원격 저장소에 푸시되기 전에 실행되며, 수신된 모든 커밋을 검토하여 허용할지 여부를 결정합니다. 예를 들어, 특정 브랜치로 푸시가 금지된 경우 이를 막을 수 있습니다.
  • update: pre-receive와 비슷하지만, 각 브랜치에 대해 한 번씩 호출됩니다. 브랜치별 검사를 할 때 유용합니다.
  • post-receive: 원격 저장소에 푸시가 완료된 후 실행되며, 후속 작업을 처리할 수 있습니다. 예를 들어, CI/CD 시스템을 트리거하거나 알림을 보낼 때 사용할 수 있습니다.
  • post-update: 한 개 이상의 브랜치가 업데이트된 후에 실행되며, 후속 작업을 위한 훅입니다.

Git Hook 사용법

  1. .git/hooks 폴더 확인
    • Git 저장소 안에는 기본적으로 hooks 폴더가 있습니다.

      1
      
        ls .git/hooks
      
  2. Hook 스크립트 작성
    • pre-commit 파일을 .git/hooks/에 저장하고 실행 권한을 부여합니다. (아래의 예제코드는 포맷팅을 실행합니다.)
      1
      2
      3
      
        #!/bin/sh
        echo "코드 포맷팅 중..."
        npx prettier --write .
      
  3. 커밋 시 자동 실행 확인
    • 이제 git commit을 실행하면, 자동으로 포맷터가 돌고 그 후에 커밋이 진행됩니다.

실제로 적용해보기(gradle)

java 프로젝트기준의 예시입니다. 예시에서는 pre-commit, commit-msg만 다뤄서 설명합니다.

또한 프로젝트 clone 후 컴파일 또는 빌드 과정을 통해 자동으로 client-side hooks을 등록하는 과정을 설명합니다.

git hooks 생성

  1. 프로젝트 루트 경로에 hooks 폴더를 생성하고 commit-msg, pre-commit 파일을 생성합니다.

    1
    2
    3
    
     mkdir hooks
     touch commit-msg
     touch pre-commit
    
  2. commit-msg 파일을 아래와 같이 생성합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    
     #!/bin/bash
    
     # Define valid prefixes
     VALID_PREFIXES="^(feat|fix|design|hotfix|style|refactor|comment|docs|test|chore|rename|remove):"
    
     # Get the commit message file
     COMMIT_MSG_FILE="$1"
    
     # Read the commit message
     COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
    
     # Check if the commit message is a merge commit
     if echo "$COMMIT_MSG" | grep -q '^Merge'; then
     exit 0
     fi
    
     # Check if the commit message starts with a valid prefix
     if ! echo "$COMMIT_MSG" | grep -qE "$VALID_PREFIXES"; then
     echo "Error: 커밋컨벤션을 지켜주세요!!아래 규칙을 따라 커밋을 해야합니다."
     echo "===================================================="
     echo "   feat     : 새로운 기능을 추가"
     echo "   fix      : 버그 수정"
     echo "   design   : CSS 등 사용자 UI 디자인 변경"
     echo "   hotfix   : 급하게 치명적인 버그를 고쳐야하는 경우"
     echo "   style    : 코드 포맷 변경, 세미 콜론 누락, 코드 수정이 없는 경우"
     echo "   refactor : 프로덕션 코드 리팩토링"
     echo "   comment  : 필요한 주석 추가 및 변경"
     echo "   docs     : 문서 수정"
     echo "   test     : 테스트 코드, 리펙토링 테스트 코드 추가, Production Code(실제로 사용하는 코드) 변경 없음"
     echo "   chore    : 빌드 업무 수정, 패키지 매니저 수정, 패키지 관리자 구성 등 업데이트, Production Code 변경 없음"
     echo "   rename   : 파일 혹은 폴더명을 수정하거나 옮기는 작업만인 경우"
     echo "   remove   : 파일을 삭제하는 작업만 수행한 경우"
     exit 1
     fi
    
     exit 0
    
  3. pre-commit 파일을 아래와 같이 생성합니다.

    이 예시에서는 maven or gradle로 test를 진행하게 설정합니다. ( 추가로 lint등과 같은 작업도 가능합니다.)

    • maven

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      
        #!/bin/bash
        if [ ! -f "./mvnw" ]; then
        echo "Maven Wrapper (mvnw) not found. Please ensure it is present in the project root."
        exit 1
        fi
      
        ./mvnw test # 테스트 진행
      
      
        if [ $? -ne 0 ]; then
        echo "Tests failed. Commit aborted."
        exit 1
        fi
      
    • Gradle

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      
        #!/bin/bash
        if [ ! -f "./gradlew" ]; then
        echo "gradle Wrapper (gradlew) not found. Please ensure it is present in the project root."
        exit 1
        fi
      
        ./gradlew test # 테스트 진행
      
        if [ $? -ne 0 ]; then
        echo "Tests failed. Commit aborted."
        exit 1
        fi
      
  4. maven-antrun-plugin를 활용하여 git hooks를 등록합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    
     <build>
         <finalName>portal-pubc</finalName>
         <plugins>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-antrun-plugin</artifactId>
                 <version>3.0.0</version>
                 <executions>
                     <execution>
                         <id>install-git-hooks</id>
                         <phase>initialize</phase>
                         <configuration>
                             <target>
                                 <copy todir="${basedir}/.git/hooks">
                                     <fileset dir="${basedir}/hooks"/>
                                 </copy>
                                 <chmod perm="755">
                                     <fileset dir="${basedir}/.git/hooks">
                                         <include name="*"/>
                                     </fileset>
                                 </chmod>
                             </target>
                         </configuration>
                         <goals>
                             <goal>run</goal>
                         </goals>
                     </execution>
                 </executions>
             </plugin>
         </plugins>
     </build>
    
  5. 위의 과정에서 만들었던 hooks들을 gradle task로 등록합니다.

    • build.gradle

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      
        // git hook 등록
        tasks.register('installLocalGitHook', Copy) {
            from "${rootProject.rootDir}/hooks"
            into "${rootProject.rootDir}/.git/hooks"
      
            eachFile {
                mode = 0755
            }
        }
      
        build.dependsOn installLocalGitHook
      
    • build.gradle.kts

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      
        val installLocalGitHook = tasks.register<Copy>("installLocalGitHook") {
            from("${rootProject.rootDir}/hooks")
            into(File("${rootProject.rootDir}/.git/hooks"))
      
            eachFile {
                mode = "755".toInt(radix = 8)
            }
        }
      
        tasks.build {
            dependsOn(installLocalGitHook)
        }
      

Git Template 활용

프로젝트 루트 경로에 git_template 폴더를 만들고, 실제 .git 폴더 구조처럼 hooks 폴더를 생성한 후 내부에 git hook 스크립트 파일을 추가합니다.

1
2
3
4
5
/main/git_templates
    └─ /hooks
        └─ pre-push
        └─ pre-commit
        └─ etc...

이후 프로젝트를 clone 할 때 --template 옵션에 사전에 생성한 template 디렉토리를 경로로 지정하여 클론을 받으면 template 옵션에 적힌 디렉토리를 기반으로 .git 디렉토리를 초기화하여 자동으로 Git hook 스크립트가 설정됩니다.

1
git clone --template=/main/git_templates <git-repo-url>

Git hook을 지원 라이브러리

  • husky : git 저장소에서 사용되는 Git hooks를 관리하고 실행하는 도구입니다. (링크)

정리

Git Hook은 귀찮은 반복 작업을 자동화하고, 팀원들의 실수를 줄일 수 있는 강력한 도구입니다.

특히 개발 팀이 커질수록 일관성과 자동화는 더욱 중요해지기 때문에, Git Hook은 필수적인 도구라고 볼 수 있습니다.

[출처]

  • https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
This post is licensed under CC BY 4.0 by the author.