# 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.

- Yayın: 2026-05-23
- Kategori: Araçlar & Teknolojiler
- Etiketler: Git
- Okuma süresi: 10 dk okuma
- Kaynak: https://www.muhammetsafak.com.tr/blog/production-git-senior-pratik-rehberi/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
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](/blog/git-akisi-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ığı](https://nvie.com/posts/a-successful-git-branching-model/) 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:

```bash
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:

```bash
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:

```bash
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

```bash
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:

```bash
git bisect reset
```

### Otomatik Bisect

Eğer "bozuk mu çalışıyor mu" sorusu bir komutla cevaplanabiliyorsa (örn. bir test):

```bash
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.

```bash
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:

```bash
git checkout def5678
git switch -c kurtarilan-branch
```

veya doğrudan branch oluştur:

```bash
git branch kurtarilan-branch def5678
```

**Önemli:** Reflog **lokal**dir. 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**.

```bash
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:

```bash
cd ../hotfix-2026-05
# çalış, commit, push
cd ../proje
git worktree remove ../hotfix-2026-05
```

Worktree'leri listelemek:

```bash
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)

```bash
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.

```bash
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:

```bash
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.

```bash
# 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:

```bash
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 stash` ile 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:

```bash
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:

```bash
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:

```bash
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ı:

```bash
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](https://medium.com/@muhametsafak/gi%CC%87t-versiyon-kontrol-sistemi-1-giri%C5%9F-kurulum-ve-yap%C4%B1land%C4%B1rma-2a5cdab1486b) 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>` |
