커밋 전에 자동으로 막아주는 최소한의 안전장치가 필요할 때
팀에서 코드 리뷰를 해도 사소한 포맷팅, 린트, 테스트 누락이 자주 새어 나갑니다. pre-commit + lefthook로 커밋 전에 자동 품질 게이트를 두면 이런 누락을 사전에 차단할 수 있습니다.
pre-commit은 깃 커밋 직전에 실행할 검사 작업을 손쉽게 묶는 도구이고, lefthook은 여러 훅과 언어별 스크립트를 빠르게 병렬 실행하도록 돕는 러너입니다. 두 도구를 함께 쓰면 린트, 타입체크, 테스트, 시크릿 키 유출 검사까지 커밋 단위로 자동화할 수 있습니다.
왜 지금 필요한가를 간단히 보면 다음과 같습니다. - 원격 CI로 올라간 뒤 실패하는 비용보다, 로컬에서 즉시 실패시키는 편이 시간과 실수를 줄입니다.
- 합의된 규칙을 사람 대신 도구가 강제해 리뷰 품질이 설계와 로직에 집중됩니다. - 신입·외주 인력이 섞여도 동일한 개발 기준을 일관되게 유지합니다.
> 안티패턴: 모든 검사를 커밋 훅에 몰아 넣으면 훅 시간이 길어집니다. 변경 파일만 검사하거나 빠른 체크는 pre-commit, 느린 통합 테스트는 CI로 분리하는 것이 좋습니다.
pre-commit과 lefthook을 한눈에 이해하기
pre-commit은 깃의 “커밋 직전” 훅에 린트·포맷·테스트 같은 검사를 묶어 실행하는 설정 모음입니다. lefthook은 그 설정을 빠르고 병렬로 돌려 커밋 대기 시간을 줄이는 실행기(러너)입니다.
작동 흐름은 간단합니다: 개발자 커밋 시도 -> pre-commit 규칙 확인 -> lefthook이 작업 병렬 실행 -> 실패 항목이 있으면 커밋 차단. 성공하면 그대로 커밋이 진행됩니다.
초보자에게 유용한 이유는 사람이 놓치기 쉬운 체크를 “키보드 한 번”에 자동으로 거치게 만들기 때문입니다. 예를 들어 ESLint, Prettier, Jest, 시크릿 스캔을 한 번에 적용할 수 있습니다.
실무 예시는 다음과 같습니다.
- 프론트엔드: Prettier로 포맷 → ESLint로 규칙 위반 차단 → TypeScript 타입체크
- 백엔드: Black/Flake8 또는 golangci-lint → 단위 테스트 샘플 실행
- 공통: .env 유사 파일에서 토큰·키 문자열 패턴 스캔
- 모노레포: 변경된 패키지만 선택 실행해 대기 시간을 단축
> 안티패턴: 모든 테스트와 빌드를 커밋 훅에 얹어 1~2분씩 지연시키는 구성. 빠른 피드백이 핵심이므로, 무거운 통합 테스트는 CI로 넘기고 훅에는 “빠르고 결정적인” 검사를 남겨두는 편이 안전합니다.
빠른 설정과 최소 체크리스트
기본 품질 게이트는 작은 단계부터 시작하는 것이 안전합니다. 필수만 먼저 적용하고, 속도가 괜찮으면 점차 늘리세요.
- 도구 설치: Python은 pre-commit을 pipx/pip로 설치, Node/PNPM은 lefthook을 devDependencies에 추가
- 초기화: pre-commit init으로 훅 연결, 이어서 lefthook install 실행
- 규칙 작성: .pre-commit-config.yaml에 검사 정의, lefthook.yml에 순서·병렬 설정
- 테스트: pre-commit run --all-files로 점검, 실패 시 규칙을 조정하거나 코드를 수정
- 확장: 포맷·린트 통과 후 타입체크, 이후 테스트·시크릿 스캔 추가
아래는 프론트엔드(Prettier, ESLint, TypeScript) 최소 예시입니다. 같은 구조로 파이썬/고 린터로 바꿔 쓸 수 있습니다.
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: prettier
name: prettier
entry: npx prettier --check
language: system
files: "\\.(ts|tsx|js|jsx|json|md)$"
- id: eslint
name: eslint
entry: npx eslint
language: system
files: "\\.(ts|tsx|js|jsx)$"
- id: tsc
name: typescript
entry: npx tsc --noEmit
language: system
files: "\\.(ts|tsx)$"
핵심은 local 리포에 npx 명령을 묶고, 커밋 대상만 검사하는 것입니다. 파일 패턴을 좁혀 불필요한 실행을 줄이세요.
# lefthook.yml
pre-commit:
parallel: true
commands:
prettier:
run: npx prettier --check {staged_files}
eslint:
run: npx eslint {staged_files}
tsc:
run: npx tsc --noEmit
pre-commit이 훅을 걸고 lefthook이 병렬로 실행해 대기 시간을 줄입니다. tsc처럼 전체 스캔은 시간이 더 듭니다.
> 팁: 느린 작업은 pre-push로 옮기고, pre-commit에는 포맷·린트만 두세요. 테스트는 변경 파일만 돌리거나 캐시를 쓰면 더 빨라집니다.
흔한 실수와 선택 기준 한 번에 정리
초기 도입에서 가장 흔한 실수는 “검사를 너무 많이 넣는 것”입니다. 커밋 훅은 빠르게 끝나야 하며 5~10초 내를 목표로 필수 항목만 남기세요.
- 커밋 훅에 테스트 전체 실행 넣기 → 변경 파일 기준의 빠른 테스트 또는 CI로 이관
- 동일한 포맷터/린터를 pre-commit과 에디터에서 중복 실행 → 한 경로만 권장
- 모든 파일을 매번 검사 → 변경 파일만 검사하고, 주기적으로 전체 검사 스크립트 별도 운영
속도 이슈가 잦다면 러너 선택을 점검하세요. pre-commit 단독은 간단하고, lefthook은 병렬 실행과 캐시로 대기 시간을 줄입니다.
팀 합류 장벽이 낮아야 한다면 pre-commit 중심으로 시작하고, 대형 저장소나 다국어 프로젝트라면 lefthook을 함께 두는 편이 낫습니다.
선택 기준은 목적과 비용을 맞추는 것입니다. 린트·포맷 자동화가 목적이면 규칙 수를 줄이고, 타입체크·시크릿 스캔까지 필요하면 병렬화와 파일 필터를 세밀히 설정합니다.
> 안티패턴: 느린 훅을 “--no-verify”로 우회하도록 방치하면 제도만 있고 실효성은 사라집니다. 병목을 찾아 비활성화·분리·병렬화 중 하나로 해결하세요.
지금 바로 적용: 최소 설정으로 시작하고 점진 확장
핵심은 커밋 전에 실패를 빨리 드러내는 것입니다. pre-commit + lefthook로 린트·포맷부터 가볍게 걸고, 속도를 보며 타입체크·시크릿 검사까지 확장하세요.
바로 실행하려면 아래 순서로 진행하세요. 각 단계는 저장소 루트에서 수행합니다.
- pre-commit 설치 및 활성화: pipx/pip 사용 → pre-commit install
- lefthook 설치: 패키지 매니저로 추가 → lefthook install
- 규칙 작성: .pre-commit-config.yaml과 lefthook.yml에 최소 린트·포맷만 정의
- 검증: pre-commit run --all-files로 1회 전체 검사 후, 커밋 흐름에서 속도 확인
처음에는 변경 파일 기준 검사에 집중하고, 전체 검사는 CI나 별도 스크립트로 분리하는 편이 안전합니다. 러너 병렬화는 대기 시간을 줄이지만, 규칙이 과하면 로컬 개발 리듬이 끊길 수 있습니다.
> 실무 팁: 커밋 훅은 5~10초 내 완료를 목표로 유지하세요. 느려지면 규칙을 줄이거나 타입·테스트는 CI로 이관하는 것이 좋습니다.
변경 파일만 빠르게 검사하기
커밋이 느려지는 이유는 전체 검사를 매번 돌리기 때문입니다. 변경 파일만 검사하고, 가능한 작업은 동시에 실행하면 체감 시간이 크게 줄어듭니다.
pre-commit은 파일 필터로 대상을 좁힙니다. lefthook은 훅을 병렬(여러 작업을 동시에)로 실행해 대기 시간을 줄입니다.
아래는 프런트엔드에서 Prettier, ESLint, TypeScript를 변경 파일 기준으로 돌리는 최소 예시입니다. 구조를 이해하는 용도입니다.
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: prettier
name: prettier
entry: npx prettier --write
language: system
files: "\\.(js|ts|tsx|json|css|md)$"
- id: eslint
name: eslint
entry: npx eslint --max-warnings=0
language: system
files: "\\.(js|ts|tsx)$"
- id: tsc
name: typescript-check
entry: npx tsc -p tsconfig.json --noEmit
language: system
pass_filenames: false
- files로 확장자를 제한해 포맷·린트를 변경 파일에만 적용합니다.
- 타입체크(tsc)는 전역 확인이 기본이며, 느리면 CI로 넘깁니다.
lefthook에서는 비슷한 작업을 병렬로 묶고, 의존이 있으면 순서를 분리합니다. 병렬은 동시에 실행한다는 뜻입니다.
# lefthook.yml
pre-commit:
parallel: true
commands:
prettier:
run: npx prettier --write {staged_files}
files: git diff --name-only --cached -- \\*.{js,ts,tsx,json,css,md}
eslint:
run: npx eslint --max-warnings=0 {staged_files}
files: git diff --name-only --cached -- \\*.{js,ts,tsx}
tsc:
run: npx tsc -p tsconfig.json --noEmit
stage_fixed: true
프리티어와 ESLint는 병렬로 처리해 체감 시간을 낮춥니다. tsc는 마지막에 단독으로 실행하고, stage_fixed로 포맷 수정 파일을 다시 스테이징합니다.
> 팁: 커밋 훅에서 전체 테스트를 돌리지 마세요. 변경 파일 기준의 빠른 테스트만 두고, 전체 검증은 CI로 분리하는 편이 안전합니다.
'프로그래밍' 카테고리의 다른 글
| Apache Arrow Flight SQL로 서비스 간 저지연 데이터 전송 실무 가이드 (1) | 2026.09.04 |
|---|---|
| dbt + DuckDB로 팀 내 로컬 레이크하우스 모델링 시작 가이드: 초보 실무자를 위한 첫 설정과 예시 (1) | 2026.09.04 |
| Dev Containers 모노레포 구성: 서비스별 devcontainer.json과 Compose 패턴 실무 가이드 (0) | 2026.09.04 |
| Git sparse-checkout + partial clone으로 모노레포 온보딩 속도 최적화 가이드 (0) | 2026.09.04 |
| Discord 포럼 채널로 개발자 Q&A 지식베이스 구축: 태그·템플릿·모더레이션 가이드 (0) | 2026.08.27 |