Git 워크플로우 및 커밋/푸시 컨벤션 설정 (husky + commitizen)
👀 Git 워크플로우 도입 배경
팀 프로젝트를 진행하면서 여러 가지 불편사항을 경험했다.
첫째, 팀원 간의 커밋 메시지 형식이 일관되지 않아 변경 사항을 추적하기가 어려웠다. 둘째, 코드 품질 관리가 체계적이지 않아 린트와 스타일 가이드라인을 준수하지 않은 코드가 종종 커밋되었다. 마지막으로, 기본 브랜치(main)에 실수로 직접 푸시하거나 PR(Pull Request) 과정이 누락되는 문제가 있었다.
이러한 문제를 해결하기 위해 Git 워크플로우를 도입하기로 결정하였다. Git 워크플로우란 Git에서 브랜치와 커밋, 푸시 등의 작업을 체계적으로 관리하는 방법을 말한다. 이를 통해 코드 품질과 협업 효율성을 높이고, 실수를 줄일 수 있을 것이라 판단했다. 이를 구현하기 위해 Husky와 Commitizen을 사용하기로 했다.
Husky는 Git의 hook 기능을 활용하여 커밋, 푸시 등의 Git 이벤트에 특정 작업을 자동으로 실행할 수 있도록 해준다. Git의 hook은 Git 명령 실행 시 특정 이벤트에서 자동으로 실행되는 스크립트이다. 이를 통해 특정 작업(예: 코드 검사, 테스트 실행 등)을 자동화하여 워크플로우의 효율성을 높일 수 있다. 예를 들어, 커밋 전에 린트 검사를 실행하거나 푸시 전에 테스트를 실행할 수 있다.
Commitizen은 표준화된 커밋 메시지 작성 도구이다. Conventional Commits와 같은 규칙을 기반으로 대화형 프롬프트를 제공하여 커밋 메시지가 일관성을 유지하도록 돕는다. 이를 통해 릴리즈 노트 생성과 버전 관리가 더욱 체계적으로 이루어진다.
1. Husky 설정
Husky는 Git의 hook을 활용하여 다양한 작업을 자동화하는 도구이다. 이를 통해 커밋 전 lint 검사, push 전 테스트 실행 등의 작업을 자동으로 수행할 수 있다.
1. husky를 설치한다.
yarn add -D husky
yarn husky init
위 명령어를 실행하면 package.json에 "prepare" : "husky" 명령어가 가 추가되며 프로젝트에 husky 폴더가 생성된다. 이는 Git의 hook 스크립트를 관리하기 위한 디렉토리이다.
2. Pre-commit 설정
Pre-commit hook은 커밋 직전에 실행되는 스크립트를 정의하는 데 사용된다.
여기서는 lint-staged를 활용하여 스테이징된 파일에 대해 Lint 및 Prettier를 자동으로 실행하도록 설정할 것이다.
1. lint-staged를 설치한다.
yarn add -D lint-staged
2. package.json에 다음과 같은 설정을 추가한다.
"lint-staged": {
"*.{js,ts,tsx,json}": [
"eslint --fix",
"prettier --write"
]
}
이 설정은 스테이징 단계인 파일 중에서 JavaScript, TypeScript 파일에 한해 eslint와 prettier를 실행하도록 한다. 이 작업은 커밋 전에 코드의 일관성과 품질을 유지하는 데 도움을 준다.
3. Husky의 pre-commit hook을 설정한다.
// 🚫 husky - add command is DEPRECATED
npx husky add .husky/pre-commit "npx lint-staged"
위 명령어를 실행하면 .husky/pre-commit 파일이 생성되며, 해당 파일은 커밋 전에 lint-staged를 실행하는 역할을 한다.
pre-commit 파일에 아래 명령어를 직접 입력해도 된다.
🚨 + 수정 : husky - add command is DEPRECATED
husky가 버전 업데이트를 하면서 더이상 add 명령어를 사용할 수 없게 되었다.
따라서 npx husky add 대신 아래 명령어를 실행하면 된다.
echo "yarn lint-staged" > .husky/pre-commit && chmod +x .husky/pre-commit
chmod +x 는 실행 권한을 추가해줌으로써 Git Hook이 동작하게 만드는 것이다.
3. Commitizen 및 cz-customizable 설정
Commitizen은 표준화된 형식으로 커밋 메시지를 작성하도록 돕는 도구이다. 주로 Conventional Commits 규칙을 기반으로 하며, 대화형 커밋 작성 과정을 제공한다. 이로 인해 커밋 메시지의 일관성을 유지하고 릴리즈 노트를 자동 생성할 수 있는 기반을 마련한다.
1. 패키지를 설치한다.
yarn add -D commitizen cz-customizable
2. 프로젝트 루트 경로에 .czrc 파일을 생성하고 다음 내용을 추가한다.
{
"path": "cz-customizable",
"config": "./.cz-config.js"
}
이 파일은 Commitizen이 cz-customizable 플러그인을 사용하도록 설정한다.
3. .cz-config.js 파일을 생성하고 다음과 같이 작성한다.
const execSync = require('child_process').execSync;
// 현재 브랜치 이름 가져오기
const getBranchName = () => {
try {
const branchName = execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
return branchName;
} catch (error) {
return ''; //브랜치 이름을 못가져왔을때 기본값
}
};
module.exports = {
types: [
{value: `feat(${getBranchName()})`, name: '✨ Feat: 새로운 기능 추가'},
{value: `style(${getBranchName()})`, name: '💄 Style: 스타일 수정'},
{value: `refactor(${getBranchName()})`, name: '🤖 Refactor: 리팩토링'},
{value: `chore(${getBranchName()})`, name: '🛠️ Chore: 설정 파일 변경'},
{value: `test(${getBranchName()})`, name: '✅ Test: 테스트 코드 수정'},
],
scopes: [],
allowCustomScopes: false,
allowBreakingChanges: ['feat', 'fix'],
skipQuestions: ['body', 'scope'],
};
이 설정은 브랜치 이름을 자동으로 커밋 메시지에 포함시켜, 현재 작업 중인 브랜치를 명시적으로 나타낼 수 있다.
4. Husky의 prepare-commit-msg hook에 Commitizen을 설정한다.
// Deprecated ⛔️
npx husky add .husky/prepare-commit-msg "exec < /dev/tty && npx cz --hook || true"
이후 git commit 명령을 실행하면 대화형 커밋 메시지 작성 화면이 나타난다.
prepare-commit-msg 파일에 아래 명령어를 직접 입력해도 된다.
exec < /dev/tty && npx cz --hook || true
🚨 + 수정 : husky - add command is DEPRECATED
마찬가지로 위 명령어 대신 아래 명렁어를 추가한다.
echo "exec < /dev/tty && npx cz --hook || true" > .husky/prepare-commit-msg && chmod +x .husky/prepare-commit-msg
실패하더라도 오류를 무시하도록 || true 를 추가해 주었다.
설정을 완료한 이후 git add . git commit 하게되면
이런 화면이 나오게 된다. 이렇게 되면 커밋 컨벤션 설정이 완료된 거다.
4. Pre-push 설정
Pre-push hook은 Git push 명령이 실행되기 전에 수행할 작업을 정의한다. 주요 목적은 중요한 브랜치 보호 및 필요한 검증 작업 실행이다.
1. .husky/pre-push 파일을 생성하고 다음 내용을 작성한다.
BRANCH=$(git rev-parse --abbrev-ref HEAD) # 현재 브랜치 이름 가져오기
PROTECTED_BRANCHES="^(main)" # 보호 브랜치 설정
# 보호 브랜치에 직접 푸시를 방지
if [[ "$BRANCH" =~ $PROTECTED_BRANCHES ]]
then
echo "🚫 Push to protected branches ($BRANCH) is prevented."
exit 1
fi
# 스테이징된 파일 중 특정 확장자가 있는지 확인
STAGED_FILES=$(git diff --cached --name-only -- '*.ts' '*.tsx')
BASE_BRANCH="main" # 기본 브랜치 이름
# PR이 이미 존재하는지 확인
EXISTING_PR=$(gh pr list --head "$BRANCH" --json number --jq '.[0].number')
if [ -n "$EXISTING_PR" ]; then
echo "⚠️ Pull Request already exists for branch: $BRANCH (#$EXISTING_PR)"
exit 0
fi
# PR 템플릿 생성
cat > /tmp/pr-template.md << EOF
## PR 제목
${CURRENT_BRANCH}: 새로운 기능 추가
## 설명
이 PR은 ${CURRENT_BRANCH} 브랜치의 내용을 ${BASE_BRANCH}로 병합합니다.
## 변경 사항
- 변경사항 1
- 변경사항 2
## 체크리스트
- [ ] 테스트 완료
- [ ] 코드 리뷰
EOF
# Pull Request 생성
echo "Creating a Pull Request for branch: $BRANCH"
gh pr create \
--base "$BASE_BRANCH" \
--head "$BRANCH" \
--title "[$BRANCH]: " \
--body-file "/tmp/pr-template.md" \
--web
만약 push할 때 PR이 없다면 이런 PR을 생성할 것이라고 룰을 정하는 것이다.
5. Post-merge 설정
Post-merge hook은 git pull 이후 실행된다. 주된 목적은 의존성 관련 파일이 변경된 경우 적절한 설치 명령을 자동으로 실행하는 것이다.
1. .husky/post-merge 파일을 생성하고 다음 내용을 작성한다.
CHANGED_FILES=$(git diff --name-only HEAD@{1} HEAD)
if echo "$CHANGED_FILES" | grep -q '^yarn.lock$'; then
echo "yarn.lock 의존성 변경 감지! yarn install 실행..."
yarn install
fi
if echo "$CHANGED_FILES" | grep -q '^ios/Podfile.lock$'; then
echo "podfile.lock 의존성 변경 감지! pod install 실행..."
cd ios && pod install && cd ..
fi
이 설정은 다른 작업자가 의존성 파일을 변경한 경우, yarn 과 pod install을 자동으로 해주므로 다른 작업자가 lock file이 바뀌는 작업을 했을 때 자동으로 맞춰지게 된다.
이렇게 말이다.
위 과정을 통해 Git 워크플로우를 체계화하고, 팀 프로젝트에서의 작업 효율성과 일관성을 극대화하였다. 각 단계에서 Husky, lint-staged, Commitizen 등 다양한 도구가 조화롭게 사용되며, 작업 생산성을 높일 수 있었다.