개발자라면 누구나 한 번쯤은 커밋 메시지에 "Fix typo"나 "Apply linter" 같은 내용을 적어본 경험이 있을 겁니다. 이런 사소한 실수는 코드 리뷰 과정에서 불필요한 시간을 소모하게 하고, 팀 전체의 생산성을 저하시키는 원인이 되기도 합니다. 만약 이런 실수를 커밋하기 전에 자동으로 바로잡을 수 있다면 어떨까요? 바로 이 지점에서 Git Hook, 특히 pre-commit hook이 강력한 해결책으로 등장합니다.
이 글에서는 Git Hook의 기본 개념부터 시작하여, 팀 단위 프로젝트에서 코드 품질을 일관되게 유지하고 개발 워크플로우를 혁신적으로 개선할 수 있는 pre-commit
프레임워크의 설정 및 활용법을 상세히 다룹니다.
Git Hook이란 무엇인가?
Git Hook은 Git의 특정 이벤트(예: 커밋, 푸시)가 발생했을 때 자동으로 실행되는 스크립트입니다. 이를 통해 개발자는 특정 조건이 충족되지 않았을 때 커밋을 막거나, 커밋 메시지 형식을 강제하거나, 테스트를 자동으로 실행하는 등 다양한 자동화 작업을 수행할 수 있습니다.
Git Hook 스크립트는 모든 Git 저장소의 .git/hooks/
디렉토리 안에 위치합니다. git init
으로 새로운 저장소를 생성하면, 이 디렉토리 안에 다양한 샘플 훅(.sample
확장자)들이 생성된 것을 볼 수 있습니다.
$ ls .git/hooks/
applypatch-msg.sample pre-commit.sample pre-rebase.sample
commit-msg.sample pre-merge-commit.sample pre-receive.sample
fsmonitor-watchman.sample pre-push.sample update.sample
post-update.sample prepare-commit-msg.sample
이 샘플 파일들 중 하나의 확장자에서 .sample
을 제거하고 실행 권한을 부여하면 해당 훅이 활성화됩니다. 예를 들어, pre-commit.sample
파일의 이름을 pre-commit
으로 바꾸고 실행 권한을 주면, git commit
명령을 실행하기 직전에 해당 스크립트가 실행됩니다.
가장 강력한 훅: pre-commit
수많은 훅 중에서도 pre-commit
은 가장 널리 사용되고 강력한 훅 중 하나입니다. 커밋이 실제로 생성되기 직전에 실행되기 때문에, 코드 품질과 관련된 거의 모든 검사를 이 단계에서 수행할 수 있습니다.
- 코드 스타일 검사 (Linting): 코드가 팀의 코딩 컨벤션을 따르는지 확인합니다.
- 코드 포맷팅 (Formatting): 정해진 규칙에 따라 코드 스타일을 자동으로 수정합니다.
- 비밀 키 및 민감 정보 유출 방지: 커밋에 실수로 포함된 API 키나 비밀번호를 찾아냅니다.
- 디버깅 코드 방지:
console.log
나debugger
같은 코드가 커밋되는 것을 막습니다. - 단위 테스트 실행: 커밋하려는 코드가 기존 테스트를 통과하는지 빠르게 확인합니다.
전통적인 Git Hook 방식의 한계
.git/hooks/
디렉토리에 직접 셸 스크립트를 작성하는 방식은 간단하지만 팀 프로젝트에서는 몇 가지 치명적인 단점이 있습니다.
- 버전 관리가 안 된다:
.git
디렉토리는 Git의 추적 대상이 아니므로, 훅 스크립트를 팀원들과 공유하고 버전을 관리하기가 매우 어렵습니다. - 설정이 번거롭다: 새로운 팀원이 프로젝트에 합류할 때마다 수동으로 훅 스크립트를 설정하고 실행 권한을 부여해야 합니다.
- 다양한 언어 환경 지원의 어려움: 파이썬, 자바스크립트, 자바 등 여러 언어를 사용하는 프로젝트에서는 각 언어에 맞는 린터와 포맷터를 설정하고 관리하는 것이 복잡해집니다.
이러한 문제들을 해결하기 위해 등장한 것이 바로 pre-commit
프레임워크입니다.
pre-commit 프레임워크로 스마트하게 관리하기
pre-commit
은 Python으로 만들어진 Git Hook 관리 프레임워크입니다. 이 프레임워크는 .pre-commit-config.yaml
이라는 설정 파일을 통해 훅을 정의하고 관리합니다. 이 파일은 프로젝트 루트에 위치하여 버전 관리가 가능하므로, 팀원 모두가 동일한 훅 설정을 공유할 수 있습니다.
1. 설치 및 초기 설정
먼저, pre-commit
을 설치합니다. Python 패키지 매니저인 pip를 사용하는 것이 일반적입니다.
# pip를 사용하여 설치
pip install pre-commit
# Homebrew (macOS)를 사용하여 설치
brew install pre-commit
설치가 완료되면, 프로젝트 루트 디렉토리에 .pre-commit-config.yaml
파일을 생성합니다. 이 파일에 우리가 사용할 훅들을 정의합니다.
다음은 기본적인 설정 파일 예시입니다.
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0 # 항상 최신 안정 버전을 사용하는 것이 좋습니다.
hooks:
- id: trailing-whitespace # 파일 끝의 공백 제거
- id: end-of-file-fixer # 파일 끝에 개행 문자 추가
- id: check-yaml # YAML 파일 문법 검사
- id: check-added-large-files # 대용량 파일이 추가되는 것을 방지
설정 파일 작성이 끝났다면, 다음 명령어를 실행하여 Git Hook을 .git/hooks/pre-commit
에 설치합니다. 이 과정은 프로젝트를 처음 클론받았을 때 한 번만 실행하면 됩니다.
pre-commit install
이제 git commit
을 시도하면, pre-commit
이 스테이징된 파일들에 대해 설정된 훅들을 자동으로 실행합니다.
2. 다양한 언어를 위한 훅 추가하기
pre-commit
의 진정한 강력함은 다양한 언어와 도구를 손쉽게 통합할 수 있다는 점에서 나옵니다. 예를 들어, Python 프로젝트에서는 black
(포맷터)과 ruff
(린터), JavaScript 프로젝트에서는 prettier
(포맷터)와 eslint
(린터)를 추가할 수 있습니다.
Python 프로젝트 예시 (black, ruff)
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- repo: https://github.com/psf/black
rev: 24.4.2
hooks:
- id: black
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.4
hooks:
- id: ruff
args: [--fix] # 자동으로 수정 가능한 오류는 수정
- id: ruff-format
JavaScript/TypeScript 프로젝트 예시 (prettier, eslint)
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- repo: https://github.com/prettier/prettier
rev: 3.2.5
hooks:
- id: prettier
# 추가적인 인자를 전달하여 특정 파일 타입에만 적용 가능
# types: [javascript, typescript, css, markdown]
- repo: local # 로컬에 설치된 eslint를 사용하는 경우
hooks:
- id: eslint
name: eslint
entry: npx eslint --fix
language: node
types: [javascript, typescript]
# 초기 실행 속도를 위해 항상 실행되도록 설정
always_run: true
# 스테이징된 파일만 인자로 전달
pass_filenames: false
repo: local
을 사용하면, package.json
에 명시된 버전의 도구를 사용할 수 있어 팀원 간의 도구 버전 불일치 문제를 해결할 수 있습니다.
3. 실제 워크플로우
이제 모든 설정이 완료되었습니다. 개발자가 코드를 수정한 후 커밋을 시도하면 어떤 일이 벌어질까요?
- 개발자가 파일을 수정하고
git add .
명령으로 스테이징합니다. git commit -m "피처 추가"
명령을 실행합니다.pre-commit
이 자동으로 실행되어.pre-commit-config.yaml
에 정의된 훅들을 스테이징된 파일에 대해 순차적으로 실행합니다.-
성공 시나리오: 모든 훅이 성공적으로 통과하면, 커밋이 정상적으로 완료됩니다.
$ git commit -m "새로운 기능 추가" Trim Trailing Whitespace........................................Passed Fix End of Files................................................Passed Check Yaml......................................................Passed black...........................................................Passed ruff............................................................Passed [feature/new-logic 1a2b3c4] 새로운 기능 추가 2 files changed, 15 insertions(+)
-
실패 시나리오: 하나 이상의 훅이 실패하면(예: 린팅 오류 발견),
pre-commit
은 해당 오류를 출력하고 커밋을 중단시킵니다.$ git commit -m "버그 수정" Trim Trailing Whitespace........................................Passed Fix End of Files................................................Passed black...........................................................Failed - hook id: black - files were modified by this hook reformatted my_bad_file.py All done! ✨ 🍰 ✨ 1 file reformatted.
이 경우,
black
이나prettier
와 같이 자동 수정 기능이 있는 훅은 파일을 직접 수정합니다. 개발자는 수정된 파일을 다시 스테이징(git add my_bad_file.py
)하고 다시 커밋을 시도하면 됩니다. 이 과정을 통해 지저분한 "Fix lint" 커밋 없이 항상 깔끔한 코드를 유지할 수 있습니다.
결론: 왜 pre-commit을 도입해야 하는가?
pre-commit
프레임워크를 도입하는 것은 단순한 도구 추가를 넘어, 개발 문화 자체를 개선하는 효과적인 방법입니다.
- 일관성 있는 코드 품질: 모든 팀원이 동일한 규칙에 따라 코드를 작성하고 검사하므로, 프로젝트 전체의 코드 품질이 상향 평준화됩니다.
- 리뷰 시간 단축: 코드 리뷰어는 스타일이나 사소한 오류 대신 비즈니스 로직에 더 집중할 수 있습니다.
- 자동화된 워크플로우: 개발자는 린팅이나 포맷팅 같은 반복적인 작업을 신경 쓸 필요 없이 개발에만 집중할 수 있습니다.
- 실수 방지: 민감 정보나 디버깅 코드가 저장소에 커밋되는 것을 사전에 차단하여 보안을 강화합니다.
처음에는 설정을 추가하고 팀원들에게 사용법을 안내하는 약간의 노력이 필요할 수 있습니다. 하지만 이 작은 투자는 장기적으로 팀의 생산성을 극대화하고, 더 견고하고 유지보수하기 쉬운 코드를 만드는 밑거름이 될 것입니다. 지금 바로 여러분의 프로젝트에 pre-commit
을 도입하여 자동화된 코드 품질 관리의 힘을 경험해 보세요.