Pre-commit Hooks ile Otomatik Kod Kalite Kontrolü
Geliştirme süreçlerinde “ama bende çalışıyordu” cümlesini kaç kez duydunuz? Ya da bir pull request incelemesinde lint hatalarıyla, yanlış format edilmiş dosyalarla, hatta production’a gidecek kodda hardcode edilmiş API key’lerle karşılaştınız mı? Pre-commit hook’lar tam da bu noktada devreye giriyor. Kod, repository’e ulaşmadan önce bir süzgeçten geçiyor ve çoğu sorun daha commit aşamasında yakalanıyor.
Bu yazıda pre-commit hook framework’ünü, gerçek dünya konfigürasyonlarını ve bir DevOps/sysadmin ekibinin gerçekten işine yarayacak araçları ele alacağım.
Pre-commit Hook Nedir, Neden Önemlidir?
Git, belirli olaylar gerçekleştiğinde otomatik olarak çalıştırılabilecek script mekanizması sağlıyor. Bunlara “git hook” deniyor. pre-commit hook’u, git commit komutu çalıştırıldığında, commit işlemi gerçekleşmeden hemen önce tetikleniyor.
Manuel olarak .git/hooks/pre-commit dosyası oluşturup shell script yazabilirsiniz, ama bu yaklaşımın ciddi dezavantajları var: taşınabilir değil, versiyon kontrolüne dahil etmek zor, ekipteki herkes ayrı ayrı kurmak zorunda.
pre-commit (pypi.org/project/pre-commit) adlı Python tabanlı framework ise bu sorunları çözüyor. YAML tabanlı konfigürasyon dosyasıyla yüzlerce hazır hook’u tek bir yerden yönetebiliyorsunuz ve bu dosya repository ile birlikte versiyonlanıyor.
Kurulum ve Temel Konfigürasyon
Python pip ile kurulum:
pip install pre-commit
# Ya da pipx kullanıyorsanız (önerilen yöntem):
pipx install pre-commit
# Homebrew (macOS/Linux):
brew install pre-commit
# Kurulum doğrulama:
pre-commit --version
Repository’nize hook’ları aktif etmek için önce .pre-commit-config.yaml dosyası oluşturuyorsunuz, ardından hook’ları git’e tanıtıyorsunuz:
# Proje dizininde:
touch .pre-commit-config.yaml
# Hook'ları .git/hooks/ altına kur:
pre-commit install
# Sadece pre-push aşamasında çalışacak hook'lar için:
pre-commit install --hook-type pre-push
Basit bir başlangıç konfigürasyonu:
# .pre-commit-config.yaml
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
- id: check-json
- id: check-merge-conflict
- id: detect-private-key
- id: check-added-large-files
args: ['--maxkb=500']
Bu konfigürasyonla bile ciddi bir koruma katmanı elde ediyorsunuz. detect-private-key hook’u özellikle kritik: AWS secret key, RSA private key ve benzeri desenleri tespit ediyor.
Python Projeleri için Kapsamlı Konfigürasyon
Bir Django projesi ya da Python tabanlı otomasyon script’leri yönetiyorsanız şu konfigürasyon iyi bir başlangıç noktası:
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
- id: check-toml
- id: check-merge-conflict
- id: detect-private-key
- id: check-added-large-files
args: ['--maxkb=1024']
- id: no-commit-to-branch
args: ['--branch', 'main', '--branch', 'master', '--branch', 'production']
- repo: https://github.com/psf/black
rev: 23.12.1
hooks:
- id: black
language_version: python3.11
- repo: https://github.com/PyCQA/flake8
rev: 7.0.0
hooks:
- id: flake8
args: ['--max-line-length=88', '--extend-ignore=E203']
additional_dependencies:
- flake8-bugbear
- flake8-comprehensions
- repo: https://github.com/PyCQA/isort
rev: 5.13.2
hooks:
- id: isort
args: ['--profile=black']
- repo: https://github.com/PyCQA/bandit
rev: 1.7.7
hooks:
- id: bandit
args: ['-c', 'pyproject.toml']
additional_dependencies: ['bandit[toml]']
no-commit-to-branch hook’u özellikle değer veririm. Doğrudan main veya production branch’ine commit atmayı engelliyor. Özellikle gece yarısı aceleyle yapılan “küçük düzeltmelerin” production’ı bozduğu senaryoları yaşadıysanız, bu hook hayat kurtarıcı oluyor.
bandit Python kodundaki güvenlik açıklarını tarıyor: hardcoded password, SQL injection desenleri, güvensiz hash fonksiyonu kullanımları gibi sorunları yakalıyor.
JavaScript ve Node.js Projeleri
Frontend veya Node.js tabanlı projeler için ESLint ve Prettier entegrasyonu:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-json
- id: check-yaml
- id: detect-private-key
- id: no-commit-to-branch
args: ['--branch', 'main', '--branch', 'staging']
- repo: https://github.com/pre-commit/mirrors-eslint
rev: v8.56.0
hooks:
- id: eslint
files: .(js|jsx|ts|tsx)$
types: [file]
additional_dependencies:
- [email protected]
- [email protected]
- '@typescript-eslint/[email protected]'
- '@typescript-eslint/[email protected]'
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0
hooks:
- id: prettier
types_or: [javascript, jsx, ts, tsx, css, json, yaml, markdown]
ESLint hook’unu kullanırken dikkat etmeniz gereken bir nokta var: additional_dependencies içindeki paket versiyonları proje package.json‘ınızdakiyle uyumlu olmalı, aksi hâlde garip davranışlar görebiliyorsunuz.
Altyapı Kodu için Hook’lar: Terraform ve Ansible
Sysadmin perspektifinden en keyif aldığım kısım burası. Uygulama kodu yazan arkadaşlar lint ve format konusunu çoktan çözmüş olabilir, ama altyapı kodu (Infrastructure as Code) için aynı disiplin çok daha az uygulanıyor.
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-yaml
exclude: 'helm/.*'
- id: detect-private-key
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.86.0
hooks:
- id: terraform_fmt
- id: terraform_validate
- id: terraform_tflint
args:
- '--args=--only=terraform_deprecated_interpolation'
- '--args=--only=terraform_deprecated_index'
- '--args=--only=terraform_unused_declarations'
- '--args=--only=terraform_comment_syntax'
- '--args=--only=terraform_documented_outputs'
- '--args=--only=terraform_documented_variables'
- id: terraform_trivy
args:
- '--args=--severity HIGH,CRITICAL'
- repo: https://github.com/ansible/ansible-lint
rev: v24.2.0
hooks:
- id: ansible-lint
files: .(yaml|yml)$
args: ['--profile=production']
terraform_trivy hook’u Terraform kodundaki güvenlik açıklarını tarıyor. Açık S3 bucket tanımı, şifrelenmemiş disk, geniş IAM permission gibi sorunları commit öncesinde yakalıyorsunuz. Bu hook’u eklemeden önce ne kadar “masum görünen” Terraform kodunun güvenlik açığı içerdiğini görünce şaşırabilirsiniz.
Ansible-lint için --profile=production argümanı biraz agresif olabilir, geliştirme ortamında --profile=basic ile başlamak daha makul.
Özel Hook Yazımı: Gerçek Dünya Senaryosu
Hazır hook’lar çoğu durumu karşılıyor, ama bazen projeye özel kontrollere ihtiyaç duyuyorsunuz. Örneğin: her Python dosyasının başında telif hakkı bilgisi olmasını zorunlu kılmak, ya da belirli bir pattern içeren dosya isimlerini engellemek.
Yerel hook’lar için local repo tipini kullanıyorsunuz:
repos:
- repo: local
hooks:
- id: check-env-file
name: .env dosyasi commit edilmesin
entry: bash -c 'if git diff --cached --name-only | grep -q "^.env$"; then echo "HATA: .env dosyasi commit edilemez!"; exit 1; fi'
language: system
pass_filenames: false
- id: check-debug-statements
name: Debug ifadelerini kontrol et
entry: bash -c 'if git diff --cached | grep -E "^+.*(console.log|debugger|pdb.set_trace|breakpoint())" | grep -v "^++" | grep -q .; then echo "UYARI: Debug ifadesi bulundu. Lutfen kaldiriniz."; exit 1; fi'
language: system
pass_filenames: false
- id: check-todo-fixme
name: TODO ve FIXME kontrolu
entry: bash -c 'git diff --cached | grep -E "^+.*(TODO|FIXME|HACK|XXX)" | grep -v "^++" && echo "Yukaridaki TODO/FIXME ifadelerini issue olarak acin." && exit 1 || exit 0'
language: system
pass_filenames: false
check-debug-statements hook’u gerçekten can kurtarıyor. Production log’larında console.log çıktıları gören, ya da bir Python servisinin beklenmedik şekilde pdb prompt’unda askıda kaldığını yaşayan varsa bu hook’u anında benimseyecektir.
TODO/FIXME hook’unu biraz daha toleranslı tutmak isteyebilirsiniz, mesela sadece uyarı verip commit’i engellememek gibi. Ekip kültürüne göre ayarlamak lazım.
CI/CD Pipeline ile Entegrasyon
Pre-commit hook’ların yerel makinede çalışması güzel, ama ekipteki herkes mutlaka yükleyip aktif etmeyebilir. Bu yüzden aynı kontrolleri CI/CD pipeline’ına da eklemek iyi bir pratik.
GitHub Actions ile:
# .github/workflows/pre-commit.yml
name: Pre-commit Kontrolleri
on:
pull_request:
branches: [main, staging]
push:
branches: [main]
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Python kur
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: pre-commit cache
uses: actions/cache@v3
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
- name: pre-commit calistir
uses: pre-commit/[email protected]
Cache adımı önemli: pre-commit her hook için sanal ortam oluşturuyor ve bu işlem zaman alıyor. Cache olmadan her PR’da 2-3 dakika kaybedebilirsiniz.
GitLab CI için de benzer bir yapı kurabilirsiniz:
# .gitlab-ci.yml içine eklenecek job
pre-commit-check:
stage: validate
image: python:3.11-slim
before_script:
- pip install pre-commit
script:
- pre-commit run --all-files
cache:
key: pre-commit-${CI_COMMIT_REF_SLUG}
paths:
- .cache/pre-commit
variables:
PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
Ekip Onboarding: Makefile ile Kolaylaştırma
Yeni bir geliştirici projeye katıldığında “pre-commit install çalıştırmayı unutma” demek yetersiz kalıyor. Makefile veya script ile otomatize etmek daha sağlıklı:
# Makefile
.PHONY: setup lint lint-all
setup:
@echo "Gelistirme ortami kuruluyor..."
pip install -r requirements-dev.txt
pre-commit install
pre-commit install --hook-type commit-msg
@echo "Kurulum tamamlandi. pre-commit hook'lari aktif."
lint:
@echo "Staged dosyalar kontrol ediliyor..."
pre-commit run
lint-all:
@echo "Tum dosyalar kontrol ediliyor..."
pre-commit run --all-files
update-hooks:
@echo "Hook'lar guncelleniyor..."
pre-commit autoupdate
@echo "Guncelleme tamamlandi. .pre-commit-config.yaml dosyasini inceleyin."
pre-commit autoupdate komutu .pre-commit-config.yaml içindeki tüm rev değerlerini otomatik olarak en son sürüme güncelliyor. Bunu aylık bir rutine dahil etmek, güvenlik yamalarının zamanında uygulanması açısından önemli.
Performans Optimizasyonu
Büyük projelerde pre-commit çalışma süresi can sıkıcı hâle gelebilir. Birkaç optimizasyon yöntemi:
repos:
- repo: https://github.com/psf/black
rev: 23.12.1
hooks:
- id: black
# Sadece belirli dizinleri kontrol et
files: ^(src|tests)/
# Belirli dosyaları hariç tut
exclude: ^src/migrations/
- repo: https://github.com/PyCQA/flake8
rev: 7.0.0
hooks:
- id: flake8
# Paralel çalışma için
args: ['--jobs=auto']
# Sadece staged dosyaları işle (varsayılan davranış zaten bu)
stages: [commit]
stages parametresi önemli: bazı ağır hook’ları sadece push aşamasına taşıyabilirsiniz. Örneğin Trivy ile güvenlik taraması her commit’te değil, push öncesinde çalışabilir:
- id: terraform_trivy
stages: [push]
args:
- '--args=--severity HIGH,CRITICAL'
Ayrıca belirli bir commit’te hook’ları geçici olarak atlamak için:
# Tüm hook'ları atla (acil durum için, alışkanlık haline getirilmemeli):
git commit -m "acil fix" --no-verify
# Belirli bir hook'u atla:
SKIP=flake8 git commit -m "WIP: devam edecek"
--no-verify kullanımını ekip içinde kural haline getirmemek önemli. Meşru kullanım senaryoları var elbette (üretilmiş kod commit etmek gibi), ama bu seçeneğin varlığı “zahmetli olunca atlıyoruz” kültürünü doğurabilir.
Commit Message Standardizasyonu
Pre-commit sadece kod kalitesiyle sınırlı değil. Commit mesajlarını da standartlaştırabilirsiniz. Conventional Commits formatını zorunlu kılmak için:
repos:
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v3.2.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
args: [feat, fix, docs, style, refactor, perf, test, chore, ci, build]
Bu hook’u aktif etmek için ayrı kurulum gerekiyor:
pre-commit install --hook-type commit-msg
Artık git commit -m "bir şeyler yaptım" gibi mesajlar reddedilecek, feat: kullanici girisi eklendi veya fix: session timeout hatasi duzeltildi formatı zorunlu hâle gelecek. Changelog oluşturmayı otomatize ediyorsanız bu standart çok değerli.
Yaygın Sorunlar ve Çözümleri
Pre-commit kullanırken karşılaşılan tipik sorunlar ve çözümleri:
Hook kurulum dizini izin sorunu:
# pre-commit cache dizinini kontrol et:
pre-commit clean
# Yeniden kur:
pre-commit install --overwrite
Belirli bir hook’u güncelleme sonrası sorun çıkartıyor:
# Tüm hook ortamlarını temizle ve yeniden oluştur:
pre-commit clean
pre-commit run --all-files
Docker ortamında pre-commit kullanımı:
# Dockerfile içinde:
RUN pip install pre-commit &&
pre-commit install-hooks
# Ya da docker-compose ile geliştirme ortamında:
# volumes kısmında .pre-commit-config.yaml mount edilmeli
Windows geliştirici ortamı:
Windows’ta pre-commit kullanıyorsanız WSL2 üzerinde çalışmanızı öneririm. Native Windows’ta bazı shell hook’ları beklendiği gibi çalışmayabiliyor. Git Bash üzerinde de temel hook’lar çalışıyor ama karmaşık shell script’ler sorun çıkartabiliyor.
SonarQube ile Entegrasyon Notu
SonarQube kapsamlı bir statik analiz aracı ama her commit’te çalıştırmak pratik değil. Genellikle SonarQube’u CI/CD pipeline’ına entegre edip pre-commit hook’larıyla tamamlayıcı bir katman oluşturmak daha mantıklı bir yaklaşım.
Pre-commit aşamasında hızlı lokal kontroller (format, basit lint, güvenlik desenleri), CI aşamasında SonarQube ile derin analiz şeklinde bir iş bölümü iyi çalışıyor. SonarQube’un sonar-scanner CLI’sını pre-push hook olarak ekleyebilirsiniz ama analiz süresi uzunsa geliştiricilerin hook’ları devre dışı bırakma riskiyle karşılaşırsınız.
Sonuç
Pre-commit hook’lar, kod kalitesini sol’a kaydırmanın (shift-left) en pratik yollarından biri. Kurulum maliyeti düşük, geri dönüşü yüksek. Bir kere doğru konfigürasyon oluşturulduğunda ekip neredeyse hiç ek yük hissetmeden kalite kontrolünden geçiyor.
Başlamak için şu sırayı öneririm: önce temel pre-commit-hooks paketini kurun ve bir hafta kullanın, ardından projenizin diline özel formatter ve linter ekleyin, sonra güvenlik odaklı hook’ları (bandit, trivy, detect-private-key) dahil edin. Son aşamada CI/CD entegrasyonunu tamamlayın.
En önemli nokta: hook’ları çok katı tutarsanız geliştiriciler --no-verify ile geçmeye başlar ve tüm sistem kağıt üzerinde kalır. Her hook’un neden orada olduğunu ekiple paylaşmak, sahiplenme duygusunu artırıyor. “Bu kural bizi yavaşlatıyor” itirazlarını ciddiye alın, gerekirse konfigürasyonu birlikte gözden geçirin. Araç ekibin önünde değil, arkasında durmalı.
