Git Hooks
Git을 통해서 여러 인원과 함께 프로젝트 소스를 관리하는 것이 일반적입니다. 그런데 만약 ESLint와 Prettier 등 정적 분석 도구들을 아무리 잘 맞추었다고 해도, 개개인이 적용을 하지 않고 push를 하면 어떻게 될까요? linting을 칼같이 지키는 인원은 자신이 변경하지 않은 소스들 까지도 같이 변경점을 보이면서 push를 하게 되고 어떤 소스를 누가 왜 고쳤는지에 대한 tracking이 어려워질 것입니다. 또한 정해진 커밋 메시지 포맷(prefix로 commit 수 기입 / prefix로 이슈 넘버 기입 /...)을 실수로 인해 누락하는 경우도 있습니다. 이처럼 프로젝트 진행에 있어서 생길 수 있는 문제점들을 해결하기 위해서 git은 git hooks라는 것을 제공하고 있습니다.
github.com/git/git/tree/master/templates에서 10개의 sample으로 제공하고 있는데 이것이 특정 상황에 따라서 적용할 수 있는 git hooks입니다.
특정 상황에 적용할 수 있는 10개의 스크립트 중, 모든 인원에 대한 lint를 적용하기 위해서 hooks--commit-msg, 그리고 hooks--pre-commit를 수정한다면 이름처럼 commit 메시지에 대한 것과 commit 이전에 수행할 것에 대한 작성을 하는 것으로 프로젝트 전 인원에 대한 lint 수행을 필수화 할 수 있습니다.
그러나 git/hooks에 있는 파일을 수정하는 것은 프로젝트의 repository의 파일로 인식되지 않기 때문에 수정한 git/hooks의 파일들을 공유하기 위한 공간을 만들어야 하는 것을 수반합니다. 이러한 추가 제약사항을 husky를 사용하는 것으로 해결할 수 있습니다.
Husky
Husky는 git commits를 돕기 위해서 나온 패키지이며 현재 v5가 나올 정도로 지속적인 배포를 하고 있습니다. 다음과 같은 명령어를 통해서 설치 가능합니다.
<!-- npm install -->
$ npm install husky --save-dev
$ npm install pinst --save-dev # if your package is not private
<!-- yarn install -->
$ yarn add husky --dev
$ yarn add pinst --dev # if your package is not parivate
설치 이후 Git Hooks를 사용하기 위해서 다음과 같은 명령어를 사용합니다.
# npm
npx husky install
# yarn
yarn husky install
그 후 husky관련 명령어를 다음과 같이 작성하게 됩니다.
// inside of package.json
{
"husky": {
"hooks": {
"commit-msg": "npm script1",
"pre-push": "npm script2",
...
}
}
}
"npm script1"에 해당하는 부분이 우리가 작성하는 npm scripts에 의해서 바꿀 수 있습니다. package.json의 스크립트가 될 수도 있으며, 셸 스크립트일 수도 있습니다. typicode.github.io/husky/#/?id=husky_git_params-ie-commitlint-를 보시면 v5에서 추가된 부분은, HUSKY_GIT_PARAMS가 사라졌다는 것입니다. 과거에는 해당 variable을 Bash Shell의 인자로 사용하는 것을 통해서 commit-msg-file(커밋 메시지 파일), commit-source(커밋 파일), 그리고 sha-1(커밋 넘버)에 대한 변수들을 넘겨주었습니다. 값을 넘겨주지 않아도, 직접적으로 $1과 같이 사용할 수 있습니다. ($1: commit-msg-file / $2: commit-soruce / $3: sha-1) 해당 변수들을 통해서 커밋 메시지에 이슈 넘버 (애자일 보드에서, 스토리 넘버)에 해당하는 것을 포함할 수 있습니다.
Commit 메시지에 이슈 / 스토리 넘버 추가하기
// inside of package.json
{
"husky": {
"hooks": {
"commit-msg": "sh husky_commit_msg.sh",
"pre-push": "sh husky_pre_push.sh",
...
}
}
}
// husky_commit_msg.sh
BRANCH_POSTFIX="git rev-parse --abbrev-ref HEAD | cut -d '-' -f2 | tr '[a-z]' '[A-Z]'"
FIRST_LINE="head -n1 $1"
if [ -z "${FIRST_LINE}" ]; then
echo "${BRANCH_POSTFIX} - $3 / $1" > $1
fi
rev-parse(브랜치의 해시 값을 return) / --abbrev-ref(해시 값 대신에 오브젝트를 return)하는 것으로 내가 작업하고 있는 브랜치의 HEAD위치에 대한 브랜치 이름을 가져오게 됩니다. 그 후 '-'로 split 된 array 중 2번째를 가져오게 됩니다. (IT200024-1234)와 같이 `${ISSUE_NUMBER}-${STORY_NUMBER}`로 구성되어 있는 경우 STORY_NUMBER를 가져올 수 있습니다. 또한 cut -d '-' -f1을 통해서 ISSUE_NUMBER를 가져올 수 있으니 프로젝트의 성격에 맞게 선택하는 것이 필요합니다. 그 후 소문자를 대문자로 변경해주는 작업을 수행합니다.
커밋 메시지 파일의 처음이 비어있는 경우 실행(origin branch와의 병합 과정이 아닌 새로운 커밋 일 때만)하게 되며, 상황에 따라서 [ -n `${BRANCH_POSTFIX}` ]와 같이 브랜치의 스토리 넘버가 있는지 확인하는 명령어 사용도 가능합니다.
최종적으로 commit-msg-file에 해당하는 것을 `${STORY_NUM} - ${SHA-1} / ${COMMIT_MSG_FILE}`로 파일 생성을 하게 됩니다. ('echo' + '>'를 통해서 파일 저장과 생성을 간단하게 할 수 있습니다)
Commit이전에 ESLint 적용하기
// husky_pre_push.sh
eslint ./src/**/*.{js,jsx,ts,tsx}
// if you have some kinds of config file...
eslint --config .myConfig.yaml ./src/**/*.{js,jsx,ts,tsx}
설치되어있는 ESLint의 설정에 따라서 linting을 적용할 수 있고, 프로젝트의 성격에 맞는 config가 존재한다면 해당 부분을 적용할 수 있습니다. ESLint에서 -fix option을 통해서 자동으로 수정할 수도 있습니다. 저의 경우는 해당 옵션을 사용하지 않는 것을 선호합니다. React의 Functional Component의 useEffect / useLayoutEffect... 등을 사용할 때 (물론 소스를 잘 구성한다면 해당 의존성 배열이 맞게 작성됩니다) -fix로 인해 의존성 배열에 변수가 추가된다면 의도와 다른 컴포넌트 기능을 보일 가능성이 높고, 또한 해당 부분 버그를 찾아내는 것이 어렵기 때문입니다.
주인장의 주저리주저리
프로젝트를 진행하다 보면, 익숙해져서 빠뜨리는(?) 경우들이 생길 수 있습니다. git hooks에 Husky를 접목함으로써 사람이 실수할 수 있는 영역을 자동화 함과 동시에, 커밋 메시지를 이슈 혹은 스토리 넘버를 합쳐서 사용하는 것으로 GitLens extension을 사용한다면 더 쉽게 이슈 트레킹을 할 수 있습니다.
※ 잘못된 부분이나 궁금한 점 댓글로 작성해 주시면 최대한 답변하겠습니다. 읽어주셔서 감사합니다.