Git İleri Seviye: Rebase, Cherry-Pick, Stash ve Profesyonel İş Akışları
Git İleri Seviye: Rebase, Cherry-Pick, Stash ve Profesyonel İş Akışları
Git kullanmayan yazılımcı yok gibi. Ama çoğumuz git add, git commit, git push üçlüsünün ötesine pek geçmiyoruz. Sonra bir gün takım büyüyor, branch'ler çoğalıyor, merge conflict'ler patlamaya başlıyor — ve "keşke Git'i daha iyi bilseydim" diyorsun.
Bu yazıda Git'in gerçek gücünü keşfedeceğiz. Rebase ile commit geçmişini temizlemeyi, cherry-pick ile tek bir commit'i başka branch'e taşımayı, stash ile yarım kalan işleri park etmeyi ve git bisect ile bug'ın tam olarak hangi commit'te girdiğini bulmayı öğreneceğiz. Teoriden çok pratiğe odaklanacağız — her komutun neden ve ne zaman kullanılacağını gerçek senaryolarla göstereceğiz.
Git Nasıl Düşünür? — Commit Grafiği
İleri seviye Git komutlarını anlamak için önce Git'in veriyi nasıl sakladığını bilmek lazım. Git'i bir fotoğraf albümü gibi düşün: her commit, projenin o anki halinin tam bir fotoğrafı (snapshot). Bu fotoğraflar birbirine zincir gibi bağlı — her commit, kendisinden önceki commit'i (parent) işaret eder.
A --- B --- C --- D (main)Burada A ilk commit, D en son commit. Branch'ler ise sadece birer işaretçi (pointer). main branch'i aslında D commit'ine işaret eden bir etiket. Yeni bir branch oluşturduğunda, sadece yeni bir işaretçi yaratıyorsun:
A --- B --- C --- D (main)
\
E --- F (feature)feature branch'i F'yi, main branch'i D'yi gösteriyor. Bu kadar basit. Bu zihinsel modeli oturtunca rebase, cherry-pick gibi kavramlar çok daha mantıklı hale geliyor.
Merge vs Rebase — Büyük Tartışma
İki branch'i birleştirmenin iki yolu var: merge ve rebase. İkisi de aynı sonucu verir (kodlar birleşir) ama commit geçmişi çok farklı görünür.
Merge: Olduğu Gibi Birleştir
# feature branch'indeyken main'deki değişiklikleri al
git checkout feature
git merge mainMerge, iki branch'in geçmişini olduğu gibi korur ve yeni bir "merge commit" oluşturur:
A --- B --- C --- D --- G (main)
\ /
E --- F (feature)G bir merge commit — iki parent'ı var: D ve F. Geçmiş tamamen korunuyor ama karmaşık projelerde bu diyagram spagetti gibi oluyor.
Rebase: Geçmişi Yeniden Yaz
# feature branch'indeyken
git checkout feature
git rebase mainRebase, feature branch'indeki commit'leri alıp main'in en sonuna "tekrar uygular". Sanki feature branch'ini main'in ucundan yeni başlatmışsın gibi:
A --- B --- C --- D (main)
\
E' --- F' (feature)E' ve F', orijinal E ve F ile aynı değişiklikleri içerir ama farklı commit hash'leri vardır. Çünkü parent'ları değişti, dolayısıyla hash de değişti.
Sonuç: Düz, temiz, okunabilir bir commit geçmişi.
Ne Zaman Hangisini Kullanmalı?
| Durum | Tercih | Neden |
|---|---|---|
| Kendi feature branch'ini güncelleme | Rebase | Temiz geçmiş, gereksiz merge commit yok |
| Feature branch'i main'e birleştirme | Merge (veya squash merge) | Hangi feature'ın ne zaman girdiği belli olsun |
| Paylaşılan branch (main, develop) | Asla rebase | Başkalarının geçmişini bozarsın |
| Açık PR'da main'i alma | Rebase | PR temiz kalır, reviewer mutlu olur |
Altın kural: Başkasının da üzerinde çalıştığı bir branch'i asla rebase etme. Rebase commit hash'lerini değiştirir — diğer geliştiricilerin local kopyasıyla uyumsuzluk yaratır ve kaos çıkar.
Interactive Rebase — Commit Geçmişinin Cerrahı
Interactive rebase (git rebase -i), Git'in en güçlü özelliklerinden biri. Commit'leri birleştirebilir, sırasını değiştirebilir, mesajlarını düzenleyebilir ve hatta silebilirsin.
Diyelim ki bir feature üzerinde çalışırken şöyle commit'ler attın:
git log --oneline
# f4a2b1c WIP: login formu başladı
# 8e3d7a2 fix typo
# 1b5c9e0 login formu tamamlandı
# 3d7f2a8 validation eklendi
# 9c1e4b6 test yazdım ama bozuk
# 2a8f3d1 testler düzeltildiBu geçmiş dağınık. PR'a böyle göndersen reviewer kafayı yer. Interactive rebase ile temizleyelim:
# Son 6 commit'i interactive rebase ile düzenle
git rebase -i HEAD~6Bir editör açılır ve şöyle bir liste görürsün:
pick f4a2b1c WIP: login formu başladı
pick 8e3d7a2 fix typo
pick 1b5c9e0 login formu tamamlandı
pick 3d7f2a8 validation eklendi
pick 9c1e4b6 test yazdım ama bozuk
pick 2a8f3d1 testler düzeltildiŞimdi bu listeyi düzenle:
pick f4a2b1c WIP: login formu başladı
fixup 8e3d7a2 fix typo
squash 1b5c9e0 login formu tamamlandı
pick 3d7f2a8 validation eklendi
pick 9c1e4b6 test yazdım ama bozuk
fixup 2a8f3d1 testler düzeltildiİşte komutların anlamları:
pick: Commit'i olduğu gibi tut
squash (s): Bir önceki commit ile birleştir, commit mesajını düzenleme şansı ver
fixup (f): Squash gibi ama commit mesajını sil (öncekini kullan)
reword (r): Commit'i tut ama mesajını değiştir
edit (e): Commit'te dur, değişiklik yapayım
drop (d): Commit'i tamamen sil
Kaydettiğinde Git bu işlemleri sırayla uygular. Sonuçta 6 commit yerine 3 temiz commit kalır:
git log --oneline
# a1b2c3d feat: login formu ve validation eklendi
# 4e5f6a7 feat: login validation eklendi
# 7b8c9d0 test: login testleri eklendiPratik Senaryo: Commit Mesajını Düzeltme
# Son commit'in mesajını değiştir (en basit yol)
git commit --amend -m "feat: kullanıcı login formu eklendi"
# Daha eskiye gitmek lazımsa interactive rebase kullan
git rebase -i HEAD~3
# İlgili commit'in başına "reword" yaz, kaydet, yeni mesajı gir⚠️ Dikkat: --amend ve interactive rebase, commit hash'lerini değiştirir. Eğer commit'leri zaten push ettiysen, git push --force-with-lease kullanman gerekir. --force-with-lease, düz --force'tan daha güvenlidir çünkü remote'da senden sonra başka biri push ettiyse işlemi reddeder.
Cherry-Pick — Tek Commit Transferi
Bazen bir branch'teki tek bir commit'i başka bir branch'e taşımak istersin. Tüm branch'i merge etmek istemezsin — sadece o spesifik değişiklik lazımdır. İşte cherry-pick tam bunu yapar.
Gerçek Senaryo
feature/payment branch'inde çalışırken kritik bir bug fix yaptın. Bu fix'in hemen main'e gitmesi lazım ama payment feature'ının tamamı henüz hazır değil.
# Önce fix'in commit hash'ini bul
git log --oneline feature/payment
# 7a3b2c1 feat: ödeme sayfası UI
# 4d5e6f7 fix: null pointer exception - kullanıcı servisi
# 1g2h3i4 feat: ödeme akışı başlangıç
# Sadece bug fix commit'ini main'e taşı
git checkout main
git cherry-pick 4d5e6f7Git, 4d5e6f7 commit'indeki değişiklikleri alır ve main branch'ine yeni bir commit olarak uygular. Yeni commit farklı bir hash'e sahip olur ama içeriği aynıdır.
Birden Fazla Commit Cherry-Pick
# Ardışık commit'ler (A dahil değil, B dahil)
git cherry-pick A..B
# Belirli commit'ler (ardışık olmak zorunda değil)
git cherry-pick abc1234 def5678 ghi9012
# Commit'i uygula ama commit etme (staging'de bırak)
git cherry-pick --no-commit abc1234Cherry-Pick'te Conflict Çıkarsa
# Conflict çıktığında Git durur ve sana söyler
# Conflict'i çöz, sonra:
git add .
git cherry-pick --continue
# Vazgeçmek istersen:
git cherry-pick --abort💡 İpucu: Cherry-pick'i çok sık kullanıyorsan, branch stratejinde bir sorun olabilir. Aynı fix'in birden fazla branch'e cherry-pick edilmesi, ileride merge conflict'lere davetiye çıkarır. Mümkünse düzgün bir branching stratejisi ile bu ihtiyacı azalt.
Git Stash — İşleri Park Et
Bir feature üzerinde çalışıyorsun, yarım kaldı, commit atacak durumda değil. Tam o sırada acil bir bug fix için başka branch'e geçmen lazım. Ama git checkout dersen Git "uncommitted changes" diye şikayet edecek. İşte stash burada devreye girer.
Stash, yarım kalan değişikliklerini geçici bir rafa kaldırır. Working directory tertemiz olur, istediğin branch'e geçersin, işini halledersin, sonra stash'i geri alırsın.
# Değişiklikleri stash'le (rafa kaldır)
git stash
# Mesaj ekleyerek stash'le (önerilir — sonra hangisi hangisiydi bul)
git stash save "login formu yarım kaldı - validation eksik"
# Veya modern syntax
git stash push -m "login formu yarım kaldı - validation eksik"
# Untracked (yeni oluşturulan) dosyaları da dahil et
git stash push -u -m "yeni dosyalar dahil"Stash Listesini Yönet
# Stash listesini gör
git stash list
# stash@{0}: On feature/login: login formu yarım kaldı - validation eksik
# stash@{1}: On main: deneysel CSS değişiklikleri
# En son stash'i geri al ve listeden sil
git stash pop
# En son stash'i geri al ama listeden silme
git stash apply
# Belirli bir stash'i geri al
git stash apply stash@{1}
# Stash'in içeriğine bak (uygulamadan önce)
git stash show stash@{0} # Özet
git stash show -p stash@{0} # Detaylı diff
# Belirli bir stash'i sil
git stash drop stash@{1}
# Tüm stash'leri temizle
git stash clearStash'ten Branch Oluşturma
Bazen stash'lediğin değişiklikleri geri almak conflict çıkarır (çünkü o arada dosyalar değişmiştir). Bu durumda stash'ten direkt yeni bir branch oluşturabilirsin:
# stash@{0}'dan yeni branch oluştur
git stash branch yeni-feature-branch stash@{0}Git, stash'in yapıldığı commit'ten bir branch oluşturur ve stash'i uygular. Conflict riski sıfır.
⚠️ Dikkat: Stash'leri uzun süre biriktirme. Birkaç günden eski stash'ler genellikle artık neye yaradığını hatırlamadığın "hayalet değişiklikler" haline gelir. Ya commit at (WIP commit bile olur, sonra squash edersin) ya da stash'i hemen işle.
Git Bisect — Bug Dedektifi
Projen düzgün çalışıyordu, birden bozuldu. Ama tam olarak hangi commit'te bozulduğunu bilmiyorsun. Binlerce commit arasından suçluyu bulmak samanlıkta iğne aramak gibi görünebilir. Ama git bisect binary search (ikili arama) algoritmasıyla bu işi inanılmaz hızlı yapar.
1000 commit varsa, en fazla 10 adımda (log₂1000 ≈ 10) suçlu commit'i bulursun.
Adım Adım Bisect
# Bisect'i başlat
git bisect start
# Şu anki commit bozuk
git bisect bad
# Bu commit'te her şey düzgün çalışıyordu (örneğin 2 hafta önceki tag)
git bisect good v2.3.0
# Git ortadaki bir commit'e checkout yapar
# Test et: bug var mı yok mu?
# Bug varsa:
git bisect bad
# Bug yoksa:
git bisect good
# Git tekrar ortaya gider... Ta ki suçlu commit'i bulana kadar
# "4d5e6f7 is the first bad commit" — buldu!
# Bisect'ten çık
git bisect resetOtomatik Bisect — Script ile
Her adımda elle test etmek yerine, bir test scripti vererek bisect'i tamamen otomatikleştirebilirsin:
# Script 0 dönerse "good", 0 dışı dönerse "bad"
git bisect start HEAD v2.3.0
git bisect run ./test-login.shtest-login.sh basit bir script olabilir:
#!/bin/bash
# Projeyi derle
mvn compile -q 2>/dev/null
if [ $? -ne 0 ]; then
exit 125 # 125 = skip (derlenmiyorsa bu commit'i atla)
fi
# Spesifik testi çalıştır
mvn test -pl user-service -Dtest=LoginServiceTest -q 2>/dev/null
# Test sonucu otomatik olarak exit code olur (0=pass, 1=fail)💡 İpucu: exit 125 özel bir koddur — "bu commit'i değerlendiremiyorum, atla" anlamına gelir. Derleme hatası olan commit'lerde kullanışlıdır.
Sık Yapılan Hatalar ve Kurtarma Yöntemleri
1. Yanlış Branch'e Commit Attım
# Henüz push etmediysen:
# Son commit'i geri al (değişiklikler staging'de kalır)
git reset --soft HEAD~1
# Doğru branch'e geç
git checkout dogru-branch
# Commit'i burada at
git commit -m "feat: doğru yere geldik"2. Push Ettikten Sonra Commit'i Geri Almak
# Revert: ters commit oluşturur (güvenli, paylaşılan branch'ler için)
git revert abc1234
# Bu, abc1234'ün yaptığı değişiklikleri geri alan YENİ bir commit oluşturur
# Geçmiş bozulmaz, herkes güvendeReset ile revert arasındaki fark: reset geçmişi siler (sanki o commit hiç olmamış gibi), revert geçmişe yeni bir "geri alma" commit'i ekler. Paylaşılan branch'lerde her zaman revert kullan.
3. Commit Mesajını Değiştirmek (Push Edilmiş)
# Son commit'i düzelt
git commit --amend -m "yeni mesaj"
git push --force-with-lease
# force-with-lease: remote'da değişiklik yoksa push et,
# varsa dur (düz --force'tan güvenli)4. Silinen Branch'i Kurtarma
# Git hiçbir şeyi hemen silmez — reflog her şeyi hatırlar
git reflog
# abc1234 HEAD@{5}: commit: feature tamamlandı
# O commit'ten branch oluştur
git branch kurtarilan-branch abc12345. Tüm Local Değişiklikleri Çöpe At
# Tracked dosyalardaki değişiklikleri geri al
git checkout -- .
# Untracked dosyaları da sil
git clean -fd
# Nuclear option — her şeyi remote'un durumuna sıfırla
git fetch origin
git reset --hard origin/main⚠️ Dikkat: reset --hard ve clean -fd geri dönüşü olmayan işlemler. Commit edilmemiş çalışman varsa kaybolur. Emin olmadan kullanma.
Reflog — Git'in Kara Kutusu
Git reflog (reference log), HEAD'in ve branch'lerin geçmişte nereye işaret ettiğinin kaydını tutar. Bir şeyleri bozduğunda, reflog hayat kurtarır.
# Reflog'u görüntüle
git reflog
# Çıktı:
# 2a8f3d1 HEAD@{0}: rebase -i (finish): returning to refs/heads/feature
# 9c1e4b6 HEAD@{1}: rebase -i (squash): feat: login eklendi
# f4a2b1c HEAD@{2}: rebase -i (start): checkout HEAD~6
# 7d4e5f6 HEAD@{3}: commit: testler düzeltildi
# ...
# Herhangi bir noktaya geri dönebilirsin
git reset --hard HEAD@{3}Reflog verileri varsayılan olarak 90 gün tutulur. Yani bir şeyleri bozduktan 90 gün içinde kurtarma şansın var.
Profesyonel Branch Stratejileri
Git Flow
Büyük, sürüm bazlı projeler için (mobil uygulamalar, paketlenmiş yazılım):
main ─────────────────────────────────────────
\ /
develop ────────────────
\ / \ /
feature-1 feature-2
\
release/1.0
\
hotfix/login-bugmain: Production kodu, her zaman stabildevelop: Aktif geliştirmefeature/*: Her özellik için ayrı branchrelease/*: Sürüm hazırlığıhotfix/*: Acil düzeltmeler
Trunk-Based Development
Küçük-orta takımlar, sürekli deploy eden projeler için:
main ──●──●──●──●──●──●──●──●──●
| | |
└─f1──┘ └─f2──┘
(kısa ömürlü feature branch'ler, max 1-2 gün)main(trunk) her zaman deploy edilebilir durumdaFeature branch'ler çok kısa ömürlü (1-2 gün max)
Sık sık main'e merge (günde en az 1 kez)
Feature flag'ler ile yarım feature'lar production'da gizlenir
Hangisini seçmeli? Takımın küçükse ve sürekli deploy ediyorsan → trunk-based. Sürüm döngülerin varsa ve takım büyükse → Git Flow. Çoğu modern web projesi için trunk-based development daha pratiktir.
Faydalı Git Alias'ları
Sık kullandığın komutları kısaltmak hayatı kolaylaştırır:
# ~/.gitconfig dosyasına ekle
git config --global alias.st "status -sb"
git config --global alias.lg "log --oneline --graph --all --decorate"
git config --global alias.co "checkout"
git config --global alias.br "branch"
git config --global alias.cm "commit -m"
git config --global alias.undo "reset --soft HEAD~1"
git config --global alias.amend "commit --amend --no-edit"
git config --global alias.stash-all "stash push -u"Kullanımı:
git st # git status -sb
git lg # güzel formatlı log grafiği
git undo # son commit'i geri al (değişiklikler kalır)
git amend # son commit'e dosya ekle (mesaj değişmez)
git stash-all # untracked dahil stash'legit lg komutu özellikle muhteşem — branch'lerin nasıl ayrılıp birleştiğini renkli bir ASCII grafiğiyle gösterir. Bunu kullanmaya başlayınca git log'a bir daha dönmezsin.
Best Practices — Profesyonel Git Kullanımı
1. Commit Mesajları Anlamlı Olsun
# KÖTÜ
git commit -m "fix"
git commit -m "güncelleme"
git commit -m "asdfgh"
# İYİ (Conventional Commits formatı)
git commit -m "feat: kullanıcı kayıt formu eklendi"
git commit -m "fix: login sırasında null pointer hatası düzeltildi"
git commit -m "refactor: UserService dependency injection'a geçirildi"
git commit -m "docs: API endpoint dokümantasyonu güncellendi"
git commit -m "test: PaymentService unit testleri eklendi"Prefixler: feat, fix, refactor, docs, test, chore, style, perf. Bu format, otomatik changelog oluşturmayı da mümkün kılar.
2. Küçük ve Atomik Commit'ler At
Her commit tek bir mantıksal değişiklik içermeli. "Login formu + veritabanı migration + CSS düzeltmesi" tek commit'te olmamalı. Neden? Çünkü:
Cherry-pick ile sadece birini almak isteyebilirsin
Revert ile sadece birini geri almak isteyebilirsin
Code review'da değişiklikleri anlamak kolaylaşır
git bisectdaha hassas sonuç verir
3. Main'i Asla Doğrudan Değiştirme
Her değişiklik branch'ten gelsin, PR/MR üzerinden review edilsin. Bu sadece büyük takımlar için değil — tek kişilik projelerde bile alışkanlık olarak yap.
4. .gitignore'u İlk Günden Doğru Kur
# IDE dosyaları
.idea/
.vscode/
*.iml
# Build çıktıları
target/
build/
dist/
node_modules/
# Ortam dosyaları — bunları ASLA commit etme
.env
.env.local
*.key
*.pem
# OS dosyaları
.DS_Store
Thumbs.dbBir kere commit'lenen dosyayı .gitignore'a eklemek yetmez — Git onu hâlâ takip eder. Önce tracking'den çıkarman lazım:
# Dosyayı Git'in takibinden çıkar (dosya silinmez)
git rm --cached .env
echo ".env" >> .gitignore
git commit -m "chore: .env dosyası tracking'den çıkarıldı"5. Sık Pull, Sık Push
Değişikliklerin local'de birikmesine izin verme. Sık pull et ki conflict'ler küçük kalsın. Sık push et ki çalışman kaybolmasın.
# Pull yaparken rebase kullan (gereksiz merge commit önlenir)
git pull --rebase origin mainBunu varsayılan yapmak istersen:
git config --global pull.rebase trueSonuç
Git, yüzeyden baktığında basit bir versiyon kontrol aracı gibi görünür ama derinlerine indikçe inanılmaz güçlü bir araç olduğunu fark edersin. Bu yazıda öğrendiklerimizi özetleyelim:
Rebase, commit geçmişini temiz ve okunabilir tutar — kendi branch'lerinde özgürce kullan ama paylaşılan branch'lerde asla
Interactive rebase, dağınık commit'leri PR'a göndermeden önce düzenlemenin en iyi yolu
Cherry-pick, acil durumlarda tek bir commit'i başka branch'e taşımanı sağlar
Stash, yarım kalan işleri güvenle park eder — ama uzun süre biriktirme
Bisect, binlerce commit arasından bug'ı binary search ile bulur
Reflog, her şeyi bozduğunda bile kurtarma şansı verir — Git'in kara kutusu
Conventional commits ve atomik commit alışkanlığı, uzun vadede projenin sürdürülebilirliğini artırır
Bu komutları öğrenmenin en iyi yolu pratik yapmak. Bir test repository'si oluştur, branch'ler yarat, bilerek karıştır, kurtarmayı dene. Gerçek projende ihtiyaç duyduğunda parmaklarının komutları tanımasını istiyorsan, kas hafızası oluşturmalısın.
Artık git add-commit-push döngüsünden çıkıp Git'in gerçek potansiyelini kullanabilecek seviyedesin. İyi commit'ler!
Bu yazıyı beğendiniz mi?
Bültene abone olun ve yeni yazılardan ilk siz haberdar olun. Spam yok, söz.
İlgili Yazılar
Git Nedir? Versiyon Kontrol Sistemi Başlangıç Rehberi
Git nedir, neden kullanılır, temel kavramları nelerdir? Repository, commit, branch, merge, remote kavramlarını, temel ko...
Docker ile Spring Boot Uygulaması Deploy Etmek
Spring Boot uygulamalarını Docker ile containerize etmenin adım adım rehberi. Multi-stage build, docker-compose, health...
Docker Multi-Stage Build ile Küçük ve Güvenli Image'lar
Docker multi-stage build ile image boyutunu 800MB'den 150MB'ye düşürün. Spring Boot, Node.js ve Go örnekleri ile product...