배경
너무 하고 싶었던건데 드디어 했다. 정규표현식이라 복잡해보여서 미뤄두고 있었는데 생각보다 금방 했다.
husky에서 pre-commit만 사용하긴 아까워서 다른 hook들에 대해 찾아보다가 prepare-commit-msg를 알게 되었다.
커밋할 때 템플릿을 띄워주면 작성할 때 참고할 수 있어 편한데, git config처럼 개인이 선택적으로 사용하는 방법 말고 좀 더 강제적인 방법이 필요했다.
prepare-commit-msg 파일 생성은 했는데 정규표현식에 부딪혀서 미뤄두다가(과제에 다른 할 일들이 우선이라) 이번에 해결했다. 어렵지 않다!
결과
자동으로 브랜치명(#이슈번호)의 형태로 커밋을 생성해준다.
아래에 커밋 형식 작성 템플릿을 띄워준다.
prepare-commit-msg
실행 시점
- Git이 commit 생성 → prepare-commit-msg 실행 → 편집기 실행
- 사용자가 커밋 메세지를 수정하려고 할 때
Argument
커밋 메시지가 들어 있는 파일의 경로, 커밋의 종류를 아규먼트로 받는다.
최근 커밋을 수정할 때는(Amending 커밋) SHA-1 값을 추가 아규먼트로 더 받는다.
용도
일반 커밋에는 별로 필요 없고 커밋 메시지를 자동으로 생성하는 커밋에 좋다. 커밋 메시지에 템플릿을 적용하거나, Merge 커밋, Squash 커밋, Amend 커밋일 때 유용하다. 이 스크립트로 커밋 메시지 템플릿에 정보를 삽입할 수 있다.
prepare-commit-msg 파일 작성
husky 설치
npm husky-init && npm install
prepare-commit-msg 파일 생성
직접 생성한다.
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
git commit 명령이 들어온 후 prepare-commit-msg 파일이 실행되면 husky.sh을 실행시키는 코드다.
코드 작성하고 git commit으로 명령을 주면 자동으로 위와 같은 화면이 콘솔 또는 터미널에 생성된다.
그냥 나가면 커밋 메세지를 작성하지 않은 것으로 여겨 커밋이 취소된다.
정규표현식 작성
브랜치 이름에서 타입과 이슈번호 추출하기
grep과 sed만 이용하면 된다.
grep은 추출을 하고 sed로 잘라먹는다. (sed는 streamlined editor 줄임말이며 쉘 명령어)
git branch를 입력하면 현재 브랜치에 *표시가 있다.
$ git branch
dev
* docs/25
grep으로 *표시가 붙은 현재 브랜치를 골라낸다.
$ git branch | grep '\*'
* docs/25
sed로 맨 앞의 *표시를 제거한다.
$ git branch | grep '\*' | sed 's/\* //'
docs/25
s 옵션은 치환을 담당하며 /로 구분한다.
s/old/new/g의 형태로 사용하는데, new 부분이 비어있으므로 * 부분을 삭제하게 된다.
/이슈번호 이전의 문자열만 남겨 타입 문자열을 얻는다.
$ git branch | grep '\*' | sed 's/\* //' | sed 's/\([^/]*\).*/\1/'
docs
/문자를 제외한 모든 문자열로 그룹을 만들어 이 그룹 이외의 문자열을 모두 지운다.
정규표현에서 ()을 쓰면 그 내용을 \1로 저장할 수 있음
[]내부에서의 ^은 not을 의미함. [^/]은 /로 시작하지 않는 부분 중 첫 글자.
[^/]*은 / 앞부분 전부에 해당
()은 그룹화하여 버퍼에 저장할 수 있다.
([^/]*)은 feature 부분을 그룹화한 것
첫번째 필드를 가져오고 다른 정보는 버린다.
([^/]*).*은 ([^/]*)로 시작하는 모든 패턴을 의미
라인 맨앞부터
$ git branch | grep '\*' | sed 's/* //' | sed 's/^.*\///'
25
^.*\/은 /앞의 모든 문자열을 말하며 sed로 인해 빈문자열로 치환된다.
따라서 25만 남는다.
echo "$TYPE(#$ISSUENUMBER): $(cat "$COMMIT_MESSAGE_FILE_PATH")" > "$COMMIT_MESSAGE_FILE_PATH"
echo "$(cat .gitmessage.txt)" >> "$COMMIT_MESSAGE_FILE_PATH"
최종 prepare-commit-msg
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# 표준출력(현재 쉘을 실행한 콘솔이나 터미널)을 변수에 저장
COMMIT_MESSAGE_FILE_PATH=$1
COMMIT_SOURCE=$2
COMMIT_AMEND=$3
# merge 할 때 type/issue 다시 생성되지 않게 함
if [ "${COMMIT_SOURCE}" = merge ];then
exit 0
fi
# amend 할 때 type/issue 다시 생성되지 않게 함
if [ "${COMMIT_AMEND}" = HEAD ];then
exit 0
fi
# grep으로 현재 브랜치 긁어오고 sed로 자름
TYPE=$(git branch | grep '*' | sed 's/* //' | sed 's/\([^/]*\).*/\1/')
ISSUENUMBER=$(git branch | grep '*' | sed 's/* //' | sed 's/^.*\///')
echo "$TYPE(#$ISSUENUMBER): $(cat "$COMMIT_MESSAGE_FILE_PATH")" > "$COMMIT_MESSAGE_FILE_PATH"
echo "$(cat .gitmessage.txt)" >> "$COMMIT_MESSAGE_FILE_PATH"
- 'git merge'이렇게 2번째 argument에 merge가 들어오면 merge commit에 type/issue가 다시 생성되지 않게 한다.
- 'git commit --amend' 이렇게 들어올 때는 --amend가 HEAD로 나타나는데, 아마 amend가 전에 커밋을 변경하는 거라 쉘에서는 바로 HEAD로 입력되는 듯 하다. 그래서 HEAD와 비교해주었다.
- type(#issue_number)식으로 생성된 후 원본 prepare-commit-msg를 아래에 추가해준다.
- 그 밑에 우리 팀이 정한 규칙이 들어있는 .gitmessage.txt파일을 아래에 출력해주었다.
- merge는 if 문 써주기 전에도 밑에 gitmessage부분이 생겼다 안생겼다해서 정확히 확인이 안 된다.
- amend는 확실히 확인했다. 정상 작동한다.
참고
1. 적용 레포
https://github.com/su-mmer/JBNU_OSS_PROJECT
2. Git Document
https://git-scm.com/book/ko/v2/Git%EB%A7%9E%EC%B6%A4-Git-Hooks
3. git branch 이름과 hook으로 commit message 컨벤션 강제하기
https://blog.deering.co/commit-convention/