Trunk Based Development: Tek Dal Stratejisi ile Hızlı Yazılım Geliştirme
Yıllarca feature branch modeliyle çalıştım. Her şey güzeldi, ta ki üç haftalık bir branch’i main’e merge etmeye çalışana kadar. O merge konflikti cehennemini yaşayan biri olarak söylüyorum: Trunk Based Development’ı ilk duyduğumda “bu nasıl çalışır ki?” diye sormuştum. Şimdi ise geri dönmem imkansız.
Trunk Based Development (TBD), tüm geliştiricilerin tek bir ana dal üzerinde çalıştığı, uzun ömürlü branch’lerin minimize edildiği bir versiyon kontrol stratejisidir. Ama bu tanım biraz kuru kalıyor. Gerçekte TBD, bir disiplin ve bir kültür meselesidir. Gün içinde birden fazla commit, küçük değişiklikler, sürekli entegrasyon ve herkesin aynı kod tabanı üzerinde çalışması demektir.
Neden Feature Branch Modeli Yorar?
Klasik Gitflow veya feature branch modelinde şu senaryoyu kaç kez yaşadınız: Bir geliştirici iki hafta boyunca kendi branch’inde çalışıyor. Main branch bu sürede 200 commit ilerledi. Merge zamanı geldiğinde ortaya çıkan konflikt sayısı, yapılan işin kendisinden daha uzun sürede çözülüyor.
Buna “merge hell” deniyor. Ama daha sinsi bir problemi var: entegrasyon gecikmesi. Kodunuzu ne kadar uzun süre main’den izole tutarsanız, entegrasyon o kadar pahalıya mal olur. Risk birikir, geri bildirim gecikilir, hatalar geç ortaya çıkar.
Google, Facebook, Microsoft gibi şirketlerin monorepo ve trunk based yaklaşıma geçişi rastlantısal değil. Araştırmalar da bunu destekliyor: DORA metriklerinde yüksek performanslı ekiplerin büyük çoğunluğu TBD uyguluyor.
Trunk Based Development’ın Temel Prensipleri
TBD’yi anlamak için birkaç temel prensibi özümsemek gerekiyor:
- Tek main branch: “trunk” veya “main” adı verilen tek bir uzun ömürlü dal
- Kısa ömürlü branch’ler: Eğer branch açılacaksa, maksimum 1-2 günlük ömrü olmalı
- Sık commit ve push: Gün içinde en az bir kez main’e entegrasyon
- Feature Flag’ler: Tamamlanmamış özellikler, flag’lerle gizlenerek main’e dahil edilir
- CI her zaman yeşil: Main branch her zaman deploy edilebilir durumda olmalı
Bu prensiplerin her biri birbiriyle bağlantılı. Sık commit yapabilmek için küçük değişiklikler gerekir. Küçük değişiklikler için feature flag’ler gerekir. Feature flag’ler için solid bir CI/CD pipeline gerekir.
Git Yapılandırması ve İlk Kurulum
TBD’ye geçerken ilk adım repository yapılandırmasını doğru kurmaktır. Main branch’i koruma altına almak, doğrudan push’ları kısıtlamak ve otomatik merge politikaları belirlemek bu aşamada yapılır.
# Main branch'i varsayılan olarak ayarla
git config --global init.defaultBranch main
# Local repository'de main branch oluştur
git checkout -b main
git push -u origin main
# Branch protection kuralı (GitHub CLI ile)
gh api repos/:owner/:repo/branches/main/protection
--method PUT
--field required_status_checks='{"strict":true,"contexts":["ci/tests","ci/lint"]}'
--field enforce_admins=true
--field required_pull_request_reviews='{"required_approving_review_count":1}'
--field restrictions=null
GitHub veya GitLab kullanmıyorsanız, pre-receive hook ile de koruma sağlayabilirsiniz:
# .git/hooks/pre-receive dosyası
#!/bin/bash
while read oldrev newrev refname; do
if [ "$refname" = "refs/heads/main" ]; then
# Main'e doğrudan push'u engelle
if [ "$oldrev" != "0000000000000000000000000000000000000000" ]; then
echo "HATA: Main branch'e doğrudan push yapılamaz."
echo "Lütfen kısa ömürlü bir branch açıp PR/MR kullanın."
exit 1
fi
fi
done
Günlük TBD Workflow’u
TBD’deki günlük çalışma ritmi feature branch’ten oldukça farklı. Sabah main’i çekiyorsunuz, küçük bir değişiklik yapıyorsunuz, testleri çalıştırıyorsunuz ve push ediyorsunuz. Bu döngü gün içinde birkaç kez tekrar ediyor.
# Güne başlarken: main'i güncelle
git fetch origin
git rebase origin/main
# Küçük bir değişiklik yap, test et
git add -p # Parça parça staging, toplu değil
git commit -m "feat: kullanici profil endpointi rate limiting eklendi"
# Push etmeden önce tekrar rebase (başkası push etmiş olabilir)
git fetch origin
git rebase origin/main
git push origin main
Burada git add -p kullanımına dikkat edin. TBD’de commit’lerinizin atomik ve anlamlı olması kritik. Toplu git add . yerine değişikliklerinizi gözden geçirerek staging yapın.
Çakışma olduğunda ise:
# Rebase sırasında çakışma çıktığında
git rebase origin/main
# CONFLICT mesajı gelirse:
git status # hangi dosyalar çakışıyor?
# Çakışmaları düzelt
git add <çakışan_dosya>
git rebase --continue
# Eğer vazgeçmek istersen:
git rebase --abort
Feature Flag Implementasyonu
TBD’nin en kritik parçası feature flag’ler. Tamamlanmamış bir özelliği main’e almak riskli görünür, ama flag’lerle bu riski tamamen ortadan kaldırabilirsiniz.
Basit bir feature flag implementasyonu:
# config/features.env dosyası (production'da tüm flagler kapalı)
FEATURE_NEW_DASHBOARD=false
FEATURE_BETA_API=false
FEATURE_DARK_MODE=true
Python örneği olsa da shell script ile de flag kontrolü yapabilirsiniz:
#!/bin/bash
# feature_check.sh
check_feature() {
local feature_name="$1"
local config_file="/etc/app/features.conf"
if [ ! -f "$config_file" ]; then
echo "false"
return
fi
local value=$(grep "^${feature_name}=" "$config_file" | cut -d'=' -f2 | tr -d '[:space:]')
if [ "$value" = "true" ] || [ "$value" = "1" ] || [ "$value" = "enabled" ]; then
echo "true"
else
echo "false"
fi
}
# Kullanım
if [ "$(check_feature 'NEW_DASHBOARD')" = "true" ]; then
echo "Yeni dashboard aktif, yeni kodu çalıştır"
# yeni_dashboard_kodu
else
echo "Eski dashboard aktif"
# eski_dashboard_kodu
fi
Feature flag’ler sayesinde geliştirici yeni dashboard kodunu main’e gönderebilir, ama production’da flag kapalı olduğu için kullanıcılar görmez. Test ortamında flag açılarak test edilir, hazır olunca production’da da açılır.
CI/CD Pipeline Entegrasyonu
TBD’nin çalışması için CI/CD pipeline’ı sağlam olmalı. Her commit main’e geldiğinde otomatik testler, lint kontrolü ve deployment pipeline’ı tetiklenmelidir.
# .github/workflows/main.yml (GitHub Actions)
# Bu dosyayı repository'nize ekleyin
name: TBD CI Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Lint kontrolü
run: |
# Shell scriptler için shellcheck
find . -name "*.sh" -exec shellcheck {} ;
- name: Unit testler
run: |
make test-unit
- name: Integration testler
run: |
make test-integration
- name: Deployment (sadece main branch'te)
if: github.ref == 'refs/heads/main'
run: |
make deploy-staging
GitLab kullanıyorsanız:
# .gitlab-ci.yml
stages:
- lint
- test
- deploy
variables:
TRUNK_BRANCH: "main"
lint-job:
stage: lint
script:
- shellcheck scripts/*.sh
- yamllint .
rules:
- if: '$CI_COMMIT_BRANCH == $TRUNK_BRANCH'
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
test-job:
stage: test
script:
- make test
coverage: '/Coverage: d+.d+/'
deploy-staging:
stage: deploy
script:
- ./deploy.sh staging
rules:
- if: '$CI_COMMIT_BRANCH == $TRUNK_BRANCH'
when: on_success
Kısa Ömürlü Branch Yönetimi
Evet, TBD’de de branch açabilirsiniz. Ama bu branch’ler maksimum bir iki gün yaşamalı. Uzayan branch’ler için uyarı sistemi kurmak iyi bir pratik.
#!/bin/bash
# stale_branch_checker.sh
# Eski branch'leri tespit et ve raporla
REPO_DIR="${1:-.}"
MAX_DAYS="${2:-2}"
WARNING_THRESHOLD=$((MAX_DAYS * 24 * 3600))
cd "$REPO_DIR" || exit 1
echo "=== Stale Branch Raporu (${MAX_DAYS} günden eski) ==="
echo ""
git fetch --all --quiet
git for-each-ref --format='%(refname:short) %(authordate:unix) %(authorname)'
refs/remotes/origin/ |
while read -r branch_name commit_time author_name; do
# main ve HEAD'i atla
[[ "$branch_name" =~ "origin/main" ]] && continue
[[ "$branch_name" =~ "HEAD" ]] && continue
current_time=$(date +%s)
age=$((current_time - commit_time))
age_days=$((age / 86400))
if [ "$age" -gt "$WARNING_THRESHOLD" ]; then
branch_clean="${branch_name#origin/}"
echo "UYARI: '$branch_clean' branch'i $age_days gündür güncellenmedi (Yazar: $author_name)"
fi
done
Bu script’i cron job olarak çalıştırın ve Slack/Teams’e bildirim gönderin:
# crontab -e
# Her gün sabah 9'da stale branch kontrolü
0 9 * * 1-5 /opt/scripts/stale_branch_checker.sh /var/repos/myproject 2 |
/opt/scripts/slack_notify.sh "#dev-alerts"
Gerçek Dünya Senaryosu: E-ticaret Projesi Geçişi
Bir e-ticaret projesinde TBD’ye geçiş sürecini anlatarayım. Ekip 8 geliştirici, Gitflow kullanıyordu. Haftalık release döngüsü vardı ve her release öncesi birleştirme süreci iki günü alıyordu.
TBD’ye geçişte önce küçük başladık:
# Eski feature branch'leri main ile sync et
git checkout feature/payment-refactor
git fetch origin
git rebase origin/main
# Değişiklikleri küçük parçalara böl
# git log ile commit'leri incele
git log --oneline feature/payment-refactor ^origin/main
# Her commit'i ayrı ayrı main'e al (cherry-pick veya rebase)
git checkout main
git cherry-pick abc123 # sadece hazır olan kısımlar
İlk iki haftada en büyük direnç “ama özellik yarım kaldı, nasıl main’e alacağız?” sorusundan geldi. Cevap feature flag’lerdeydi. Ödeme refactor’ünün yeni kodu flag arkasına alındı:
# deploy script'inde feature flag kontrolü
#!/bin/bash
deploy_payment_module() {
local env="$1"
if [ "$env" = "production" ]; then
PAYMENT_FLAG=$(aws ssm get-parameter
--name "/myapp/features/new_payment"
--query 'Parameter.Value'
--output text 2>/dev/null || echo "false")
if [ "$PAYMENT_FLAG" = "true" ]; then
echo "Yeni ödeme modülü deploy ediliyor..."
deploy_new_payment
else
echo "Eski ödeme modülü deploy ediliyor..."
deploy_legacy_payment
fi
fi
}
Üçüncü haftada ekip ritmi oturdı. Altıncı haftada release öncesi birleştirme süreci sıfıra düştü. Çünkü main her zaman deploy edilebilir durumdaydı.
Commit Mesajı Standartları
TBD’de commit mesajları daha da kritik çünkü her commit doğrudan main history’sine gidiyor. Conventional Commits standardını zorunlu kılmak iyi bir pratik.
# commit-msg hook'u
# .git/hooks/commit-msg
#!/bin/bash
commit_msg=$(cat "$1")
pattern="^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(([a-z-]+))?: .{1,100}$"
if ! echo "$commit_msg" | grep -qE "$pattern"; then
echo ""
echo "HATA: Commit mesajı formatı yanlış!"
echo ""
echo "Format: <tip>(<kapsam>): <açıklama>"
echo ""
echo "Geçerli tipler: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert"
echo ""
echo "Örnek: feat(auth): JWT token yenileme mekanizması eklendi"
echo "Örnek: fix(payment): null pointer exception düzeltildi"
echo ""
exit 1
fi
Bu hook’u tüm ekibe dağıtmak için:
#!/bin/bash
# setup_hooks.sh - Yeni developer onboarding script'i
HOOKS_DIR=".git/hooks"
SCRIPTS_DIR="scripts/git-hooks"
echo "Git hook'ları yapılandırılıyor..."
for hook in "$SCRIPTS_DIR"/*; do
hook_name=$(basename "$hook")
cp "$hook" "$HOOKS_DIR/$hook_name"
chmod +x "$HOOKS_DIR/$hook_name"
echo "Hook kuruldu: $hook_name"
done
echo ""
echo "TBD workflow kurulumu tamamlandı."
echo "Lütfen docs/tbd-guide.md dosyasını okuyun."
Yaygın Sorunlar ve Çözümleri
TBD’ye geçişte karşılaşılan sorunların başında “incomplete feature” korkusu geliyor. Geliştiriciler yarım kalan kodu main’e almaktan çekiniyor. Bunun çözümü feature flag olmakla birlikte, kod tasarımının da bu yaklaşımı desteklemesi gerekiyor.
İkinci büyük sorun CI süresinin uzaması. Her commit’te tetiklenen CI, eğer 20-30 dakika sürüyorsa iş akışını yavaşlatır. Çözüm:
# Paralel test çalıştırma
# Makefile örneği
test-parallel:
@echo "Testler paralel çalıştırılıyor..."
@mkdir -p test-results
# Unit testleri arka planda başlat
make test-unit > test-results/unit.log 2>&1 &
UNIT_PID=$$!
# Integration testleri arka planda başlat
make test-integration > test-results/integration.log 2>&1 &
INT_PID=$$!
# Her iki işlemin bitmesini bekle
wait $$UNIT_PID
UNIT_EXIT=$$?
wait $$INT_PID
INT_EXIT=$$?
# Sonuçları değerlendir
if [ $$UNIT_EXIT -ne 0 ] || [ $$INT_EXIT -ne 0 ]; then
echo "HATA: Testler başarısız!"
cat test-results/unit.log
cat test-results/integration.log
exit 1
fi
echo "Tüm testler başarılı."
Üçüncü yaygın sorun ise hotfix senaryoları. TBD’de hotfix nasıl yapılır? Ana branch her zaman deploy edilebilir durumda olduğu için, hotfix doğrudan main’e yapılır ve hemen deploy edilir. Release branch’i tutanlar için ise cherry-pick kullanılır.
Sonuç
Trunk Based Development, teoride basit görünür. Pratikte ise ekip disiplini, sağlam CI/CD altyapısı ve feature flag kültürü gerektirir. Geçiş acı verici olabilir, özellikle uzun süredir feature branch modeline alışmış ekiplerde.
Ama şunu söyleyeyim: TBD’ye geçtikten sonra merge konflikti cehenneminden kurtuldunuz, release süreçleri sadeleşti ve deploy sıklığınız arttıysa, bu acıya değdi demektir. Yüksek frekanslı entegrasyon, hataları erken yakalıyor. Küçük değişiklikler, rollback’i kolaylaştırıyor. Main’in her zaman yeşil olması, ekibe güven veriyor.
Başlamak için büyük bir dönüşüme gerek yok. Şu an üzerinde çalıştığınız projeye küçük adımlarla başlayın: commit boyutlarını küçültün, CI sürenizi kısaltın ve bir sonraki feature için flag implementasyonunu deneyin. Bir ay sonra farkı kendiniz göreceksiniz.
