husky로 git hook에 conventional commit 적용하기
Conventional Commits?
커밋 메세지에 사용자와 기계 모두가 이해할 수 있는 의미를 부여하기 위한 스펙
명확한 커밋 히스토리를 생성하기 위한 간단한 규칙을 제공
커밋 히스토리를 이용하여 더 쉽게 자동화된 도구를 만듦
이 컨벤션은 커밋 메세지에 신규 기능 추가, 문제 수정, 커다란 변화가 있음을 기술함으로써 유의적 버전(Sementic Versioning)과 일맥상통
git 으로 commit 시에 일괄된 양식을 유지 → 그 양식을 바탕으로 버전 관리나 Change Log 를 자동으로 만들 수 있음
커밋 메시지 구조
<타입>[적용 범위(선택 사항)]: <설명>
[본문(선택 사항)]
[꼬리말(선택 사항)]
Git hook에 Conventional Commit 적용하기
Commit 메시지를 아름답고 정갈하게 유지하기 위해 conventional commit을 git hook에 적용하는 과정을 소개하려고 한다.
1. 패키지에 husky 적용하기
husky 가 뭔가요?
husky는 git hook을 손쉽게 제어하도록 도와주는 npm 라이브러리이다.
git hook?
git을 쓰다가 특정 이벤트(커밋할 때, 푸시할 때 등등)가 벌어졌을 때, 그 순간에 ‘갈고리’를 걸어서 특정 스크립트가 실행되도록 도와주는 것!
물론 husky를 쓰지 않더라도 git hook을 설정할 수 있는 공식적인 방법은 따로 있다..git/hooks
폴더에 들어가서 스크립트를 작성하면 된다.
그러나 .git/hooks
폴더 안에 스크립트 파일을 넣게 되면 그 파일은 git
에 기록되지 않아서 따로 관리해야 한다는 단점이 있다.
또 git hook
으로 npm scripts
를 제어하고 싶을 때, 예컨대 npm test
등의 명령어를 써야 한다면 스크립트를 작성하는 게 번거롭다.
husky
는 굳이 .git/hooks
폴더를 건드리지 않고도 git hook
스크립트를 제어할 수 있게 도와준다.
husky 설치하기
npx husky-init && npm install
package.json
파일에는 아래와 같은 script가 추가된 것을 확인할 수 있다.
"prepare": "husky install"
그리고, .husky
디렉토리가 생성된 것도 확인할 수 있다.
.husky/pre-commit
파일이 생성되었는데, 기존에 test
명령어가 있었기 때문에 이를 감지하여 아래와 같이 추가되었다.
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm test
2. 패키지에 commitlint 적용하기
commitlint 는 뭔가요?
commit 에 대한 lint를 확인하여 성공/실패를 리턴해주는 도구이다.
commitlint 설치하기
npm install -D @commitlint/cli @commitlint/config-conventional
commitlint config 추가하기
루트 디렉토리에 commitlint.config.js
파일을 추가해준다.
module.exports = { extends: ['@commitlint/config-conventional'] };
commitlint의 기본 컨벤션
module.exports = {
parserPreset: 'conventional-changelog-conventionalcommits',
rules: {
'body-leading-blank': [1, 'always'],
'body-max-line-length': [2, 'always', 100],
'footer-leading-blank': [1, 'always'],
'footer-max-line-length': [2, 'always', 100],
'header-max-length': [2, 'always', 100],
'subject-case': [
2,
'never',
['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
],
'subject-empty': [2, 'never'],
'subject-full-stop': [2, 'never', '.'],
'type-case': [2, 'always', 'lower-case'],
'type-empty': [2, 'never'],
'type-enum': [
2,
'always',
[
'build',
'chore',
'ci',
'docs',
'feat',
'fix',
'perf',
'refactor',
'revert',
'style',
'test',
],
],
},
};
3. commit-msg hook 적용하기
commit-msg
훅은 최종적으로 커밋이 완료되기 전에, 프로젝트 상태나 커밋 메시지를 검증하기 위해 사용한다.
husky hook 에 commit-msg 추가하기
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit $1'
이렇게 하면, commitlint 의 컨벤션으로 husky가 commit-msg 훅을 실행한다.
commit-msg hook 동작 확인하기
그럼 어디한번 동작하는지 확인해볼까.
우선 잘못된 커밋 메시지를 넣어보자.
git commit -m 'hello-world'
두둥탁- 짠-.
subject 가 비어있으면 안되고, type 도 비어있으면 안된다고 지적해준다.
이번엔 제대로된 메시지를 넣어보자.
git commit -m 'test: hello world'
아주아주 잘되는 것을 확인할 수 있다.
git kraken 에서 동작 확인하기
야심차게 git kraken 에서도 잘못된 커밋메시지로 커밋을 시도해봤다.
그런데 웬걸 😱
커밋이 너무 잘된다... 젠장.
commit-msg 훅 로그를 들여다보니.
별 말도 없다. 어쩌란 건지..
그래서 issue tracking 을 해보니, 아래와 같은 문제점이 있다고 한다.
버전 7.5부터 Gitkraken은
core.hookspath
를 지원하지 않으며.git/hooks
을 사용할 것입니다.이 문제를 해결하는 방법은 매우 간단합니다..git/hooks
에서 허스키의 디렉토리로의 심볼릭 링크를 만듭니다.
최선의 방법은 아니지만 내가 찾은 유일한 방법입니다.$ rm -rf .git/hooks && ln -s ../.husky .git/hooks
You may add this command to your package.json
postinstall
script, alongsidehusky install
:{ "scripts": { "postinstall": "husky install && rm -rf .git/hooks && ln -s ../.husky .git/hooks" } }
From you project root:
참고로, git 공식 문서에 이런 글이 있다.
By default the hooks directory is$GIT_DIR/hooks
, but that can be changed via thecore.hooksPath
configuration variable.husky v5부터는 기본 git의 경로 (
.git/hooks
)가 아닌 사용자 지정 후크 경로 (.husky
) 가 사용됩니다 . 이것은 로컬 git 구성core.hookspath
키 설정을 통해 수행됩니다.
하...
이렇게까지 해서 맥 Big Sur OS 에도 최적화되지 못한 채, 커밋 날릴때마다 7초 이상 기다리게 하더니, 이제는 깃훅 path 커스텀도 막아버리는 무례한 깃크라켄의 만행을, 돈을 내가면서 참아줘야 하는걸까.
결국 했다.
gitkraken 을 사용하지 않는 컨트리뷰터들을 위해 아래 postinstall script 는 추가하지 않고, 로컬에서만 .git/hooks 에 심볼링 링크를 걸어주는 것으로 끝냈다.
4. 컨벤션에 맞게 커밋하기
컨벤션에 맞게 커밋하는 것을 도와주는 commitizen cli 도구를 사용하려고 한다.
commitizen 설치하기
npm i -D commitizen
commitizen 어댑터 구성하기
commitizen으로 커밋을 만들기 위한 방식은 어떤 어댑터를 사용하느냐에 따라 달라진다.
여기에서는 cz-conventional-changelog 어댑터 사용할 것이다.
npx commitizen init cz-conventional-changelog --save-dev --save-exact
위 명령어는 아래 3가지를 수행한다.
cz-conventional-changelog
어댑터를 설치한다.- 그리고 dev dependency에 추가한다.
- 아래처럼
config.commitizen
을package.json
에 추가한다.
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
}
이렇게 설정하면, 커밋하려고 할 때 사용할 어댑터를 commitizen에게 알려준다.
commitizen script 추가하기
package.json
에 아래와 같은 script 를 추가한다.
husky와 함께 쓰는 경우는 commit 명령어로 쓰지 말라는 경고가 있으니 주의하자.
"scripts": {
...
"cm": "cz"
}
npx cz
or
npm run cm
위 명령어를 통해 commitizen으로 커밋을 정형화된 포맷으로 할 수 있다.
그러나, git commit 명령어를 실행시켰을 때, commitizen 을 먼저 실행시킬 수 있도록 설정한다면, 굳이 이 명령어를 쓰지 않아도 된다.
커밋 시 commitzen 실행하도록 husky에 훅 추가하기
npx husky add .husky/prepare-commit-msg 'exec < /dev/tty && node_modules/.bin/cz --hook || true'
commitizen 으로 커밋하기
git commit
- 원하는 type 선택하기
- 필요한 설명 입력하기
- 이슈에 연결하기(선택사항)
- 결과 확인
5. commitizen 어댑터 변경하기 (선택사항)
엇, 그런데 commitizen 에서 제공하는 어댑터 중에 commitlint 를 사용하는 것이 있다는 것을 뒤늦게 알아버렸다.
위에 commit-msg 훅에서 commitlint를 사용하고 있어서, 일관성 유지에는 commitlint를 사용하는 것이 좋을 것 같아서 commitlint 어댑터로 바꿔보겠다.
기존 어댑터 삭제하기
위에서 추가해준 cz-conventional-changelog 어댑터를 삭제해주자.
npm uninstall -D cz-conventional-changelog
새로운 어댑터 추가하기
npm i -D @commitlint/prompt
commitizen 어댑터 설정 변경하기
아래처럼 package.json
에서 config.commitizen
을 변경해주자.
"script": {
...
"cm": "git-cz"
},
"config": {
"commitizen": {
"path": "./node_modules/@commitlint/prompt"
}
}
변경된 어댑터 동작 확인하기
git commit
전혀 다른 방식으로 커밋이 진행되는 것을 확인할 수 있다.
마치며.
마지막에 바꾼 어댑터.
써보니까 너무 별로여서 다시 기존 어댑터로 롤백했다.
ㅎㅎ..
그로부터 며칠 뒤.prepare-commit-msg
훅을 뻈다.
이제 대충 커밋 컨벤션이 머리에 있는데 저 과정으로 커밋하는게 영 불편했다.
그래서 필요한 경우에는 npm run cm
으로 실행시키는 것으로.
References
https://www.huskyhoochu.com/npm-husky-the-git-hook-manager/
https://git-scm.com/book/ko/v2/Git맞춤-Git-Hooks
https://blog.cookapps.io/guide/conventional-commits/
https://www.conventionalcommits.org/ko/v1.0.0/
https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines