Git Hook 이란?
Git Hook은 특정 Git 이벤트(커밋, 푸시, 머지 등)가 발생할 때 자동으로 실행되는 스크립트를 말한다. 레포지토리 루트의 .git/hooks/ 폴더에 훅 스크립트를 넣으면 해당 이벤트 발생 시 자동 호출된다.
•
대표적인 Git Hook의 예:
◦
pre-commit: 커밋 수행 전에 실행
◦
prepare-commit-msg: 커밋 메시지를 작성하기 전에 실행
◦
post-commit: 커밋 완료 직후에 실행
Git Hook 장점
•
코드 품질검사 자동화
◦
커밋 시 코드 포맷팅(파이썬의 Black, isort 등)과 린트(Flake8, Pylint)를 자동으로 수행해 코드 품질을 유지
◦
린트 검사로 잠재적인 문제를 미리 발견하여 개발 프로세스 내에서 발생할 수 있는 이슈를 최소화
•
커밋메시지 표준화
◦
브랜치명이나 Jira 이슈 번호를 커밋 메시지에 자동으로 삽입해 메시지의 일관성을 확보
•
개발자 생산성 향상
◦
반복적인 코드 포맷 및 스타일 검사를 훅 단에서 처리함으로써 개발자가 별도 명령어를 실행할 필요를 제거
현재 데이터 팀에서는 pre-commit과 prepare-commit-msg를 적용해 코드 품질 관리를 하고 있다. 이어서 이 두 훅을 어떻게 설정하고 적용했는지 알아보겠다.
Pre-commit 훅 설정
1.
준비사항
•
기존 Git 전역 커밋 템플릿 설정이 있으면 제거 (없다면 생략 가능)
git config --unset --global commit.template
Bash
복사
•
pre-commit 설치
pip install pre-commit
Bash
복사
2.
pre-commit-config.yaml 생성 및 작성
•
프로젝트 루트에서 기본 샘플을 복사
pre-commit sample-config > .pre-commit-config.yaml
Bash
복사
•
필요한 훅만 설정
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace # 불필요한 행 끝 공백 제거
- id: end-of-file-fixer # 파일 끝에 공백/줄바꿈을 자동 추가
- id: check-yaml # YAML 문법 검사
- id: check-added-large-files # 추가된 큰 파일 감지
- repo: https://github.com/psf/black
rev: 23.12.0
hooks:
- id: black # Python 코드 자동 포매팅
language_version: python3
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort # import 구문 정리
args:
- --profile=black
- repo: https://github.com/PyCQA/autoflake
rev: v2.2.1
hooks:
- id: autoflake # 불필요한 import 및 변수 제거
args:
- --in-place
- --remove-unused-variables
- --remove-all-unused-imports
- --expand-star-imports
- repo: https://github.com/pycqa/flake8
rev: 6.1.0
hooks:
- id: flake8 # 코드 스타일 검사
args:
- --ignore=W605,W503,E402,E203
- --max-line-length=200
- repo: https://github.com/pycqa/pylint
rev: v3.3.0
hooks:
- id: pylint # 정적 분석 및 코드 품질 검사
additional_dependencies:
- pydantic
- acryl-datahub
- pyyaml
YAML
복사
3.
작성한 훅 적용
pre-commit install
Bash
복사
•
YAML 파일 변경 시 위 명령을 다시 실행해줘야 변경 사항이 적용됨
4. 테스트 예시
•
git commit 을 하면 다음과 같이 지정해둔 작업들이 하나씩 실행됨
$ git add .
$ git commit -m "feat: 예시 커밋"
Trim Trailing Whitespace.................................................Passed
fix end of files.........................................................Passed
check yaml...............................................................Passed
check for added large files..............................................Passed
black....................................................................Passed
isort....................................................................Passed
autoflake................................................................Passed
flake8...................................................................Passed
pylint...................................................................Passed
[main 401a188] [main] Replace indent logic
3 files changed, 632 insertions(+), 10 deletions(-)
create mode 100644 .pylintrc
YAML
복사
•
모든 훅이 통과되야 커밋이 완료된다
•
에러가 있을 경우, 해당 훅의 에러 메시지가 출력되고 커밋은 중단
Prepare-commit-msg 훅 설정
prepare-commit-msg 훅은 커밋 메시지를 작성하기 전에 호출되며, 미리 정의한 템플릿을 커밋 메시지에 삽입하거나 브랜치명을 자동 반영할 수 있다.
1. 커밋 템플릿 작성
•
레포지토리 루트에 .gitmessage.txt를 만들고 기본 템플릿을 작성
•
예시 템플릿
###############
# <타입> : <제목> 형태로 커밋 제목 작성
# 예시: feat: 로그인 기능 추가
[__TICKET_NAME__]
# 줄바꿈용
################
# 본문 (세부 내용) 작성: 한 줄 72자 이내
# 여러 줄 작성 시, 줄마다 "-" 사용
################
JIRA __TICKET_NAME__
################
# 타입 목록:
# feat : 새로운 기능 추가
# fix : 버그 수정
# docs : 문서 수정
# test : 테스트 코드 추가
# refact : 코드 리팩토링
# style : 코드 의미에 영향 없는 변경
# chore : 빌드/패키지 매니저 수정 등
################
Plain Text
복사
2.
prepare-commit-msg 스크립트 작성
•
prepare-commit-msg 생성
touch .git/hooks/prepare-commit-msg
Bash
복사
•
스트립트 내용 작성
COMMIT_MSG_FILE=$1
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
# 자신의 .gitmessage.txt 경로로 수정
cp <자신의 경로>/.gitmessage.txt "$COMMIT_MSG_FILE"
sed -i '' "s/__TICKET_NAME__/$BRANCH_NAME/g" "$COMMIT_MSG_FILE"
Bash
복사
•
$1은 Git이 넘겨주는 실제 커밋 메시지 파일 경로
•
git rev-parse --abbrev-ref HEAD 명령으로 현재 브랜치명 추출
•
sed -i 로 gitmessage.txt 파일 내 모든 __TICKET_NAME__ 을 현재 브랜치명으로 변경
3.
실행권한 부여
chmod +x .git/hooks/prepare-commit-msg
Bash
복사
4.
테스트
git commit
Bash
복사
•
에디터가 열리면서 .gitmessage.txt 내용이 로드되고, 상단에 현재 브랜치명이 자동 삽입
•
템플릿을 수정하여 저장하면 커밋이 완료
예시
•
브랜치명이 develop-1234 이고 커밋 메시지로 fix: 오류 수정을 입력한 경우 주석은 사라지고 최종적으로 아래와 같은 형태가 됨
[develop-1234] fix: 오류 수정
JIRA develop-1234
C#
복사
Pylint
Pylint란?
Pylint는 Python 코드 스타일과 버그 패턴을 정적 분석해주는 도구이다. .pylintrc 파일을 통해 팀 표준 규칙을 한곳에서 관리함으로써 코드 일관성 제고 및 잠재적 버그 사전 차단이 가능하다.
내부적으로 AST(Abstract Syntax Tree) 기반으로 코드 구조를 분석하여 다음 항목들을 검사한다 .
•
코드 포맷
•
네이밍 컨벤션
•
모듈 import 유효성 검사
•
잠재적 런타임 오류
•
코드 복잡도
pylintrc 기본 템플릿 생성
•
기본 템플릿 생성 명령어:
pylint --generate-rcfile > .pylintrc
Bash
복사
•
Pylint가 제공하는 모든 기본 설정 항목을 포함한 파일이 생성
•
필요에 따라 값을 수정해 각 프로젝트 규칙에 맞게 조정
•
(.pylintrc 은 필수 파일은 아니고 Git Hook 내의 정의로도 동작 가능)
주요 설정
생성된 .pylintrc 파일에는 각 섹션별로 기본값이 주석 처리되어 있다.
프로젝트 규모나 코드 복잡도에 따라 pylint 실행 시의 성능에 영향을 줄 수 있으므로, 검사할 디렉터리 범위(ignore 옵션)나 최대 검사 깊이(max-line-length) 등을 초기 단계에서 적절히 조정하는 것이 권장된다.
[MASTER]
•
init-hook을 통해 모듈 탐색 경로를 추가할 수 있음
[MASTER]
init-hook=import sys; sys.path.append('app/src')
Plain Text
복사
[MESSAGES CONTROL]
•
린트 경고 또는 오류 코드를 끄려면 disable= 목록에 추가
[MESSAGES CONTROL]
disable =
missing-docstring,
duplicate-code,
broad-exception-raised
Plain Text
복사
•
missing-docstring: 모듈, 클래스, 함수에 docstring이 없을 때 발생하는 메시지 차단
•
duplicate-code: 중복 코드 블록 검사 차단
•
broad-exception-raised: except Exception:와 같이 광범위한 예외 처리 경고 차단
[FORMAT]
•
코드 스타일(최대 행 길이 등)을 정의
[FORMAT]
max-line-length = 200
indent-string = ' '
Plain Text
복사
•
max-line-length: 한 줄 최대 글자 수 지정(PEP8 기본값 100)
•
indent-string: 들여쓰기에 사용할 공백 개수 지정
[TYPECHECK]
•
타입 검사 관련 설정을 정의
•
외부 라이브러리 임포트 시 발생하는 타입 체크 오류를 무시
[TYPECHECK]
ignored-modules = boto3, botocore
ignored-classes = SQLObject, optparse.Values
Plain Text
복사
함수별 pylint 주석 제어
Pylint 설정을 전체 .pylintrc 외에도, 함수나 특정 코드 블록 위에 # pylint: disable=... 주석을 달아 세부 제어할 수 있다.
•
함수 전체에 경고 비활성화 예시
# pylint: disable=too-many-arguments, unnecessary-else
def complex_function(a, b, c, d, e, f):
# ... 복잡한 로직 ...
return result
# pylint: enable=too-many-arguments, unnecessary-else
Python
복사
•
한 줄에만 적용
def foo(bar): # pylint: disable=unused-argument
pass
Python
복사
•
파일 맨 위에서 모듈 전체 제어
# pylint: disable=missing-docstring, invalid-name
Python
복사
이렇게 하면 특정 함수나 모듈에 한정해서 필요한 규칙만 비활성화하거나, 블록이 끝난 뒤 다시 활성화할 수 있어 훨씬 유연한 관리가 가능하다.
pylint 실행 예시
•
처음 pylint를 실행하면 방대한 정도의 에러 및 경고 목록이 출력될 수 있다.
•
커밋 훅이 활성화된 상태에서는 모든 에러를 해결하지 않으면 커밋이 차단된다. (그래서 이번 작업에서 처음에 에러가 너무 많아 일단 끄고 github action 작업을 완료한 후에 다시 켜서 점수를 확인했다.)
•
초반에는 필수적인 에러 위주로 우선 순위를 둬서 해결하고, 점차적으로 리팩토링하거나 예외처리를 통해 기준을 맞추도록 관리하는 것이 좋다.
데이터허브 프로젝트의 Pylint 결과
•
코드 수가 적음에도 불구하고 다수의 에러가 발생
•
각 에러 항목에는 고유한 에러 코드가 부여되어 있어 어떤 유형의 문제인지 파악 가능
************* Module src.application.test
app/src/application/test.py:5:0: E0401: Unable to import 'datahub.sdk' (import-error)
app/src/application/test.py:8:0: E0401: Unable to import 'app.src.services.test' (import-error)
app/src/application/test.py:9:0: E0401: Unable to import 'app.src.utils.set_logger' (import-error)
************* Module src.services.test
app/src/services/test.py:4:0: E0401: Unable to import 'yaml' (import-error)
app/src/services/test.py:5:0: E0401: Unable to import 'datahub.sdk' (import-error)
app/src/services/test.py:8:0: E0401: Unable to import 'app.src.configs.yaml_config' (import-error)
app/src/services/test.py:34:16: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation)
app/src/services/test.py:37:16: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation)
app/src/services/test.py:43:8: R1705: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it (no-else-return)
app/src/services/test.py:61:12: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation)
app/src/services/test.py:63:12: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation)
app/src/services/test.py:67:12: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation)
-----------------------------------
Your code has been rated at 3.86/10
Plain Text
복사
•
Pylint 최종 점수는 전체 코드 품질을 10점 만점 기준으로 산정한 값
메시지 코드 분류
•
E(Error): 코드 실행에 지장을 주는 심각 오류(모듈을 못 찾음, 구문 에러 등)
•
W(Warning): 코드가 정상 동작해도 성능 저하나 유지보수성 저해 가능성을 알리는 경고
•
R(Refactor): 코드 구조 개선이나 리팩토링 권고
•
C(Convention): 네이밍 컨벤션이나 줄 길이 초과 등 스타일 위반
•
I(Information): 정보성 메시지 또는 제안
해결 기록
•
E0401: Unable to import 'app.src.utils….
◦
파이썬에서 모듈 탐색시 패키지로 인식하려면 __init__.py 필요한데 app 경로에 누락됨
◦
app 경로에 __init__.py 추가해서 해결
•
E0401: Unable to import 'datahub.sdk'
◦
pylint가 검사할 때 해당 라이브러리가 설치되어 있어야 실제로 import 가능
◦
pylint는 기본적으로 시스템 경로와 프로젝트 경로만 참조하므로, 가상환경이 활성화되지 않은 상태여서 라이브러리 찾지 못함
◦
.pre-commit-config.yaml 파일의 pylint 부분에 additional_dependencies 항목으로 datahub-sdk-python을 명시하여, Pylint 실행 시 해당 라이브러리를 자동 설치하도록 설정하여 해결
•
W1203: Use lazy % formatting in logging functions
◦
로깅 메시지에서 f-string을 사용하면 lazy loading 이 되지 않는 다는 경고
◦
예를 들어,
logger.info(f"Updated metadata for {urn}")
Python
복사
◦
로그 레벨이 INFO보다 높아 메시지를 출력하지 않을 때도, f-string 내부 표현(예시에서는 urn)이 미리 문자열을 만들어둠
◦
만약 로깅 레벨이 WARNING 일 경우 최종 로그 메시지는 출력되지 않지만 f string이 내부 표현식을 계산하는 비용이 계속 발생한다는 이야기
logger.info("Updated metadata for %s", urn)
Python
복사
◦
pylint에서는 위와 같이 % 포맷을 사용하는 것을 권고
•
R1705: Unnecessary "else" after "return" (no-else-return)
◦
if 조건: return 구문 다음에 불필요하게 else: 블록을 사용하는 경우 뎁스가 증가하여 가독성이 저하
◦
예시
def func(x):
if x < 0:
return True
else:
return False
Python
복사
위 코드는 다음과 같이 개선
def func(x):
if x < 0:
return True
return False
Python
복사
Git Hook을 도입하면서 이전 직장에서는 Black을 적용했던 경우와 적용하지 않았던 경우가 커밋마다 뒤섞여 관리되지 않던 혼란에서 벗어났다. 코드 품질 규칙이 중앙화 + 자동화되고 스타일 문제가 사라졌다. 또한 Pylint로 자주 발생하는 모듈 import error나 미처 알지못한 잠재적 에러를 미리 확인할 수 있어 안정감이 생겼다. 이런식으로 하나씩 체계가 잡혀가는 과정을 경험하면서, 개발 프로세스가 정돈될 수 있다는 것을 깨달았고 덕분에 많은 것을 배울 수 있었다.
