Git Hooks ile Commit ve Push Kuralları Tanımlama
Ekipte birinin “çalışıyor ya, neden commit mesajı önemli ki?” dediğini duyan her sysadmin bilir bu acıyı. Altı ay sonra o commit’i bulmaya çalışırken “fix stuff” veya “asdfjkl” yazan onlarca satırla karşılaşmak, insanın içini karartan bir deneyim. Git hooks tam da bu noktada devreye giriyor: kuralları koyuyorsun, otomatik olarak uygulanıyor, tartışma bitmiş oluyor.
Git Hooks Nedir ve Nerede Yaşar?
Git hooks, Git’in belirli olaylar gerçekleştiğinde otomatik olarak çalıştırdığı script’lerdir. Her Git reposunda .git/hooks/ dizini bulunur ve içinde örnek dosyalar zaten hazır gelir. Bu dosyaların .sample uzantısını kaldırıp çalıştırılabilir yapman yeterli.
ls -la .git/hooks/
# applypatch-msg.sample
# commit-msg.sample
# pre-commit.sample
# pre-push.sample
# pre-receive.sample
# update.sample
Hook’lar iki ana kategoriye ayrılır:
- Client-side hooks: Geliştiricinin kendi makinesinde çalışır.
pre-commit,commit-msg,pre-pushbunların en yaygın örnekleri. - Server-side hooks: Uzak repoda çalışır.
pre-receive,update,post-receivebu kategoriye girer.
Buradaki kritik nokta şu: client-side hook’lar bypass edilebilir. git commit --no-verify komutuyla geçilebilir. Bu yüzden gerçek anlamda zorlayıcı kurallar için server-side hook’lara ihtiyaç var. İkisini birlikte kullanmak en sağlıklı yaklaşım; client-side hızlı geri bildirim sağlar, server-side ise gerçek güvenceyi.
pre-commit Hook ile Kod Kalitesi Kontrolleri
pre-commit hook, git commit komutu çalıştırıldığında, kullanıcı mesaj yazmadan önce tetiklenir. Eğer hook sıfır dışı bir değer döndürürse commit işlemi iptal edilir.
Basit bir örnekle başlayalım. Diyelim ki Python projesinde print() ifadelerinin production’a gitmesini istemiyorsun:
#!/bin/bash
# .git/hooks/pre-commit
staged_files=$(git diff --cached --name-only --diff-filter=ACM | grep '.py$')
if [ -z "$staged_files" ]; then
exit 0
fi
found_print=0
for file in $staged_files; do
if grep -n "print(" "$file" | grep -v "#.*print("; then
echo "HATA: $file dosyasinda debug print() ifadesi bulundu."
found_print=1
fi
done
if [ $found_print -ne 0 ]; then
echo ""
echo "Commit iptal edildi. Lutfen print() ifadelerini kaldiriniz."
echo "Logging kullanmak icin: import logging"
exit 1
fi
exit 0
Bu hook’u aktif etmek için:
chmod +x .git/hooks/pre-commit
Biraz daha gelişmiş bir senaryo düşünelim. Ekipte hem Python hem JavaScript kodu var, syntax hatası olan dosyaların commit edilmesini engellemek istiyorsun:
#!/bin/bash
# .git/hooks/pre-commit - Syntax kontrol hook'u
RED='33[0;31m'
GREEN='33[0;32m'
NC='33[0m'
error_found=0
# Python syntax kontrolü
python_files=$(git diff --cached --name-only --diff-filter=ACM | grep '.py$')
for file in $python_files; do
if ! python3 -m py_compile "$file" 2>/dev/null; then
echo -e "${RED}[SYNTAX HATASI]${NC} $file - Python syntax hatasi"
python3 -m py_compile "$file"
error_found=1
fi
done
# JavaScript syntax kontrolü (node gerektirir)
js_files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '.(js|jsx)$')
for file in $js_files; do
if ! node --check "$file" 2>/dev/null; then
echo -e "${RED}[SYNTAX HATASI]${NC} $file - JavaScript syntax hatasi"
error_found=1
fi
done
# Büyük dosya kontrolü (5MB üzeri)
large_files=$(git diff --cached --name-only | xargs -I{} sh -c 'test -f "{}" && du -b "{}" | awk -v f="{}" "{if ($1 > 5242880) print f}"')
if [ -n "$large_files" ]; then
echo -e "${RED}[BUYUK DOSYA]${NC} 5MB'dan büyük dosyalar commit edilemez:"
echo "$large_files"
error_found=1
fi
if [ $error_found -ne 0 ]; then
echo ""
echo -e "${RED}Commit engellendi.${NC} Yukaridaki hatalari duzeltip tekrar deneyin."
exit 1
fi
echo -e "${GREEN}Tum kontroller basarili.${NC}"
exit 0
commit-msg Hook ile Mesaj Kuralları
Commit mesajı formatını standart hale getirmek, uzun vadede projenin izlenebilirliği açısından çok değerli. Conventional Commits formatını (feat:, fix:, docs: vb.) zorunlu kılmak, otomatik changelog üretiminin de kapısını aralar.
#!/bin/bash
# .git/hooks/commit-msg
commit_msg_file=$1
commit_msg=$(cat "$commit_msg_file")
# Merge commit'leri atla
if echo "$commit_msg" | grep -q "^Merge"; then
exit 0
fi
# Conventional Commits pattern
pattern="^(feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert)((.+))?: .{1,72}"
if ! echo "$commit_msg" | grep -qE "$pattern"; then
echo ""
echo "HATA: Gecersiz commit mesaji formati!"
echo ""
echo "Beklenen format:"
echo " <tip>(<kapsam>): <aciklama>"
echo ""
echo "Gecerli tipler:"
echo " feat - Yeni ozellik"
echo " fix - Bug duzeltme"
echo " docs - Dokumantasyon"
echo " style - Kod formati"
echo " refactor - Yeniden yapilandirma"
echo " perf - Performans iyilestirme"
echo " test - Test ekleme/duzeltme"
echo " chore - Bakim isleri"
echo " ci - CI/CD degisiklikleri"
echo ""
echo "Ornek gecerli mesajlar:"
echo " feat(auth): kullanici girisi JWT ile guncellendi"
echo " fix(api): null pointer hatasi duzeltildi"
echo " docs: README guncellendi"
echo ""
echo "Gonderilen mesaj: '$commit_msg'"
exit 1
fi
# Mesaj uzunluk kontrolü (ilk satir max 72 karakter)
first_line=$(echo "$commit_msg" | head -1)
if [ ${#first_line} -gt 72 ]; then
echo "HATA: Commit mesajinin ilk satiri 72 karakteri gecemez."
echo "Mevcut uzunluk: ${#first_line} karakter"
exit 1
fi
exit 0
Bu hook sayesinde git commit -m "hızlı fix" yazan birisi anında şu mesajla karşılaşır:
HATA: Gecersiz commit mesaji formati!
İlk başta ekip buna biraz homurdanır, ama iki hafta sonra herkes alışır ve proje geçmişi okunabilir hale gelir.
pre-push Hook ile Branch ve Test Kontrolleri
pre-push hook, git push çalıştırıldığında ağ üzerinden veri gönderilmeden önce devreye girer. Bu hook’a hangi branch’e push yapılacağı bilgisi de gelir, bu yüzden main veya master branch’e doğrudan push’u engellemek için idealdir.
#!/bin/bash
# .git/hooks/pre-push
protected_branches=("main" "master" "production" "staging")
current_branch=$(git symbolic-ref HEAD | sed 's!refs/heads/!!')
for branch in "${protected_branches[@]}"; do
if [ "$current_branch" = "$branch" ]; then
echo "HATA: '$branch' branch'ine dogrudan push yapilamaz!"
echo ""
echo "Lutfen bir feature branch olusturun:"
echo " git checkout -b feature/yeni-ozellik"
echo ""
echo "Pull Request acarak merge etmeniz gerekiyor."
exit 1
fi
done
# Push öncesi testleri çalıştır
if [ -f "package.json" ] && grep -q '"test"' package.json; then
echo "Testler calistiriliyor..."
if ! npm test --silent; then
echo ""
echo "HATA: Testler basarisiz oldu. Push iptal edildi."
echo "Hatalari duzeltip tekrar deneyin."
exit 1
fi
echo "Tum testler gecti."
fi
exit 0
Hook’ları Ekiple Paylaşmak: Git Template ve Araçlar
En büyük sorun şu: .git/hooks/ dizini .gitignore kapsamında değil ama Git tarafından da takip edilmiyor. Bir ekip üyesi repoyu klonladığında hook’lar gelmez. Bunu çözmenin birkaç yolu var.
Yöntem 1: Repo içinde hooks dizini
Proje kökünde bir hooks/ veya .githooks/ dizini oluşturup hook’ları buraya koy, sonra Git’e bu dizini kullanmasını söyle:
# Proje kökünde .githooks/ dizini oluştur
mkdir .githooks
# Hook'ları buraya taşı veya oluştur
# Git config'e kaydet
git config core.hooksPath .githooks
# Bunu tüm ekibin yapması için Makefile veya setup scripti ekle
# Makefile'a ekle:
setup:
git config core.hooksPath .githooks
chmod +x .githooks/*
echo "Git hooks aktif edildi."
Yöntem 2: Husky (Node.js projeleri için)
Node.js projelerinde Husky kullanmak çok yaygın ve pratik:
# Kurulum
npm install --save-dev husky
npx husky init
# .husky/pre-commit dosyası otomatik oluşur
# package.json'a prepare script eklenir
# .husky/commit-msg içeriği:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx commitlint --edit "$1"
Yöntem 3: Git Template Directory
Sistem genelinde tüm yeni klonlanan repolara otomatik uygulanmasını istiyorsan:
# Global template dizini oluştur
mkdir -p ~/.git-templates/hooks
# Global config'e ekle
git config --global init.templateDir ~/.git-templates
# Hook'ları template dizinine koy
cp pre-commit ~/.git-templates/hooks/
chmod +x ~/.git-templates/hooks/pre-commit
# Mevcut repolara uygulamak için
cd /path/to/existing/repo
git init # Template'i uygular, mevcut dosyaları bozmaz
Server-Side Hook’larla Gerçek Güvenlik
Client-side hook’lar bypass edilebileceğinden, kritik kurallar için server tarafında da kontrol şart. GitLab ve Gitea gibi platformların kendi hook mekanizmaları var, ama bare repo üzerinde doğrudan çalışmak da mümkün.
#!/bin/bash
# /path/to/repo.git/hooks/pre-receive
# Bare repo'nun hooks dizininde bulunur
while read oldrev newrev refname; do
branch=$(echo "$refname" | sed 's|refs/heads/||')
# Protected branch kontrolü
if [[ "$branch" =~ ^(main|master|production)$ ]]; then
# Force push engeli
if [ "$oldrev" != "0000000000000000000000000000000000000000" ]; then
merge_base=$(git merge-base "$oldrev" "$newrev" 2>/dev/null)
if [ "$merge_base" != "$oldrev" ]; then
echo "HATA: '$branch' branch'ine force push yasaktir!"
exit 1
fi
fi
# Commit mesajı formatı kontrolü
if [ "$oldrev" = "0000000000000000000000000000000000000000" ]; then
oldrev=$(git rev-list --max-parents=0 "$newrev")
fi
commits=$(git rev-list "$oldrev".."$newrev")
for commit in $commits; do
msg=$(git log --format="%s" -n 1 "$commit")
if ! echo "$msg" | grep -qE "^(feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert)"; then
echo "HATA: Gecersiz commit mesaji: '$msg'"
echo "Commit: $commit"
echo "Conventional Commits formati kullanilmalidir."
exit 1
fi
done
fi
done
exit 0
Gerçek Dünya Senaryosu: Finans Uygulamasında Hook Kullanımı
Geçmişte bir finans şirketinin DevOps ekibinde çalışırken karşılaştığım bir durumu aktarayım. Production ortamında kritik bir SQL migration dosyası yanlışlıkla commit edildi, gözden kaçtı ve deploy sırasında felaket oldu. Bunun ardından şu hook’u geliştirdik:
#!/bin/bash
# .git/hooks/pre-commit - Finans projesi güvenlik kontrolleri
RISK_PATTERNS=(
"DROP TABLE"
"TRUNCATE TABLE"
"DELETE FROM.*WHERE.*1=1"
"DROP DATABASE"
"passwords*=s*['"][^'"]*['"]"
"secret_keys*=s*['"][^'"]*['"]"
"aws_secret_access_key"
"private_key"
)
staged_files=$(git diff --cached --name-only --diff-filter=ACM)
risk_found=0
for file in $staged_files; do
[ -f "$file" ] || continue
for pattern in "${RISK_PATTERNS[@]}"; do
matches=$(grep -in "$pattern" "$file" 2>/dev/null)
if [ -n "$matches" ]; then
echo "GUVENLIK UYARISI: $file dosyasinda riskli pattern bulundu:"
echo " Pattern: $pattern"
echo " Satirlar:"
echo "$matches" | head -5 | sed 's/^/ /'
echo ""
risk_found=1
fi
done
done
# Migration dosyaları için ek kontrol
migration_files=$(echo "$staged_files" | grep -i "migration|migrate")
if [ -n "$migration_files" ]; then
echo "BILGI: Migration dosyasi commit ediliyor."
echo "$migration_files"
echo ""
# Onay iste
exec < /dev/tty
read -p "Migration dosyasini commit etmek istediginizden emin misiniz? (evet/hayir): " confirm
if [ "$confirm" != "evet" ]; then
echo "Commit iptal edildi."
exit 1
fi
fi
if [ $risk_found -ne 0 ]; then
echo "Guvenlik kontrolu basarisiz. Lutfen yetkili ile gorusun."
exit 1
fi
exit 0
Bu hook sayesinde hem credential leak’ler hem de tehlikeli SQL operasyonları commit aşamasında engellendi. Yanlış pozitif durumlar için git commit --no-verify hala mevcut ama bu komutu kullanmak için ekip lideri onayı politikası oluşturduk.
Hook Yazarken Dikkat Edilmesi Gerekenler
Birkaç pratik noktanın altını çizmek istiyorum:
- Exit kodları kritik: Hook sıfır döndürürse işlem devam eder, sıfır dışı döndürürse iptal edilir. Bu basit kurala uymayan hook’lar beklenmedik davranışlar üretir.
- Performans önemli: Her commit’te 30 saniye beklemek geliştiricileri hook’ları devre dışı bırakmaya iter. Kontrolleri mümkün olduğunca hızlı tut, sadece staged dosyaları kontrol et.
- Taşınabilirlik: Hook’larda
#!/bin/bashyerine#!/usr/bin/env bashkullanmak daha güvenli. Ayrıca macOS ve Linux arasındased,grepdavranış farklılıklarına dikkat et.
- Anlamlı hata mesajları: Sadece “HATA” yazmak yetmez. Kullanıcıya neyi yanlış yaptığını ve nasıl düzelteceğini açıkça söyle.
- Bypass mekanizması: Acil durumlarda
--no-verifybayrağı hayat kurtarır. Bunu tamamen kapatmaya çalışma, sadece kötüye kullanımı loglayabilirsin.
- İdempotent ol: Hook birden fazla çalıştırıldığında aynı sonucu üretmeli, yan etkisi olmamalı.
Sonuç
Git hooks, ekip içi kod kalitesi standartlarını ve güvenlik kurallarını elle tutulur hale getirmenin en pratik yoludur. “Lütfen commit mesajlarını şu formatta yazın” e-postası göndermek yerine, kuralı teknik olarak uygulayan bir hook yazmak hem daha etkili hem de tartışmayı ortadan kaldırır.
Client-side hook’larla hızlı geri bildirim sağla, server-side hook’larla gerçek güvenceyi oluştur. Bu ikisinin kombinasyonu, --no-verify ile bypass edilemeyen bir kalite kapısı yaratır. Hook’ları ekiple paylaşmak için .githooks/ dizini yaklaşımı veya Husky gibi araçlar kullanmak, “bende çalışıyor” sorununu da çözer.
Başlamak için büyük bir sisteme ihtiyacın yok. Mevcut en acı noktandan başla; belki commit mesajları kaostur, belki credential’lar sızdırılıyor, belki testler push’tan sonra kırılıyor. Tek bir küçük hook bile bu sorunların birini tamamen ortadan kaldırabilir. Oradan itibaren zaten kendi kendine büyüyor.
