Husky

Kay·2023년 7월 18일
0

개인 프로젝트

목록 보기
6/6

코드스테이츠에서 처음으로 진행하는 개인 프로젝트 쇼핑몰과 비슷하지만 매우 간단한 기능만을 가지고 있는 프로젝트인 fe-sprint-coz-shopping 를 일주일간 진행하였다.

이번 프로젝트에 git flow를 적용하기로 하여 다른 분들께 git flow에 대해 설명할 겸 간단히 흐름을 그려보았다.

feature에서 작업이 끝나면 PR을 올려 origin/develop 에 머지하고,
develop 브랜치에 체크아웃하여 origin/develop의 변경사항을 pull 하는 과정이다.

어느 날 한 분이 하나의 질문을 해주셨는데 "실수로 origin/main에 merge 했는데 어떡하죠?" 였다.

실제로 나도 전 프로젝트에 git flow를 적용하여 작업을 하다가 실수로 origin/main 브랜치에 머지한 적이 있었다.

물론 개인 프로젝트에서는 바로 revert하고 진행하면 큰 문제가 되지 않지만,

  • 만약 실무 상황에서 upstream/main 브랜치에 push가 되는 순간 CI/CD가 동작하도록 세팅되어 있다면..?
  • origin/main에 머지한 순간 깨닫지 못하고 한참 다른 작업과 머지를 반복한 후에야 깨닫게 된다면..?
    상상만 해도 너무 무서운 상황이었다.

그래서 Git 이벤트가 발생했을 때 특정 스크립트를 실행하는 git hook과 husky에 대해 학습하기 시작했다.

git hook과 husky

git hook은 Git 이벤트가 발생했을 때 특정 스크립트를 실행하는 것을 의미하며, husky는 git hook을 쉽게 사용할 수 있도록 도와주는 라이브러리이다.

git 이벤트와 git hook의 단계는 다음과 같다.

출처: husky 로 git hook 하자

husky

🐶 husky 공식 문서

1. 설치하기

npx husky-init && npm install

2. package.json에 prepare 스크립트 추가하기

prepare 스크립트는 패키지가 패킹 되기 전에 실행되는 스크립트로 npm publish, npm pack 의 스크립트가 실행될 때, 로컬에서 파라이터 없이 npm install 스크립트가 실행될 때 호출됩니다.

"scripts": {
	...
    "prepare": "husky install",
}

3. sample pre-commit hook 추가하기

이번 프로젝트는 vite로 세팅하였고, vite의 기본 세팅에 eslint 검사를 위한 스크립트가 이미 추가 되어 있었다.
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",

커밋하기 전 npm run lint을 해서 문제가 없을 경우에만 커밋하고 싶기 때문에 npm run lint를 뒤에 붙여 pre-commit hook을 추가해주었다.

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

그러면 .husky/pre-commit 경로의 파일에 다음과 같은 내용이 담기게된다.

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

npm run lint

그리고 이후 커밋마다 lint 검사를 하고 에러가 없을 경우에만 커밋을할 수 있도록 한다.

* Troubleshooting

현재 m2 air 에서 nvm과 sourcetree를 사용하고 있었다.
sourcetree에서 테스트 커밋을 하려던 중 npm: command not found 에러와 만났다.

누군가 자세한 이유를 작성해주셔서 이 글을 통해 이유를 대충 알 수 있었다.

Sourcetree(osx)에서 Husky(git-commit-hook)가 동작하지 않는 이유: Can’t find “…” in PATH

요약하면 경로 문제였다.

공식 문서에서도 Troubleshooting의 첫 문제로 command not found를 정리하였다. Command not found 해결방법

  1. ~/.huskyrc 에 nvm의 경로를 링크를 참고하여 적는다.
  2. 소스트리를 커맨드라인을 이용하여 연다.
    • 소스트리를 열고싶은 경로에서 stree

* 상업용 프로젝트에서 사용하면 안되는 v5

Husky 사용할 때 주의! v5 버전 릴리스와 라이선스 정책 변경

husky에 대해 찾아보다 위와 같은 글을 읽었는데 현재는 MIT 라이선스를 가진 상황이어서 v5를 제외한 버전은 걱정할 필요가 없다고 했다.

하지만 이 글을 통해 라이브러리 사용 전 라이선스를 꼭 확인해야겠다는 생각을 하게되었다.

pre-commit

pre-commit 에 적용하고 싶었던 내용은 다음과 같다.

  • eslint 체크
  • 브랜치 체크 -> develop 또는 main 브랜치일 경우 커밋 불가

따라서 위 세팅을 통해 만들어진 파일 아래 현재 체크아웃된 브랜치와 커밋하면 안되는 브랜치를 비교하여 일치할 경우 에러를 내는 코드를 추가하였다.

변수

  • BRANCH: 현재 체크아웃된 브랜치
  • PROTECTED_BRANCHES: 커밋하면 안되는 브랜치
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run lint

# 현재 체크아웃된 브랜치
BRANCH=`git rev-parse --abbrev-ref HEAD`
# 커밋하면 안되는 브랜치
PROTECTED_BRANCHES="^(master|main|develop)"

echo "current branch: $BRANCH"
echo "protected branches: $PROTECTED_BRANCHES"

# 문자열을 비교하기 위한 문법
if [[ "$BRANCH" =~ $PROTECTED_BRANCHES ]]
then
  echo -e "main 또는 develop에 commit할 수 없습니다."
  echo -e "\n🚫 Cannot commit to local $BRANCH branch" && exit 1
fi

exit 0

pre-push

pre-push에서 적용하고 싶은 내용은 다음과 같았다.

  • main 또는 develop 브랜치에 push 불가

변수

  • BRANCH: 현재 체크아웃된 브랜치
  • PROTECTED_BRANCHES: 푸시하면 안되는 브랜치
  • LOCAL_REF, REMOTE_REF: 푸시하려는 브랜치

내 코드의 한계점

아래 주석으로 적었지만 BRANCH에서 BRANCH로 푸시할 때만 LOCAL_REF, REMOTE_REF 변수의 값을 알 수 있었다.

즉, feature/13에 체크아웃되어 있는 상태에서 develop 브랜치를 push하려고 한다면 LOCAL_REF, REMOTE_REF 정보가 뜨지 않았고
feature/13에 체크아웃되어 있는 상태에서 feature/13에 push할 때만 LOCAL_REF, REMOTE_REF 정보가 떴다.

따라서 "체크아웃된 브랜치에서 해당 브랜치만 Push하는지 확인하는 조건문"을 추가하여 체크아웃된 브랜치 외의 브랜치를 push하는 것을 막았으나 이는 그리 좋은 코드는 아닌 것 같다.

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

# 출처: https://typicode.github.io/husky/migrating-from-v4.html
# 출처: https://library.gabia.com/contents/8492/
# 출처: https://stackoverflow.com/a/63322891
# 출처: https://github.com/typicode/husky/issues/1052

LOCAL_REF=""
REMOTE_REF=""

# 체크아웃된 브랜치에서 체크아웃된 브랜치만 push할 때 변수를 감지함
if read local_ref local_oid remote_ref remote_oid; then
  echo "local_ref $local_ref"
  LOCAL_REF="$local_ref"
  echo "remote_ref $remote_ref"
  REMOTE_REF="$remote_ref"
fi

# 체크아웃된 브랜치
BRANCH=`git rev-parse --abbrev-ref HEAD`
# push하면 안되는 브랜치
PROTECTED_BRANCHES="^(main|master|develop)"

# 문자열을 비교하기 위한 문법
if [[ "$BRANCH" =~ $PROTECTED_BRANCHES ]]
then
  echo -e "main 또는 develop에 push할 수 없습니다. feature 브랜치로부터 PR을 만들어 제출해주세요."
  echo -e "\n🚫 Cannot push to remote $BRANCH branch, please create your own branch and use PR." && exit 1
fi

echo "current branch: $BRANCH"
echo "protected branches: $PROTECTED_BRANCHES"
echo "remote name: $1"
echo "git url: $2"

# 체크아웃된 브랜치에서 해당 브랜치만 Push하는지 확인하는 조건문
if [[ $LOCAL_REF != *"$BRANCH"* ]]
then
  echo -e "체크아웃한 브랜치만 push해주세요."
  echo -e "\n🚫 You must use (git push origin $BRANCH)" && exit 1
fi

exit 0

1개의 댓글

comment-user-thumbnail
2023년 7월 18일

글 잘 봤습니다, 많은 도움이 되었습니다.

답글 달기