Git Hooks 정리

milkboy2564·2023년 3월 25일
0

ESLint, Prettier, Testing …

위에 서술한 모든 도구 또는 규칙은 코드를 작성하거나 협업을 하는데 있어 통일성, 가독성, 안정성 등을 높일 수 있는 아주 유용한 규칙들이다.

이러한 규칙들을 가지고 개발을 진행하게 되면 추후에 발생하게 될 유지, 보수의 비용을 줄여줄 수 있다.

하지만 말만 번지르르하게 하고 정작 정의한 규칙들을 지키지 않으면 말짱 도루묵이다. 이러한 문제를 해결할 수 있도록 하는 것이 바로 Git Hooks다.

아무리 지키려고 노력해도 인간이기 때문에 발생할 수 있는 Human Error까지 모두 잡아내기에는 불가능하다. 그래서 Git hooks를 통해 이러한 규칙들을 강제하면서 무조건 지켜지는 규칙을 만드는 것이다.

Git Hooks

Git Hook이란 Git과 관련된 이벤트가 발생했을 때 특정한 스크립트를 실행하는 기능이다. 크게 클라이언트 훅과 서버 훅으로 나뉘는데 클라이언트 훅은 commit, push, merge 등이 발생하기 전에 실행하는 훅이고 서버 훅은 remote repository로 push가 발생했을 때 실행하는 훅이다.

클라이언트 훅 은 커밋 워크플로 훅이메일 워크플로 훅, 그리고 기타 훅 으로 분류할 수 있다.

커밋 워크플로 훅 은 git commit 명령으로 커밋을 할 때 실행하는 훅이고 이메일 워크플로 훅 은 git am 명령으로 이메일을 통해 patch 파일을 적용할 때 실행하는 훅이다. 기타 훅 은 rebase, merge, push 와 같은 이벤트를 실행할 때 실행하는 훅을 포함한다.

분류설명
커밋워크플로 훅pre-commitcommit 을 실행하기 전에 실행
prepare-commit-msgcommit 메시지를 생성하고 편집기를 실행하기 전에 실행
commit-msgcommit 메시지를 완성한 후 commit 을 최종 완료하기 전에 실행
post-commitcommit 을 완료한 후 실행
이메일워크플로 훅applypatch-msggit am 명령 실행 시 가장 먼저 실행
pre-applypatchpatch 적용 후 실행하며, patch 를 중단시킬 수 있음
post-applypatchgit am 명령에서 마지막으로 실행하며, patch 를 중단시킬 수 없음
기타 훅pre-rebaseRebase 하기 전에 실행
post-rewritegit commit –amend, git rebase 와 같이 커밋을 변경하는 명령을 실행한 후 실행
post-mergeMerge 가 끝나고 나서 실행
pre-pushgit push 명령 실행 시 동작하며 리모트 정보를 업데이트 하고 난 후 리모트로 데이터를 전송하기 전에 실행. push 를 중단시킬 수 있음

Git Hook 적용 예시

#!/bin/sh

FORBIDDEN_HTTPS_URL="https://github.com/milkboy2564/milkboy.git" # insert your remote url (https)
FORBIDDEN_SSH_URL="git@github.com:milkboy2564/milkboy.git" # insert your remote url (ssh)
FORBIDDEN_REF="refs/heads/master" # insert branch ref

remote="$1"
url="$2"

if [ "$url" != "$FORBIDDEN_HTTPS_URL" -a "$url" != "$FORBIDDEN_SSH_URL" ]
then
    exit 0 # Forked Project 에서는 제한하지 않음
fi

if read local_ref local_sha remote_ref remote_sha
then
    if [ "$remote_ref" == "$FORBIDDEN_REF" ]
    then
        echo "DO NOT PUSH it master"
        exit 1 # 금지된 ref 로 push 를 실행하면 에러
    fi
fi

exit 0

Git Hook 공유하기

Git hook은 프로젝트 내의 .git 디렉토리 안에 작성한다. 하지만 .git 디렉토리는 tracking의 대상이 아니므로 Github과 같은 remote repository에 올라가지 않는다.

아래는 Git hook을 공유하는 세 가지 방법이다.

  1. Git hook 설정 Script 공유
  2. Git Template
  3. Husky

Git hook Script

이 방식은 Git hook을 .git/hooks 경로에 복사하는 스크립트를 만들어 별도로 버전 관리하는 방식이다.

이 방법은 가장 기본적인 방법이지만 개발자가 해당 repository를 clone한 후 직접 해당 스크립트(Git hook을 복사하는 스크립트)를 실행하지 않으면 적용되지 않는 방법이다.

Git Template

Git Template 방식은 repository clone 시 --template 옵션을 통해 .git 파일을 초기화할 수 있다는 점을 이용한 방식이다.

이 방식은 Git hook을 자신의 컴퓨터 내의 별도의 경로에서 관리할 수 있다는 장점이 있지만 미리 Template 파일을 공유하고 있어야 하며 clone 시 --template 옵션을 사용하지 않으면 적용할 수 없다는 단점이 있다.

Husky

위 두 방식은 모두 Git Hook을 공유할 수 있는 방법이긴 하지만 작업자가 직접 어떠한 설정을 하지 않는다면 적용할 수 없다는 치명적인 단점이 존재한다.

규칙을 강제하기 위해 사용하는 것이 Git hook인데 사전에 작업자가 Git hook 관련 설정(Ex. 스크립트 실행 등)을 하지 않으면 규칙을 강제할 수 없기 때문에 문제가 발생할 여지가 크다고 볼 수 있고 강제성이 제대로 부여됐다고 생각하기 어렵다.

Husky는 모듈을 관리하기 위해 npm을 사용하고 있다면 최고의 선택이 될 수 있다.

Husky는 Git Hooks을 보다 쉽게 적용할 수 있는 npm 모듈이다. 심지어 Git Hooks에 대해 자세히 알지 못하더라도 commit, push 정책을 관리하고 공유할 수 있다.

Husky 사용 예시

먼저 npm에서 husky를 설치해준다.

npm install --save-dev husky

다음으로 commit 정책을 설정하면된다.

package.json 파일에 직접 설정해도 되고 .huskyrc 파일에 설정해줘도 된다. .huskyrc 파일로 분리해서 설정하는 이유는 별도의 파일로 관리하기 위함이라고 생각하면 된다.

.huskyrc 파일을 만들고 아래와 같이 작성한다.

{
    "hooks": {
        "pre-commit": "echo 'Hello World!'"
    }
}

이렇게 작성하게 되면 commit 이전에 터미널에서 Hellow World! 라는 메세지가 보이게 된다. 이런 방식으로 commit, push 등의 Git 이벤트가 발생했을 때 정책들을 설정해주면 된다.

또한 Husky는 개발자가 모듈을 설치하는 것만으로 적용 된다. 다시 말해, repository를 clone 받고 npm install 커맨드를 치면 자동으로 적용이 된다.

이는 곧 위 두 방식의 문제점으로 언급됐던 작업자가 Git hook을 적용하기 전에 어떠한 설정을 해줘야 한다는 단점이 사라지게 된다.

위에서 .git 파일에 직접 작성해줬던 master에 직접 push를 방지하는 Git hook 파일을 husky를 활용해서 다시 작성해보자.

먼저 huskyhooks 라는 디렉토리를 만들어준다. 참고로 이름은 어떻게 지어도 상관없다.

huskyhooks 디렉토리 안에 pre-push 라는 이름을 가진 파일을 만들고 아래와 같이 작성한다.

#!/bin/sh

FORBIDDEN_HTTPS_URL="https://github.com/gabia/git-hooks-study.git" # insert your remote url (https)
FORBIDDEN_SSH_URL="git@github.com:gabia/git-hooks-study.git" # insert your remote url (ssh)
FORBIDDEN_REF="refs/heads/master" # insert branch ref

ARR_GIT_PARAMS=($(echo $HUSKY_GIT_PARAMS))
ARR_GIT_STDIN=($(echo $HUSKY_GIT_STDIN))

remote=${ARR_GIT_PARAMS[0]}
url=${ARR_GIT_PARAMS[1]}

local_ref=${ARR_GIT_STDIN[0]}
local_sha=${ARR_GIT_STDIN[1]}
remote_ref=${ARR_GIT_STDIN[2]}
remote_sha=${ARR_GIT_STDIN[3]}

if [ "$url" != "$FORBIDDEN_HTTPS_URL" -a "$url" != "$FORBIDDEN_SSH_URL" ]
then
    exit 0 # Forked Project 에서는 제한하지 않음
fi

if [ "$remote_ref" == "$FORBIDDEN_REF" ]
then
    echo "DO NOT PUSH it master"
    exit 1 # 금지된 ref 로 push 를 실행하면 에러
fi

exit 0

그리고 .huskyrc 파일에서 pre-push 훅이 위 스크립트를 실행하도록 설정해주면 된다.

{
    "hooks": {
        "pre-commit": "echo 'Hello World!",
        "pre-push": "./huskyhooks/pre-push"
    }
}

이렇게 설정하게 되면 push 하기 전 pre-push 스크립트가 실행되고 만약 master 브랜치에 push 하는 경우 에러를 발생시키면서 push가 실패하게 된다.

다른 예시로 commit 전에 lint 검사를 해서 코드 스타일이나 문법 오류를 점검할 수 있고 push 전에 test를 해볼 수도 있다.

Husky 실습

Install

  1. 자동으로 설치

    npx husky-init && npm install
    or
    npx husky-init && yarn
  2. 수동으로 설치

    npm install -save-dev huksy
    or
    yarn add --dev husky
    npx husky install

설치하면 Root 경로에 .husky 라는 폴더 생성

Setting

설치(clone) 후 자동적으로 깃 훅이 가능하게 만들어 주기 위해 Package.json 파일에 스크립트 추가

// package.json
{
	...,
  "scripts": {
	  ...,
    "prepare": "husky install"
  }
} 

Add Git Hooks

pre-commit

npx husky add .husky/pre-commit 'npm run lint-fix'
or
npx husky add .husky/pre-commit 'yarn lint-fix'

위와 같이 실행하면 .husky 디렉토리에 pre-commit 파일이 생깁니다.

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

npm run lint-fix
echo 'Hello Mcircle'

pre-push

npx husky add .husky/pre-push 'npm run test'
or
npx husky add .husky/pre-push 'yarn test'

pre-push도 마찬가지로 위 와 같이 실행하면 됩니다.

내가 수정한 파일만 lint 검사하기

npm install -D lint-staged
or
yarn add --dev lint staged

그 후 package.json에 lint-staged 를 수행하는 스크립트를 추가해줍니다.

// package.json
{
	...,
  "scripts": {
	  ...,
    "lint-staged": "lint-staged"
  },
  "lint-staged":{
    "**/*.{ts,tsx,js,jsx}": [
       "eslint -fix"
    ]
  }
} 

실행해야 하는 스크립트가 변경되었으니 pre-commit Hook도 변경해줍니다.

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

npm run lint-staged
echo 'Hello Mcircle'

참조

profile
FE 개발자

0개의 댓글