깃훅 가이드
dictionary-api는 husky 없이, scripts/ 디렉토리의 bash 스크립트를 Gradle 태스크로 .git/hooks/에
직접 동기화하는 방식을 쓴다.
훅 설치 방식
Section titled “훅 설치 방식”scripts/pre-commit,scripts/commit-msg파일을.git/hooks/로 그대로 복사하는 Gradle 태스크를 등록한다.build.gradle tasks.register('updateGitHooks', Copy) {from file('scripts')into file('.git/hooks')filePermissions {unix(0755)}}- 빌드(
compileJava)할 때마다 이 태스크가 자동 실행돼, 훅 스크립트가 바뀌어도 별도 설치 명령 없이 최신 상태로 동기화된다.build.gradle tasks.named('compileJava') {dependsOn 'spotlessApply'dependsOn 'updateGitHooks'dependsOn 'configureGitMerge'} - 같은 시점에
configureGitMerge태스크가 로컬 저장소에git config merge.ff false를 적용한다. fast-forward가 가능한 상황에서도git merge가 병합 커밋을 남기게 하려는 설정이다.그래서 원격 동기화는build.gradle tasks.register('configureGitMerge', Exec) {commandLine 'git', 'config', 'merge.ff', 'false'}git pull --ff-only처럼 항상--ff-only로 한다.git-*스킬은 이 설정에 기대지 않고--ff-only/--no-ff를 직접 명시한다.
pre-commit — 브랜치 보호 + 포맷 검사
Section titled “pre-commit — 브랜치 보호 + 포맷 검사”#!/usr/bin/env bashset -euo pipefail
# master 브랜치는 작업 브랜치를 병합(git merge)하는 용도로만 쓴다 — 여기에 직접 커밋하는 것(일반 커밋,# --amend, cherry-pick, revert 등)은 막는다. 병합 커밋은 .git/MERGE_HEAD 존재 여부로 구분한다.current_branch=$(git symbolic-ref --short -q HEAD || true)merge_head="$(git rev-parse --git-path MERGE_HEAD 2>/dev/null || true)"if [[ "$current_branch" == "master" ]] && { [[ -z "$merge_head" ]] || [[ ! -f "$merge_head" ]]; }; then echo "master 브랜치에는 직접 커밋할 수 없습니다." >&2 exit 1fi
if ! ./gradlew spotlessCheck --quiet; then echo "Spotless 포맷 검사에 실패했습니다." >&2 echo " './gradlew spotlessApply' 실행 후 다시 커밋하세요." >&2 exit 1fiMERGE_HEAD가 없으면서 현재 브랜치가master면 즉시 차단한다 — 일반 커밋뿐 아니라--amend,cherry-pick,revert도 여기 걸린다.pre-merge-commit훅이 없으면git merge가 이pre-commit훅으로 대체 실행되므로, 병합 자체는MERGE_HEAD가 있어 통과한다../gradlew spotlessCheck로 코딩 컨벤션의 포맷 규칙을 검사한다 — 실패하면spotlessApply실행을 안내하고 커밋을 막는다.- 이 훅은 로컬 저장소에서만 작동한다 — 다른 세션의 원격 직접 push나 GitHub PR 병합(원격에서 커밋이 만들어짐)까지는 막지 못한다.
- AI가
master에서 커밋 명령을 실행하는 것은 이 git 훅과 별도로 Claude Code 훅(.claude/hooks/)이 명령 실행 전에 한 번 더 막는다(깃 브랜치 전략 참고).
commit-msg — Conventional Commits 형식 검증
Section titled “commit-msg — Conventional Commits 형식 검증”#!/usr/bin/env bashset -euo pipefail
commit_msg_file="$1"subject_line=$(grep -v '^#' "$commit_msg_file" | grep -v '^$' | head -n1)
# Merge 커밋은 Git이 자동 생성하므로 검사에서 제외한다.if [[ "$subject_line" =~ ^Merge ]]; then exit 0fi
pattern='^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-zA-Z0-9_./-]+\))?!?: .+'
if [[ ! "$subject_line" =~ $pattern ]]; then echo "커밋 메시지가 Conventional Commits 형식이 아닙니다." >&2 echo " 형식: <type>(<scope>): <subject>" >&2 exit 1figit commit -m "제목"으로 만든 메시지의 첫 줄(주석·빈 줄 제외)만 검사한다.Merge로 시작하는 줄은 검사를 건너뛴다 —git merge가 자동으로 만드는 병합 커밋 메시지 (Merge branch '...')를 예외 처리하기 위함이다.@commitlint/cli같은 npm 도구 대신 정규식 하나로 직접 검증한다 — Node.js 의존성 없이 순수 bash로 동작한다. (dictionary-app은 커밋 메시지 검사 도구를 두지 않는다.)- 요약 부분의 50자 제한과 한글 작성 규칙은 이 훅이 검사하지 않는다 — 형식(
type(scope):)만 확인한다.