Git Rebase ile Temiz Commit Geçmişi Oluşturma
Commit geçmişine baktığınızda “WIP”, “düzelttim”, “bir daha düzelttim”, “şimdi oldu galiba” gibi mesajlar görüyorsanız, yalnız değilsiniz. Ekiplerin büyük çoğunluğu bunu yaşıyor. Ama bu geçmişi temizlemek, production’a almadan önce anlamlı hale getirmek mümkün. git rebase tam da bu iş için var ve doğru kullanıldığında hem kod review süreçlerini hem de uzun vadeli bakımı ciddi ölçüde kolaylaştırıyor.
Rebase Nedir, Merge’den Farkı Ne?
Merge işlemi iki branch’in geçmişini bir araya getirir ve bu birleşmeyi gösteren bir “merge commit” oluşturur. Tarih korunur ama zamanla iç içe geçmiş, takip edilmesi güç bir yapı ortaya çıkar. Rebase ise farklı çalışır: commit’lerinizi alır, hedef branch’in en son commit’inin üzerine yeniden uygular. Sanki feature branch’inizi baştan o noktadan başlayarak yazmış gibi görünür.
# Merge ile birleştirme - merge commit oluşturur
git checkout main
git merge feature/user-auth
# Rebase ile birleştirme - doğrusal geçmiş
git checkout feature/user-auth
git rebase main
git checkout main
git fast-forward merge feature/user-auth
Sonuç olarak merge, “iki yol birleşti” der. Rebase ise “sen zaten hep bu yoldan gittin” der. Hangisinin daha okunabilir olduğunu büyük projelerde bir git log --oneline --graph çıktısına bakarak kolayca anlayabilirsiniz.
Interactive Rebase: Asıl Güç Buradan Geliyor
Standart rebase bir branch’i başka bir branch’in üzerine taşır. Interactive rebase ise commit geçmişini düzenlemenizi sağlar. Squash, reword, edit, drop komutlarıyla commit’lerinizi yeniden şekillendirebilirsiniz.
# Son 4 commit'i interaktif olarak düzenle
git rebase -i HEAD~4
Bu komutu çalıştırdığınızda bir editör açılır ve şöyle bir şey görürsünüz:
pick a1b2c3d feat: kullanici kayit formu eklendi
pick e4f5g6h fix: form validasyon hatasi
pick i7j8k9l WIP - kayit islemi yarim kaldi
pick m1n2o3p düzelttim artık calısıyor
Bu noktada her commit için farklı bir aksiyon belirleyebilirsiniz:
- pick: Commit’i olduğu gibi tut
- reword: Commit mesajını değiştir ama içeriği koru
- edit: Commit’i duraklayarak değiştir
- squash: Önceki commit’le birleştir, mesajları birleştir
- fixup: Önceki commit’le birleştir ama mesajı sil
- drop: Commit’i tamamen sil
Gerçek Dünya Senaryosu: Feature Branch Temizliği
Bir e-ticaret projesinde ödeme modülü geliştiriyorsunuz. Üç günlük çalışma sonucunda şöyle bir geçmiş oluştu:
git log --oneline
# f3a9c12 artık düzgün çalışıyor
# 2b8e741 payment gateway bağlantısı
# 9d1f883 typo düzeltme
# 4c7a219 test ekledim
# 1e6b348 WIP stripe entegrasyonu
# 8a2d574 stripe sdk eklendi
# 3f9c021 main: son durum
Bu geçmişi PR açmadan önce temizlemek gerekiyor. İlk önce neyi squash edip neyi tutacağımızı planlıyoruz, sonra interactive rebase başlatıyoruz:
git rebase -i HEAD~6
Editörde şu değişiklikleri yapıyoruz:
pick 8a2d574 stripe sdk eklendi
squash 1e6b348 WIP stripe entegrasyonu
squash 2b8e741 payment gateway bağlantısı
squash f3a9c12 artık düzgün çalışıyor
pick 4c7a219 test ekledim
fixup 9d1f883 typo düzeltme
Kaydedip çıktıktan sonra squash edilen commit’lerin mesajlarını birleştirmemizi ister. Güzel bir commit mesajı yazarız:
feat: Stripe ödeme entegrasyonu eklendi
- Stripe SDK bağımlılığı package.json'a eklendi
- PaymentGateway servisi oluşturuldu
- Webhook handler implementasyonu yapıldı
- Ödeme başarı/hata akışları tanımlandı
Sonuç olarak 6 dağınık commit yerine 2 anlamlı commit kaldı. Biri implementasyonu, diğeri testleri temsil ediyor.
Conflict Yönetimi: Paniklemeden Devam Etmek
Rebase sırasında conflict çıkması oldukça normal. Merge’deki gibi toplu gelmez, commit commit gelir. Bu hem avantaj hem de dikkat gerektiren bir durum.
# Rebase başlattınız, conflict çıktı
git rebase main
# Auto-merging src/payment/stripe.js
# CONFLICT (content): Merge conflict in src/payment/stripe.js
# error: could not apply 8a2d574... stripe sdk eklendi
# Durumu kontrol edin
git status
# Conflict'i çözün, dosyayı düzenleyin
# Sonra çözülen dosyayı stage'e alın
git add src/payment/stripe.js
# Rebase'e devam edin - commit etmeyin!
git rebase --continue
Eğer bir noktada “bu conflict’i çözmek istemiyorum, geri döneyim” diyorsanız:
git rebase --abort
Bu komutu çalıştırdığınızda rebase öncesi duruma dönersiniz. Hiçbir şey kaybolmaz. Bu özelliği bilmek rebase yaparken özgüveninizi önemli ölçüde artırır.
Upstream’deki Değişikliklerle Senkronizasyon
Ekip ortamında en sık karşılaşılan senaryo şu: Main branch güncellendi, siz feature branch’inizde çalışmaya devam ediyorsunuz. Merge commit oluşturmadan güncel kalmak istiyorsunuz.
# Remote'u güncelleyin
git fetch origin
# Feature branch'inizdeyken main üzerine rebase yapın
git checkout feature/payment-module
git rebase origin/main
# Ya da daha kısa yoldan
git pull --rebase origin main
git pull --rebase özellikle bireysel çalışırken veya küçük ekiplerde çok kullanışlı. Default pull davranışını değiştirmek isteyenler için global config:
# Tüm pull işlemlerini varsayılan olarak rebase yapsın
git config --global pull.rebase true
# Sadece bu repo için
git config pull.rebase true
Bu ayardan sonra sıradan bir git pull komutu merge yerine rebase yapacak. Bazı ekipler bunu standart haline getiriyor, tartışmalı bir karar ama doğrusal geçmişi seviyorsanız işe yarıyor.
Autosquash ile Hayatı Kolaylaştırmak
Commit’lerinizi yazarken “bu commit ileride şu commit’e squash edilecek” diyebiliyorsunuz. --fixup ve --squash flag’leri bu iş için:
# Normal bir commit yaptınız
git commit -m "feat: kullanici profil sayfasi"
# Sonra bir hata fark ettiniz, düzelttiniz
git add src/profile.js
git commit --fixup HEAD
# Ya da commit hash ile
git commit --fixup a1b2c3d
Bu commit’in mesajı otomatik olarak fixup! feat: kullanici profil sayfasi olarak ayarlanır. Daha sonra interactive rebase’de --autosquash kullandığınızda bu commit’ler otomatik olarak doğru yere yerleştirilir:
git rebase -i --autosquash HEAD~5
Editör açıldığında fixup commit’ler zaten doğru pozisyona taşınmış ve fixup olarak işaretlenmiş şekilde gelir. Tek yapmanız gereken kaydetmek.
Force Push: Ne Zaman, Nasıl?
Rebase sonrası remote’a push etmek istediğinizde sorunla karşılaşırsınız. Commit hash’leri değiştiği için git normal push’u reddeder.
# Bu hata verir
git push origin feature/payment-module
# ! [rejected] feature/payment-module -> feature/payment-module (non-fast-forward)
# Force push gerekli
git push --force-with-lease origin feature/payment-module
--force değil, mutlaka --force-with-lease kullanın. Farkı kritik:
- –force: Remote’daki durumu umursamaz, üzerine yazar. Başkası aynı branch’e push ettiyse onların değişikliklerini siler.
- –force-with-lease: Remote’daki son bilinen durumla karşılaştırır. Başkası push ettiyse işlemi durdurur ve sizi uyarır.
Özellikle şunu kesinlikle yapmayın: main veya master branch’ine force push. Bu neredeyse her ekipte kırmızı çizgidir ve iş yerinizde sizi çok zor durumda bırakabilir.
# main'e force push - YAPMAYIN
git push --force-with-lease origin main # Hayır.
# Eğer force push gerekiyorsa (ve bunu yapmak zorundaysanız)
# önce ekiple konuşun, herkesin branch'i güncellemesini bekleyin
Branch Koruma Stratejisi
Büyük ekiplerde rebase stratejisini politika haline getirmek gerekiyor. Şöyle bir iş akışı sağlıklı çalışıyor:
# Developer kendi branch'inde çalışıyor
git checkout -b feature/inventory-system
# ... geliştirmeler, dağınık commit'ler ...
# PR açmadan önce main'i pull et, rebase yap
git fetch origin
git rebase origin/main
# Interactive rebase ile commit'leri temizle
git rebase -i origin/main
# Temizlenmiş branch'i push et
git push --force-with-lease origin feature/inventory-system
Bu akış her developer’ın PR açmadan önce uyguladığı bir ritual haline gelirse, main branch’in geçmişi her zaman temiz ve lineer kalır.
Git Rebase ile Log Okumayı Kolaylaştırmak
Temiz geçmişin gerçek değeri, bir şeyler ters gittiğinde ortaya çıkar. git bisect kullanıyorsanız, her commit’in bağımsız ve anlamlı olması hayat kurtarır.
# Hangi commit hatayı getirdi?
git bisect start
git bisect bad HEAD
git bisect good v2.1.0
# Git sizi otomatik olarak ortadaki commit'e götürür
# Test edin, iyi mi kötü mü söyleyin
git bisect good # ya da git bisect bad
# Sonunda hangi commit'in soruna yol açtığını bulursunuz
# 3f9c021 is the first bad commit
Eğer geçmişiniz “WIP WIP WIP düzelttim WIP” şeklindeyse bisect neredeyse kullanılamaz hale gelir. Anlamlı commit’ler bisect’i güçlü bir araç haline getirir.
Geçmişi görselleştirmek için şu alias’ları global config’e eklemek faydalı:
git config --global alias.lg "log --oneline --graph --decorate --all"
git config --global alias.hist "log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short"
# Kullanımı
git lg
git hist
Rebase Yaparken Sık Yapılan Hatalar
Paylaşılan branch’lerde rebase yapmak: Eğer bir branch’i birden fazla kişi kullanıyorsa o branch’te rebase yapmak ciddi sorunlara yol açar. Ekip arkadaşınızın çalışması kendi yerinde ama sizin yeniden yazdığınız geçmişle artık uyuşmuyor. “Golden rule of rebasing” denen şey budur: yalnızca kendi yerel ve paylaşılmamış commit’lerinizi rebase edin.
Squash’ı gereğinden fazla yapmak: Her şeyi tek commit’e squash etmek de iyi değil. Mantıksal olarak bağımsız değişiklikleri birbirinden ayırmak, gelecekte git revert yapmanız gerektiğinde hayat kurtarır.
Rebase sonrası test etmemek: Conflict çözdünüz, her şey güzel göründü ama uygulamayı test etmediniz. Özellikle çok sayıda conflict çözüldükten sonra tam test yapmadan push etmek riskli.
# Rebase sonrası her zaman testleri çalıştırın
git rebase origin/main
npm test # ya da pytest, cargo test, go test...
# Sadece o zaman push edin
git push --force-with-lease origin feature/my-feature
Rebase vs Merge: Hangi Durumda Ne Kullanmalı?
Her ikisinin de kullanım alanı var, dogma haline getirmemek gerekiyor.
Rebase tercih edin:
- Feature branch’inizi main ile senkronize ederken
- PR öncesi kendi commit’lerinizi temizlerken
- Doğrusal, takip edilmesi kolay bir geçmiş istediğinizde
- Küçük, kısa ömürlü feature branch’lerinde
Merge tercih edin:
- Public veya paylaşılan branch’leri birleştirirken
- Release branch’lerini main’e alırken
- Büyük özellik setlerinin geçmişini korumak istediğinizde
- “Bu değişiklik ne zaman geldi?” sorusunun cevabı önemliyse
Bazı ekipler “squash and merge” stratejisini tercih ediyor: Tüm PR’ı tek bir commit olarak main’e alıyorsunuz. GitHub, GitLab ve Bitbucket’ın hepsi bunu destekliyor. Bu strateji hem temiz geçmiş sağlıyor hem de rebase anlaşmazlıklarını ortadan kaldırıyor.
Sonuç
Git rebase, bir kez alıştıktan sonra vazgeçemeyeceğiniz araçlardan biri. İlk başta biraz ürkütücü gelebilir, özellikle “commit geçmişini değiştiriyorum, bir şeyleri kaybeder miyim?” kaygısı yaygın. Ama git reflog her commit’i 30 gün boyunca sakladığı için neredeyse her durumdan geri dönmek mümkün.
# Bir şeyler ters giderse, reflog'dan kurtarın
git reflog
# HEAD@{0}: rebase finished: returning to refs/heads/feature/payment
# HEAD@{1}: rebase: feat: stripe sdk eklendi
# HEAD@{2}: rebase: checkout origin/main
# HEAD@{3}: commit: WIP - yarım kalmis is
# İstediğiniz noktaya dönün
git reset --hard HEAD@{3}
Ekip içinde rebase kullanımını standart hale getirmek için önce küçük adımlarla başlayın. git pull --rebase global ayarı, --fixup commit’leri ve PR öncesi interactive rebase alışkanlığı zamanla otomatik hale gelecek. Birkaç ay sonra dağınık merge geçmişlerine baktığınızda neden daha önce geçmediğinizi merak edeceksiniz.
Temiz commit geçmişi bir estetik kaygı değil, ekip verimliliğini doğrudan etkileyen pratik bir beceri.
