Git Squash ile Commit Geçmişini Temizleme

Üç yıl önce bir ekiple çalışırken şahit olduğum bir sahneyi hiç unutmıyorum: Kıdemli bir geliştirici, pull request’e bakıyor ve yüzü ekşiyor. “Bu da ne?” diyor. Commit listesine bakıyorum: “düzelttim”, “tekrar düzelttim”, “neden çalışmıyor”, “tamam buldum”, “son düzeltme”, “gerçekten son düzeltme”. Yedi commit, tek bir özellik için. Ve hepsi main branch’e gidecek. İşte o gün ekibimizde git squash konuşuldu ve o günden sonra kod tabanımız çok daha temiz hale geldi.

Squash Nedir ve Neden Önemlidir

Git’te squash, birden fazla commit’i tek bir commit’te birleştirme işlemidir. Teknik tanımı bu kadar basit, ama önemi çok daha derin.

Bir feature branch üzerinde çalışırken kaç tane “ara commit” yapıyorsunuz? Debug için eklediğiniz geçici kodlar, yazım hataları, refactor denemeleri… Bunların hepsi commit geçmişine giriyor. Bu karmaşa main branch’e taşındığında git log artık bir hikaye anlatmıyor, bir kaos belgesi haline geliyor.

Squash’ın iki temel faydası vardır:

  • Anlamlı commit geçmişi: Her commit, tamamlanmış ve anlamlı bir değişikliği temsil eder.
  • Bisect verimliliği: git bisect ile hata tespiti yaparken temiz bir geçmiş hayat kurtarır.
  • Code review kalitesi: Reviewer’lar, dağınık commit’ler yerine mantıklı gruplar halinde değişiklikleri inceler.
  • Revert kolaylığı: Bir özelliği geri almak istediğinizde tek bir commit’i revert etmek, yedi commit’i tek tek geri almaktan çok daha pratiktir.

Interactive Rebase ile Squash Yapma

Squash işleminin en yaygın ve en güçlü yolu git rebase -i yani interactive rebase kullanmaktır. Temel sözdizimi şöyle:

git rebase -i HEAD~N

Burada N, kaç commit geriye gitmek istediğinizi belirtir. Örneğin son 5 commit’i düzenlemek istiyorsanız:

git rebase -i HEAD~5

Bu komutu çalıştırdığınızda bir editör açılır ve şuna benzer bir çıktı görürsünüz:

pick a1b2c3d feat: kullanıcı login sayfası eklendi
pick e4f5g6h fix: login formu düzeltildi
pick h7i8j9k fix: tekrar düzelttim
pick l0m1n2o style: buton rengi değiştirildi
pick p3q4r5s fix: son düzeltme

Buradaki pick kelimesi her commit için bir eylem belirtir. Squash için bu eylemleri değiştirmeniz gerekir:

  • pick (p): Commit’i olduğu gibi tut
  • squash (s): Commit’i bir önceki commit ile birleştir, mesajları da birleştir
  • fixup (f): Commit’i bir önceki ile birleştir ama bu commit’in mesajını at
  • reword (r): Commit’i tut ama mesajını değiştir
  • drop (d): Commit’i tamamen sil
  • edit (e): Commit’te dur ve değişiklik yapma imkanı ver

İlk commit’i pick bırakıp gerisini squash yaparsınız:

pick a1b2c3d feat: kullanıcı login sayfası eklendi
squash e4f5g6h fix: login formu düzeltildi
squash h7i8j9k fix: tekrar düzelttim
squash l0m1n2o style: buton rengi değiştirildi
squash p3q4r5s fix: son düzeltme

Kaydedip çıktığınızda Git sizi bir commit mesajı editörüne alır. Tüm eski mesajları görürsünüz ve tek bir anlamlı mesaj yazabilirsiniz.

Fixup ile Daha Hızlı Squash

Squash ve fixup arasındaki farkı anlamak önemli. squash kullandığınızda her commit’in mesajını görebilir ve istediğinizi seçebilirsiniz. fixup kullandığınızda ise alt commit’in mesajı hiç sorulmadan üst commit’in mesajına birleştirilir. Genelde “tamam buldum”, “düzelttim” gibi anlamsız commit mesajları için fixup çok daha pratik:

pick a1b2c3d feat: kullanıcı login sayfası eklendi
fixup e4f5g6h fix: login formu düzeltildi
fixup h7i8j9k fix: tekrar düzelttim
fixup l0m1n2o style: buton rengi değiştirildi
fixup p3q4r5s fix: son düzeltme

Bu şekilde sadece ilk commit mesajı kalır, diğerleri sessizce yutulur.

Commit Esnasında Fixup Kullanımı

Modern Git workflow’larında bir numara daha var: commit yaparken --fixup flag’ini kullanabilirsiniz. Bu, hangi commit’i düzelttiğinizi önceden belirtir:

# Önce normal commit
git commit -m "feat: ödeme sistemi entegrasyonu"

# Sonra bir şeyi düzelttiniz
git add payment.py
git commit --fixup HEAD

--fixup HEAD kullandığınızda Git otomatik olarak “fixup! feat: ödeme sistemi entegrasyonu” şeklinde bir commit mesajı üretir. Ardından --autosquash ile bu commit’leri otomatik olarak sıralayabilirsiniz:

git rebase -i --autosquash HEAD~3

Editör açıldığında fixup commit’ler zaten doğru yere otomatik yerleştirilmiş olur. Bunu her seferinde yazmak zorunda kalmamak için global config’e ekleyebilirsiniz:

git config --global rebase.autoSquash true

Gerçek Dünya Senaryosu: Feature Branch Temizliği

Diyelim ki bir e-ticaret platformunda sepet özelliği üzerinde çalışıyorsunuz. Birkaç günlük çalışmanın ardından branch’inizde şu commit’ler var:

git log --oneline

7f3a1b2 debug: console.log sildim
6e2d0c1 fix: sepet toplamı yanlış hesaplanıyordu
5d1f9b0 wip: indirim hesabı ekliyorum
4c0e8a9 fix: yazım hatası
3b9d7f8 feat: indirim kodu sistemi
2a8c6e7 fix: fiyat formatı düzeltildi
1b7d5f6 feat: sepete ürün ekleme

Bu branch main’e merge edilmeden önce düzgün bir squash yapmalısınız. Mantıklı gruplar oluşturalım:

git rebase -i HEAD~7

Editörde şunu oluşturuyoruz:

pick 1b7d5f6 feat: sepete ürün ekleme
fixup 2a8c6e7 fix: fiyat formatı düzeltildi
pick 3b9d7f8 feat: indirim kodu sistemi
fixup 4c0e8a9 fix: yazım hatası
fixup 5d1f9b0 wip: indirim hesabı ekliyorum
fixup 6e2d0c1 fix: sepet toplamı yanlış hesaplanıyordu
fixup 7f3a1b2 debug: console.log sildim

Sonuç olarak iki anlamlı commit elde ediyoruz:

git log --oneline

b2c3d4e feat: indirim kodu sistemi
a1b2c3d feat: sepete ürün ekleme

Merge ile Squash: git merge –squash

Interactive rebase dışında bir yöntem daha var: git merge --squash. Bu yaklaşım özellikle bir feature branch’i main’e alırken kullanışlıdır:

git checkout main
git merge --squash feature/sepet-ozelligi
git commit -m "feat: sepet sistemi eklendi - ürün ekleme ve indirim kodu desteği"

Bu yöntemin interactive rebase’den farkı şudur: branch üzerindeki tüm commit’leri tek seferde ana branch’e squash edersiniz. Ama dikkat: bu işlem feature branch’i merge etmez, sadece değişiklikleri staging’e alır. Branch hala ayrı durur ve git branch -d feature/sepet-ozelligi ile silmeniz gerekir.

Bu yöntemi şu durumlarda tercih ederim:

  • Feature branch kendi içinde tutarlı bir iş birimi
  • Branch’te commit geçmişini korumaya gerek yok
  • Tek komutla temiz bir commit isteniyorsa

Conflict Durumlarında Squash

Rebase sırasında conflict çıkması can sıkıcı ama yönetilebilir. Conflict durumunda Git sizi bilgilendirir:

CONFLICT (content): Merge conflict in src/cart.js
error: could not apply 3b9d7f8... feat: indirim kodu sistemi
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".

Adım adım çözüm:

# Conflict'i düzenleyin
vim src/cart.js

# Düzeltilen dosyayı stage'leyin
git add src/cart.js

# Rebase'e devam edin
git rebase --continue

Eğer işin içinden çıkamıyorsanız ve başa dönmek istiyorsanız:

git rebase --abort

Bu komut sizi rebase başlamadan önceki haline geri alır. Hiçbir şey kaybolmaz.

Squash Sonrası Force Push

Squash yaptıktan sonra branch geçmişi değiştiği için remote’a normal push çalışmaz. Force push kullanmanız gerekir:

git push origin feature/sepet-ozelligi --force-with-lease

--force-with-lease kullanmanızı şiddetle öneririm, düz --force yerine. Neden? Çünkü --force-with-lease, remote branch’te sizin bilginiz dışında başka birinin değişiklik yapıp yapmadığını kontrol eder. Başkası push yaptıysa işleminiz başarısız olur ve farkında olmadan birinin çalışmasını silmezsiniz. Çok kişiyle çalışılan branch’lerde bu detay kritiktir.

Eğer remote’da değişiklik yapılmış olduğunu biliyorsanız ve yine de force push yapmak istiyorsanız:

git push origin feature/sepet-ozelligi --force

Ama bu komutu main veya develop gibi ortak branch’lerde asla kullanmayın.

Squash Konusunda Ekip Kuralları

Squash güçlü bir araç ama ekip olarak üzerinde anlaşılmış kurallar olmadan kaos yaratır. Genelde şu prensipleri öneririm:

  • Kendi branch’inizde her zaman squash yapabilirsiniz. Bu branch size ait, kimse etkilenmez.
  • Paylaşılan branch’lerde squash yapmadan önce ekibi haberdar edin. Rebase geçmişi yeniden yazar, diğerlerinin branch’leri uyumsuz hale gelir.
  • Main/develop branch’inde asla rebase ve squash yapmayın. Bu kural kırılmaz.
  • Merge commit’i mi squash mı? Bunu ekipçe karar verin. GitHub’ın “Squash and merge” butonu bunu otomatik yapar, ama tüm ekibin bu konvansiyonu bilmesi şart.

GitHub, GitLab ve Bitbucket’ta pull request merge edilirken “Squash and merge” seçeneği sunulur. Bu seçenek feature branch’teki tüm commit’leri tek bir commit olarak main’e alır. CI/CD pipeline’larında bu ayarı standart haline getirmek, otomatik bir temizlik sağlar.

Git Alias ile Squash Workflow’u Hızlandırma

Her seferinde uzun komutlar yazmaktan kurtulmak için git alias kullanabilirsiniz:

git config --global alias.squash-last '!f() { git rebase -i HEAD~$1; }; f'

Artık şöyle kullanabilirsiniz:

# Son 5 commit'i squash etmek için
git squash-last 5

Bir başka kullanışlı alias: feature branch’i main’e squash merge etmek için:

git config --global alias.squash-merge '!f() { git merge --squash $1 && git commit; }; f'

Kullanımı:

git squash-merge feature/sepet-ozelligi

Squash Neyi Silmez

Bir yanılgıya dikkat çekmek istiyorum: Squash, commit içindeki kod değişikliklerini silmez. Sadece commit objelerini birleştirir. Yani “tamam buldum” commit’indeki kod düzeltmesi squash sonrasında da orada durur, sadece o commit artık görünmez hale gelir.

Eğer gerçekten bir commit’teki değişikliği silmek istiyorsanız interactive rebase’de drop kullanmanız gerekir:

pick a1b2c3d feat: kullanıcı login sayfası eklendi
drop e4f5g6h fix: bu değişikliği tamamen silmek istiyorum
pick h7i8j9k feat: şifre sıfırlama eklendi

Ama bu işlem de dikkat ister, çünkü sonraki commit’lerle conflict yaratabilir.

Squash ve Git Reflog

“Squash yaptım ve bir şeyleri mahvettim, geri dönebilir miyim?” sorusunun cevabı: Evet, büyük ihtimalle dönebilirsiniz. Git reflog tam bu durumlar için var:

git reflog

Bu komut son yaptığınız tüm git işlemlerini listeler. Rebase’den önceki haline dönmek için:

git reflog

abc1234 HEAD@{0}: rebase (finish): returning to refs/heads/feature/sepet
def5678 HEAD@{1}: rebase (pick): feat: indirim kodu sistemi
ghi9012 HEAD@{2}: rebase (start): checkout main
jkl3456 HEAD@{3}: commit: debug: console.log sildim

Rebase öncesi duruma dönmek için:

git reset --hard HEAD@{3}

Reflog varsayılan olarak 90 gün boyunca kayıt tutar. Bu süre içinde neredeyse her şeyi kurtarabilirsiniz. Bu yüzden squash’tan korkmayın; Git bir güvenlik ağı sunar.

Sonuç

Git squash, öğrendikten sonra bir daha bırakamayacağınız bir alışkanlık. Commit geçmişini bir belge gibi düşünün: ne kadar temiz ve anlamlıysa, altı ay sonra projeye bakan kişi için o kadar değerli. O kişi çoğu zaman kendinizsiniz.

Başlangıç için şu kadar yeter: feature branch’lerinizde çalışırken serbestçe commit atın, branchi merge etmeden önce git rebase -i ile temizleyin. Bunu bir hafta boyunca yapın ve commit geçmişinin nasıl değiştiğini görün.

Interactive rebase, fixup, --force-with-lease, reflog… Bunlar birbirini tamamlayan araçlar. Hepsini birden öğrenmek zorunda değilsiniz, ama squash’la başlayıp adım adım diğerlerine geçmek mantıklı bir yol. Kod yazıyorsunuz, evet, ama o kodun geçmişini de yazıyorsunuz. İkincisine de önem verin.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir