Tuesday, June 17, 2025

pre-commit으로 팀의 코드 품질 자동화하기

개발자라면 누구나 한 번쯤은 커밋 메시지에 "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.logdebugger 같은 코드가 커밋되는 것을 막습니다.
  • 단위 테스트 실행: 커밋하려는 코드가 기존 테스트를 통과하는지 빠르게 확인합니다.

전통적인 Git Hook 방식의 한계

.git/hooks/ 디렉토리에 직접 셸 스크립트를 작성하는 방식은 간단하지만 팀 프로젝트에서는 몇 가지 치명적인 단점이 있습니다.

  1. 버전 관리가 안 된다: .git 디렉토리는 Git의 추적 대상이 아니므로, 훅 스크립트를 팀원들과 공유하고 버전을 관리하기가 매우 어렵습니다.
  2. 설정이 번거롭다: 새로운 팀원이 프로젝트에 합류할 때마다 수동으로 훅 스크립트를 설정하고 실행 권한을 부여해야 합니다.
  3. 다양한 언어 환경 지원의 어려움: 파이썬, 자바스크립트, 자바 등 여러 언어를 사용하는 프로젝트에서는 각 언어에 맞는 린터와 포맷터를 설정하고 관리하는 것이 복잡해집니다.

이러한 문제들을 해결하기 위해 등장한 것이 바로 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. 실제 워크플로우

이제 모든 설정이 완료되었습니다. 개발자가 코드를 수정한 후 커밋을 시도하면 어떤 일이 벌어질까요?

  1. 개발자가 파일을 수정하고 git add . 명령으로 스테이징합니다.
  2. git commit -m "피처 추가" 명령을 실행합니다.
  3. pre-commit이 자동으로 실행되어 .pre-commit-config.yaml에 정의된 훅들을 스테이징된 파일에 대해 순차적으로 실행합니다.
  4. 성공 시나리오: 모든 훅이 성공적으로 통과하면, 커밋이 정상적으로 완료됩니다.
    $ 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(+)
    
  5. 실패 시나리오: 하나 이상의 훅이 실패하면(예: 린팅 오류 발견), 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을 도입하여 자동화된 코드 품질 관리의 힘을 경험해 보세요.


0 개의 댓글:

Post a Comment