본문 바로가기
생산성

git hooks 설정으로 팀 효율 높이기

by Luke K 2022. 12. 4.
이번 편은 commit이나 push 등의 git 명령어 동작 전에 코드 품질을 점검하고 잘못된 상황을 체크할 수 있는 git hooks를 다룬다.
git hooks 사용을 도와주는 husky라는 기술, 관련된 쉘 스크립트 작성법에 대해서 다뤄보려고 한다.

 

WeView 프로젝트를 진행하며 프론트엔드 개발과 팀에게 도움이 되는 환경설정도 겸하고 있다.

현재 커밋 메시지와 코드 정렬 기준 등을 포함한 내용들이 팀 컨벤션과 일치하도록 유지되고 있다.

개발 환경에서 반복되는 일들을 자동화하고 사람이 하나씩 체크해야 하는 일들을 기술로 자동화하는 것은 팀에 도움이 된다.

설정은 한 번이지만 팀의 수가 늘어나도 설정을 바탕으로 높아진 효율은 유지된다.

또 개발환경 설정을 바탕으로 컨벤션을 자동화하고 코드 품질을 높일 수 있다.

개발환경 설정부터 유용함을 경험하면서의 과정을 적용한 기술별로 포스팅하려고 한다.

git hooks가 무엇인지부터 알아보자.

git hooks는 무엇인가?

git은 git에서 일어나는 이벤트에 특정 스크립트를 실행할 수 있도록 Hook이라는 기능을 지원한다.

직접 .git/hooks 폴더에 들어가 살펴보자.

이러한 hooks들이 자리 잡고 있고 저 10개의 이벤트가 발생하는 시점에 동작하는 스크립트를 작성하면 실행시킬 수 있다.

저 hook에 관한 설명은 아래 테이블과 같고 출처는 가비아 블로그이다.

그러면 git hooks는 어떨 때 사용될 수 있을까?

필자가 적용한 git hooks로 git hooks에 사용되는 예시를 알아보자.

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

weview 서비스에서 적용한 git hooks

weview 팀이 husky를 적용한 내용은 다음과 같다.

commit 전에 코드의 안정성 확인 (lint, ts checking 등)
commit 전에 코드 스타일 일관성 유지 (prettier, eslint, stylelint 등)
push 전에 push 되는 branch 확인

각각의 script를 살펴보기 전에 한 가지 생각해보아야 한다.

husky 도입

열심히 git hooks에 적용되는 script를 만들어봤자 모든 팀원의 프로젝트에 적용된 것이 자명하지 않으면 의미가 없다.

괜히 우리 팀이 적용했는지 체크하는 일이 늘어날 수도 있다.

husky는 git hooks에 따른 script를 적용해놓았을 때 개발자에게 적용이 강제돼 설정해놓은 script를 자동으로 사용할 수 있는 환경을 만드는 기술이다.

즉 git hooks의 적용을 쉽고 직관적이게 만드는 기술이다.

husky 설치

npm install --save-dev husky

git hooks를 각각의 다른. git 폴더에 어떻게 적용시키나 하는 걱정 없이 husky만 설치하면 git hooks는 husky의 관리하에 움직인다.

npm install을 실행하면 자동으로 커스텀 git hooks가 적용되도록 해보자.

client/package.json

"scripts": {
  "prepare": "chmod ug+x .husky/* && cd .. && husky install client/.husky",
}

chmod ug+x .husky/\*는 .husky 폴더의 실행 권한을 줄 수 있도록 하는 쉘 명령어이다.

만약 사용하지 않으면

| hint: The '.husky/pre-commit' hook was ignored because it's not set as executable.

이런 에러를 만나게 된다.

필자의 경우 cd.. 을 해준 이유는 client폴더 안에 있는 .husky를 바깥 루트 폴더에 있는 .git과 연동해야 하기 때문이다.

.git디렉토리가 존재하는 경로로 이동해주어야 husky install을 실행할 수 있어 .git 폴더가 있는 경로로 이동해주었다.

다음 husky install client/.husky 명령어로 client 내부의 .husky 디렉터리와 git hooks를 연결해주었다.

pre-commit 스크립트

pre commit 이벤트는 커밋 git commit을 입력하고 실제 커밋이 찍히는 사이에 동작한다.

우리 팀은 typescript환경에서 개발하고 있다.

그렇기에 타입을 검사하는 과정을 포함한다.

또 git에서 staged 된 파일들을 lint(컨벤션 대로 정렬)할 수 있게하는 스크립트를 작성하려고 한다.

타입 체크

type check는 마지막말고 중간에도 편하게 type check를 할 수 있도록 package.json 내부에 스크립트를 작성해놓고 .husky/pre-commit 에서 재활용하였다.

package.json

"scripts": {
  "checkTs": "tsc --noEmit",
}

이후 npm run checkTs 를 실행하면 type 검사가 실행된다.

staged된 파일들 lint 하기

staged된 파일들만 lint하게 도와주는 lint-staged 라는 npm 라이브러리가 있다.

npm install --save-dev lint-staged

staged된 파일의 확장자에 맞추어 어떤 lint를 적용할 지 커스텀할 수 있다.

필자의 경우엔 eslint와 prettier 적용을 하였다.

client/package.json

"scripts": {
  "lintStaged": "lint-staged",
},
"dependencies": {},
.....
"lint-staged": {
    "*.{ts,tsx,js,jsx}": [
        "eslint --fix",
        "prettier --write"
    ],
    "*.{md,json}": [
        "prettier --write"
    ]
},

전체 pre-commit 쉘 스크립트

client/.husky/pre-commit

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

cd client
npm run checkTs
npm run lintStaged

적용 후 커밋 실행 시 화면

eslint 체킹에서 잘못된 부분이 있는 경우 

적용된 설정들을 Pass 한 경우 

pre-push 스크립트

weview 프로젝트에서는 dev 브랜치가 개발서버와 일치하고 main 브랜치가 실제 서버와 일치한다는 규칙을 가지고 branch 전략을 구성했다.

따라서 dev 브랜치와 main 브랜치로의 push를 제한하기 위해 스크립트를 작성하였다.

해당 포스팅을 참고하였다. 가비아-husky로 git hook하자

client/.husky/pre-push

#!/bin/sh

FORBIDDEN_HTTPS_URL="<https://github.com/boostcampwm-2022/web06-WeView.git>"
FORBIDDEN_SSH_URL="git@github.com:boostcampwm-2022/web06-WeView.git"
FORBIDDEN_REF_MAIN="refs/heads/main"
FORBIDDEN_REF_DEV="refs/heads/dev"

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

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

if read local_ref local_sha remote_ref remote_sha
then
  echo "현재 푸쉬하는 브랜치는 $local_ref 내부입니다."
    if [ "$remote_ref" == "$FORBIDDEN_REF_MAIN" ] || [ "$remote_ref" == "$FORBIDDEN_REF_DEV" ]
    then
        echo "DO NOT PUSH TO MAIN OR DEV"
        exit 1 # 금지된 ref 로 push 를 실행하면 에러
    fi
fi

exit 0

하나씩 살펴보는 pre-push 스크립트

push가 금지돼야할 저장소와 브랜치의 주소를 변수로 만들어준다.

FORBIDDEN_HTTPS_URL="<https://github.com/boostcampwm-2022/web06-WeView.git>"
FORBIDDEN_SSH_URL="git@github.com:boostcampwm-2022/web06-WeView.git"
FORBIDDEN_REF_MAIN="refs/heads/main"
FORBIDDEN_REF_DEV="refs/heads/dev"

push 명령어가 일어날 때 $1은 원격 이름 $2는 주소이므로(해당 내용 문서 바로가기) 각각을 이름으로 파악하기 위해 변수로 만들자.

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

실제 저장소에서는 push를 제한하고 fork해서 작업하는 본인의 브랜치는 push를 제한하지 않도록 이른 exit을 실행시킨다. (exit 0은 push가 성공된다.)

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

이 후 push하는 브랜치(remote-ref)를 체크해서 main브랜치나 dev 브랜치에 push하면 에러가 발생하여 push가 되지 않도록 설정한다.

if read local_ref local_sha remote_ref remote_sha
then
  echo "현재 푸쉬하는 브랜치는 $local_ref 내부입니다."
    if [ "$remote_ref" == "$FORBIDDEN_REF_MAIN" ] || [ "$remote_ref" == "$FORBIDDEN_REF_DEV" ]
    then
        echo "DO NOT PUSH TO MAIN OR DEV"
        exit 1 # 금지된 ref 로 push 를 실행하면 에러
    fi
fi

dev 브랜치 push 시 결과 화면

마무리

git hooks를 잘 이용하면 컨벤션을 맞추거나 실수를 줄이는 데 이용할 수 있다.

또 husky를 이용한다면 팀원들이 같은 환경에서 작업을 하고 있음을 자명하게 만든 상태로 작업을 할 수 있다.

팀원들이 적용한 설정에 공감할수록 이러한 기술의 적용들은 진가를 발휘한다.

팀에 해당 기술을 가치를 체험한 후 좋았던 점을 설득하고 팀원들의 능률이 올라갈 방법을 생각해서 적용하기를 추천드린다.

필자의 경우는 팀원들이 추가하면 편해질 것이라 생각하는 git hooks가 생기면 지속적으로 추가하고 있다.

해당 프로젝트의 코드는 여기에서 확인할 수 있다.

글에서는 client 폴더를 예시로 들었지만 nest.js와 typescript로 이루어진 husky 설정도 확인할 수 있다.

읽으시는 분들이 개발환경 설정에 드는 시간을 더 줄여 집중하고 싶은 부분에 더 집중하실 수 있기를 바란다.

참고 자료

가비아 기술 블로그

우아한 형제들 기술 블로그

허스키 공식문서

댓글