Production'da Git: Senior'ın Pratik Rehberi
Workflow seçiminden bisect ile bug avına, rebase disiplininden worktree ve signed commit'lere — Git'i bir yıllarca kullandıktan sonra fark ettiklerim.
Git’i 5 yıl kullanan biri ile 15 yıl kullanan biri arasındaki fark, bildikleri komut sayısı değil. Fark şu: ilki Git’i kullanmayı öğrenmiş, ikincisi Git’le neyi ne zaman yapacağına karar vermeyi öğrenmiş.
Bu yazı temel komutlar üzerine değil. add, commit, push, pull zaten biliniyor. Bu yazı, kararlar ve az bilinen ama hayat kurtaran parçalar üzerine: hangi workflow’u seçeceğine, tarihçeni nasıl temiz tutacağına, hangi commit’in bug’ı soktuğunu nasıl bulacağına, force-push’tan nasıl kurtulacağına dair.
Daha temel branch/merge/PR disiplini için daha önce yazdığım Git akışı: branch, merge ve pull request disiplini yazıma bakabilirsin. Bu yazı onun bir üst katmanı.
1. Workflow Seçimi: GitFlow vs GitHub Flow vs Trunk-Based
“Git workflow’unuz ne?” sorusunun cevabı yoksa, bilmeden bir tane seçmişsiniz demektir — büyük ihtimalle yanlışını. Üç ana akış var:
GitFlow
Vincent Driessen’in 2010’da yayınladığı klasik model: main (production), develop (entegrasyon), feature/*, release/*, hotfix/* dalları. Her şey planlı, her yayın için ayrı release branch.
Ne zaman seçilir: Versiyonlu, planlı yayın takvimi olan ürünler. Mobile uygulamalar (App Store yayın döngüsü), enterprise yazılım, “v2.3.1” gibi numaralı sürümler dağıtan paket projeleri.
Ne zaman seçilmez: Web uygulamaları, sürekli deploy edilen sistemler. GitFlow’u SaaS’a uygulamak — release branch’ler çoğunlukla anlamsız, develop her zaman main’in 2-3 commit gerisinde takılır.
GitHub Flow
Sade model: main her zaman deploy edilebilir, her özellik için kısa ömürlü branch, PR ile merge, merge sonrası anında deploy. Release branch yok, develop branch yok.
Ne zaman seçilir: Küçük-orta takım (3-15 kişi), CI/CD kurulu, günde bir-iki kez deploy. Türkiye’deki agency’lerin ve startup’ların büyük çoğunluğu burada doğru karar verir.
Ne zaman seçilmez: 50+ kişilik takımlar — main’e PR sırası çok uzun olur. Veya hâlâ haftalık manuel deploy ediyorsanız — bu workflow ön koşulu “deploy bir saniyelik iş” olması.
Trunk-Based Development
Her geliştirici doğrudan main’e commit’ler (veya çok kısa ömürlü branch). Yarım kalan özellikler feature flag arkasında saklanır. Branch ömrü maksimum birkaç saat.
Ne zaman seçilir: Büyük takımlar (Google, Facebook, Spotify), continuous deployment kültürü, feature flag altyapısı kurulu. Yüksek disiplin gerektirir.
Ne zaman seçilmez: Feature flag altyapınız yoksa veya CI test coverage’ınız düşükse — main’i bozmak çok kolaylaşır.
Karar Matrisi
| Durum | Tavsiye |
|---|---|
| Mobile uygulama, App Store dağıtımı | GitFlow |
| SaaS, küçük-orta takım, günlük deploy | GitHub Flow |
| 3 kişilik freelance agency | GitHub Flow (ya da daha basit: PR’sız doğrudan) |
| 50+ geliştirici, deploy günde 10+ | Trunk-based |
| Versiyonlu paket / library | GitFlow (release branch yararlı) |
| Plansız, “ne lazımsa o” | GitHub Flow (varsayılan) |
Anti-pattern: Üçünü karıştırmak. “Hem develop branch’ımız var hem feature flag kullanıyoruz hem her özellik release branch’le çıkıyor” — bu üç ayrı disiplinin yarısını uygulamak, hiçbirinin avantajını almamak demek.
2. Tarihçe Hijyeni: Rebase ve Autosquash
PR açtın, review aldın, “şu değişikliği yap, şu typo’yu düzelt” yorumları geldi. Üç ayrı düzeltme commit’i atıp PR’ı update’ledin. Şimdi PR’da 1 anlamlı commit + 3 “address review” commit’i var. Merge sonrası git log bunu okuyacak.
Bunu çözmenin iki yolu var. Squash merge (GitHub’da tek tıkla) her şeyi tek commit’e indirir — ama bu da farklı bir aşırılık: 200 satırlık 3 mantıksal değişiklik tek commit’e sıkışır.
Doğru orta yol: interactive rebase + autosquash.
Fixup Commit Workflow
Review yorumunu adresliyorsun. Yeni commit yerine:
git add .
git commit --fixup <orijinal-commit-hash>
Bu komut fixup! <orijinal commit message> başlıklı bir commit oluşturur. Aynı şekilde 2-3 fixup daha atabilirsin.
PR mergea hazır olduğunda:
git rebase -i --autosquash main
Editör açıldığında her fixup, ait olduğu commit’in altına otomatik konumlanmış ve fixup olarak işaretlenmiş olarak gelir. Hiçbir şey değiştirmeden kaydet — Git fixup’ları ait oldukları commit’lerin içine yedirir. Sonuç: tarihçe git commit --fixup öncesi kadar temiz, ama review’da yapılan düzeltmeler ilgili commit’in parçası olmuş.
Bunu daha da kolaylaştırmak için:
git config --global rebase.autosquash true
Bu ayardan sonra git rebase -i her seferinde otomatik autosquash uygular. Bir daha hatırlamana gerek kalmaz.
Golden Rule
Public branch’lerde — main, develop, başkalarının çektiği herhangi bir branch — asla rebase yapma. Tarihçeyi yeniden yazmak, başkasının clone’unda bulunan commit hash’lerini değersizleştirir. Sonuç: merge conflict cehennemi.
Rebase senin private branch’inde yapılır. PR henüz merge edilmemişse, branch hâlâ senindir — rebase serbest. Merge edildikten sonra dokunma.
3. Bisect ile Bug Avı
Klasik senaryo: production’da bir regression var. Geçen hafta çalışıyordu, bugün çalışmıyor. Aradaki 80 commit’in hangisi soktu? Manuel binary search yapacaksan 4 saatlik bir iş. Bisect ile 6 dakika.
Manuel Bisect
git bisect start
git bisect bad HEAD # şu an bozuk
git bisect good v2.3.0 # bu sürümde çalışıyordu
Git seni iki commit’in ortasındaki commit’e götürür. Test edersin:
- Bozuk:
git bisect bad - Çalışıyor:
git bisect good
Her cevap arama alanını yarıya indirir. 80 commit için ~7 adımda doğru commit’i bulur.
Bitirince:
git bisect reset
Otomatik Bisect
Eğer “bozuk mu çalışıyor mu” sorusu bir komutla cevaplanabiliyorsa (örn. bir test):
git bisect start HEAD v2.3.0
git bisect run npm test -- --testPathPattern=image-upload
Git senin yerine her commit’te testi çalıştırır, exit code 0 ise “good”, değilse “bad” sayar. Sen bir kahve içersin, 80 commit içinden bug’ı sokan commit’i ekranda görürsün.
Bisect’in en güzel tarafı: Hangi commit’in bug’ı soktuğunu bulduğunda, o commit’in author’ı, mesajı ve tam değişiklik kümesi gözünün önündedir. Git blame’in tek satıra bakması yerine, bisect bütün hikâyeyi verir.
4. Reflog: “Ben Commit’lerimi Kaybettim” Anları
Reset —hard yaptın. Wrong branch’e merge ettin. Force-push yedin. “Commit’lerim gitti.” Hayır, gitmedi.
Git, HEAD’in son ~90 günlük tüm hareketlerini reflog’da tutar. Hiçbir commit gerçekten silinmez — sadece ona referans veren branch/tag kalmaz. Reflog ile geri bulunabilir.
git reflog
Çıktı şuna benzer:
abc1234 HEAD@{0}: reset: moving to HEAD~3
def5678 HEAD@{1}: commit: addressed review feedback
ghi9012 HEAD@{2}: commit: refactor user service
jkl3456 HEAD@{3}: pull: Fast-forward
“Sildiğini sandığın” commit def5678. Geri getirmek için:
git checkout def5678
git switch -c kurtarilan-branch
veya doğrudan branch oluştur:
git branch kurtarilan-branch def5678
Önemli: Reflog lokaldir. Sadece kendi makinendeki Git’in tuttuğu kayıt. Eğer başkasının force-push’u sebebiyle commit kaybettiysen, onların reflog’una bakmak gerekir.
İkinci önemli nokta: garbage collection eninde sonunda gerçekten siler. Varsayılan: reflog 90 gün, “unreachable” commit’ler 30 gün sonra GC’lenir. Yani “geçen yıl yaptığım bir şeyi” kurtarmak için reflog güvenli değil.
5. Worktree: Aynı Repo, Birden Fazla Klasör
Bir feature üzerinde çalışıyorsun, branch’inde yarım kalmış değişiklikler var. Birden acil bir hotfix gerekti — main’e geçmen lazım ama mevcut işini kaybetmek istemiyorsun.
Klasik çözüm: git stash, branch değiştir, hotfix, geri dön, stash pop. İşliyor ama:
- Stash’ler birikiyor
- IDE state’i kaybediyor
- Açık dosyalar değişiyor
- Hangi stash neyin diye unutuyorsun
Daha iyi yol: worktree.
git worktree add ../hotfix-2026-05 main
Bu komut, aynı repo için ama farklı klasörde ve main branch’inde ikinci bir çalışma alanı oluşturur. Feature branch’in dokunulmamış kalır, IDE’de iki ayrı klasör açabilirsin, hotfix’i bitirip:
cd ../hotfix-2026-05
# çalış, commit, push
cd ../proje
git worktree remove ../hotfix-2026-05
Worktree’leri listelemek:
git worktree list
Tipik kullanım senaryoları:
- Code review için PR branch’ini ayrı klasörde çekme (kendi work’üne dokunmadan)
- Birden fazla feature paralel geliştirme
- Acil hotfix için anlık ortam
- Eski bir release’e bakmak gerektiğinde (
git worktree add ../v2.3 v2.3.0)
Bir kez kullanmaya başlayınca stash’ten daha çok worktree açtığını fark edersin.
6. Signed Commits: GPG ve SSH
Git, commit’lere yazılan author bilgisini kontrol etmez. git config user.email "linus@kernel.org" yazıp commit atabilirsin — Git itiraz etmez. Bu, supply chain saldırılarının en sevdiği açık.
Çözüm: commit imzalama. Her commit’i kendi anahtarınla imzalarsın, GitHub/GitLab onu doğrular ve “Verified” rozetiyle gösterir.
İki yöntem var.
GPG ile İmzalama (klasik)
gpg --full-generate-key
gpg --list-secret-keys --keyid-format=long
git config --global user.signingkey <KEY_ID>
git config --global commit.gpgsign true
GPG public key’i GitHub’a yüklersin (Settings → SSH and GPG keys). Bundan sonra her commit otomatik imzalanır.
Sorun: GPG kurulumu, key management, expire eden key’ler, başka cihaza taşırken çıkan zorluklar. Çoğu geliştirici “GPG sonra bakarım” diye erteler ve hiç imzalanmamış commit atmaya devam eder.
SSH ile İmzalama (modern)
Git 2.34+ ve GitHub 2022 sonrası: SSH key’inle commit imzalayabilirsin. Zaten Git push için kullandığın SSH key’i.
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
GitHub’da: Settings → SSH and GPG keys → “New SSH key” → key type’ı Signing Key seç (Authentication key ayrı).
Bu kadar. GPG’nin tüm kurulum zorluğunu atlamış olursun. 2024 sonrası başlayan biri için SSH signing varsayılan seçim olmalı.
Doğrulamak için:
git log --show-signature -1
Çıktıda Good "git" signature for ... görmen gerekir.
7. Tehlikeli Komutlar: --force Disiplini
Üç komut iş kaybettirebilir:
git push --force yerine --force-with-lease
--force upstream’i hiç sormadan ezer. Eğer branch’ten başka biri bir commit pushladıysa, onun commit’i kaybolur.
# Tehlikeli
git push --force
# Güvenli
git push --force-with-lease
--force-with-lease upstream’in son hâlinin senin son fetch ettiğin hâlle aynı olduğunu kontrol eder. Birisi araya commit attıysa push reddedilir, “tekrar fetch et” denir.
Bunu da otomatikleştirmek için alias:
git config --global alias.pushf 'push --force-with-lease'
git reset --hard
Working directory + index + HEAD’i hedef commit’e sıfırlar. Kaydetmediğin değişiklikler gider. Reflog’a girmez — yani geri alınamaz.
Alternatif yaklaşımlar:
- Sadece commit’i geri alacaksan:
git reset --soft HEAD~1(değişiklikleri staging’de bırakır) - Working directory’yi temizleyeceksen: önce
git stashile yedek al, sonra reset
git clean -fd
Working directory’deki tracked olmayan dosyaları siler. .gitignore’da olanları da silmek için -x ekler. Yanlış parametreyle çağrılırsa node_modules veya local config’ler uçar.
Önce git clean -nfd (dry run) çalıştır, ne silineceğini gör; sonra -f ile gerçek silmeyi yap.
8. Az Bilinen Güçlü Komutlar
git rerere — “Reuse Recorded Resolution”
Aynı merge conflict’i defalarca çözüyorsan (uzun süreli feature branch + sık main rebase senaryosu klasik), rerere bir kez çözdüğün conflict’i hatırlar, bir sonraki sefer otomatik uygular:
git config --global rerere.enabled true
Bir defa açtıktan sonra unutursun, sessizce arka planda çalışır. Bir hafta sonra fark edersin: “Bu conflict’i çözdüğümü hatırlamıyorum ama Git çözmüş?”
git log -S ve git log -G — Pickaxe
Bir kod parçasının ne zaman ve hangi commit ile eklendiğini/silindiğini bulmak için:
git log -S"OldFunctionName" # belirli stringin commit'lerini bul
git log -G"regex_pattern" # regex ile ara
“Bu deprecated fonksiyon ne zaman kaldırılmıştı?” sorusunun en hızlı cevabı bu. Tüm tarihçeyi tarar, geriye dönük arkeoloji yapar.
git blame -L
Tüm dosya değil, belirli satır aralığını blame’le:
git blame -L 45,80 src/Service/PaymentGateway.php
Büyük dosyalarda altın değerinde — sadece ilgilendiğin bölümün yazılış hikâyesini görürsün.
git switch ve git restore
Git çok eskiden beri git checkout’u iki farklı iş için kullanıyor: branch değiştirme ve dosya geri alma. Git 2.23 (2019) bunları ayırdı:
git switch main # branch değiştir
git switch -c yeni-feature # yeni branch oluştur ve geç
git restore dosya.php # working directory'deki değişikliği geri al
git restore --staged dosya.php # staging'den geri al (unstage)
checkout hâlâ çalışıyor ama yeni kod yazarken switch/restore kullanmak daha net. Junior’lara yeni komutları öğretmek, eski checkout’un iki anlamlı kullanımının yarattığı karışıklığı engelliyor.
Kapanış
Git’i öğrenmek bir kerelik bir iş değil. İlk yıl add, commit, push öğrenirsin. İkinci yıl branch, merge, rebase. Beşinci yıl bisect, reflog, worktree. On beşinci yıl rerere, pickaxe, signed commits. Komutlar 20 yaşında ama her geçen yıl yeni bir tane öğreniyorsun çünkü her yeni senaryo yeni bir araç gerektiriyor.
Bu yazıda işlediğim komutların hiçbiri yeni değil — bisect 2005’ten beri var. Yeni olan, onları ne zaman kullanacağını bilmek. Senior bir geliştiriciyi diğerlerinden ayıran şey komut sayısı değil; doğru komutu doğru anda hatırlamak.
Git temelleri için 2022’de yazdığım Medium serisine bakabilirsin — kurulumdan branch ve cherry-pick’e kadar başlangıç-orta seviye komutlar orada. Bu yazı onun üstüne, yıllar içinde gerçek production sorunlarının öğrettiği parçaları topluyor.
Yeni bir Git komutu öğrendiğinde — bu hafta öğreneceğin bir tane var — onu ~/.gitconfig’inde alias olarak kaydet. İki ay sonra adını unutursun, alias unutmaz.
Komut Hızlı Referansı
| İhtiyaç | Komut |
|---|---|
| Review düzeltmesi commit’i | git commit --fixup <hash> |
| Fixup’ları yedir | git rebase -i --autosquash main |
| Bug’ı hangi commit soktu? | git bisect start → bad/good |
| Otomatik bisect | git bisect run <test-command> |
| Kaybolan commit’i bul | git reflog |
| Paralel çalışma alanı | git worktree add <path> <branch> |
| SSH ile imzalama | git config gpg.format ssh |
| Güvenli force push | git push --force-with-lease |
| Conflict’leri hatırla | git config rerere.enabled true |
| String’in commit’ini bul | git log -S"text" |
| Satır aralığı blame | git blame -L 10,50 file |
| Modern branch geçişi | git switch <branch> |