Program
Git kullanırken yazılım geliştirmede popüler bir motto şudur: "Erken commit, sık commit." Bunu yapmak her değişikliğin iyi belgelendiğini, iş birliğini güçlendirdiğini ve projenin evrimini takip etmeyi kolaylaştırdığını sağlar. Ancak bu, çok fazla commit oluşmasına da yol açabilir.
İşte burada commit’leri squash etmenin önemi devreye girer. Squash, birden çok commit kaydını tek, bütünlüklü bir commit’te birleştirme işlemidir.
Örneğin, bir oturum açma formu özelliği üzerinde çalıştığımızı ve aşağıdaki dört commit’i oluşturduğumuzu varsayalım:

Özellik tamamlandığında, proje geneli için bu commit’ler fazla ayrıntılıdır. Gelecekte, geliştirme sırasında karşılaştığımız ve düzelttiğimiz bir hatayı bilmemiz gerekmeyecektir. Ana dalda temiz bir geçmiş sağlamak için bu commit’leri tek bir commit’te squash ederiz:

Git’te Commit Nasıl Squash Edilir: Interaktif Rebase
Commit’leri squash etmenin en yaygın yöntemi interaktif rebase kullanmaktır. Şu komutla başlatırız:
git rebase -i HEAD~<number_of_commits>
<number_of_commits> kısmını squash etmek istediğimiz commit sayısıyla değiştirin.
Bizim örneğimizde dört commit var, dolayısıyla komut şöyle olur:
git rebase -i HEAD~4
Bu komutu çalıştırmak interaktif bir komut satırı düzenleyicisi açacaktır:

Üst bölüm commit’leri gösterir, alt bölüm ise commit’lerin nasıl squash edileceğine dair açıklamalar içerir.
Dört commit görüyoruz. Her biri için hangi komutu uygulayacağımıza karar vermeliyiz. pick (p) ve squash (s) komutlarıyla ilgileniyoruz. Bu dört commit’i tek bir commit’te birleştirmek için ilkini pick yapıp kalan üçünü squash edebiliriz.
Komutları, her commit’in başındaki metni değiştirerek uygularız; özel olarak, ikinci, üçüncü ve dördüncü commit için pick ifadesini s ya da squash olarak değiştiririz. Bu düzenlemeleri yapmak için, komut satırı metin düzenleyicisinde klavyeden i tuşuna basarak “INSERT” moduna geçmemiz gerekir:

i tuşuna bastıktan sonra alt kısımda -- INSERT -- metni görünecek, bu da ekleme moduna geçtiğimizi belirtir. Artık ok tuşlarıyla imleci hareket ettirebilir, karakter silebilir ve standart bir metin düzenleyicide olduğu gibi yazabiliriz:

Değişikliklerden memnun kaldığımızda, klavyeden Esc tuşuna basarak ekleme modundan çıkmamız gerekir. Sıradaki adım değişiklikleri kaydedip düzenleyiciden çıkmaktır. Bunun için önce : tuşuna basarak düzenleyiciye bir komut çalıştırmak istediğimizi belirtiriz:

Düzenleyicinin altında artık bir iki nokta üst üste : görürüz; bu, bir komut girmemizi ister. Değişiklikleri kaydetmek için "write" anlamına gelen w komutunu kullanırız. Düzenleyiciyi kapatmak için "quit" anlamına gelen q kullanılır. Bu komutlar birleştirilip wq olarak birlikte yazılabilir:

Komutu çalıştırmak için Enter tuşuna basarız. Bu işlem mevcut düzenleyiciyi kapatır ve yeni squash edilmiş commit için mesaj girmemize olanak tanıyan yeni bir düzenleyici açar. Düzenleyici, squash edeceğimiz dört commit’in mesajlarını içeren varsayılan bir mesaj gösterecektir:

Mesajı, birleştirilen bu commit’lerin getirdiği değişiklikleri doğru biçimde yansıtacak şekilde düzenlemenizi öneririm—sonuçta squash’ın amacı temiz ve kolay okunur bir geçmişi korumaktır.
Düzenleyiciyle etkileşime geçip mesajı düzenlemek için yeniden i tuşuna basarak düzenleme moduna girer ve mesajı istediğimiz gibi düzenleriz.

Bu durumda commit mesajını "Oturum açma formunu uygula" ile değiştiriyoruz. Düzenleme modundan çıkmak için Esc’e basın. Ardından : tuşuna basıp wq komutunu girerek ve Enter’a basarak değişiklikleri kaydedin.
Commit Geçmişi Nasıl Görüntülenir
Genel olarak, tüm commit geçmişini hatırlamak zor olabilir. Commit geçmişini görüntülemek için git log komutunu kullanabiliriz. Bahsedilen örnekte, squash yapmadan önce git log komutunu çalıştırmak şunu gösterirdi:

Commit listesini gezinmek için yukarı ve aşağı ok tuşlarını kullanın. Çıkmak için q’ya basın.
Squash işleminin başarılı olduğunu doğrulamak için git log kullanabiliriz. Squash’tan sonra çalıştırıldığında, yeni mesajla tek bir commit görüntülenecektir:

Squash edilmiş commit’i push etmek
Yukarıdaki komut yerel depo üzerinde etkili olacaktır. Uzak depoyu güncellemek için değişikliklerimizi push etmemiz gerekir. Ancak commit geçmişini değiştirdiğimiz için --force seçeneğini kullanarak zorla push etmemiz gerekir:
git push --force origin feature/login-form
Zorla push, uzak daldaki commit geçmişini üzerine yazar ve o dalda çalışan diğerlerini potansiyel olarak etkiler. Bunu yapmadan önce ekiple iletişim kurmak iyi bir uygulamadır
İş arkadaşlarını etkileme riskini azaltan daha güvenli bir zorla push yöntemi, --force-with-lease seçeneğini kullanmaktır:
git push --force-with-lease origin feature/login-form
Bu seçenek, yalnızca uzak dal son fetch veya pull’dan bu yana güncellenmemişse zorla push etmemizi sağlar.
Belirli commit’leri squash etme
Beş commit’imiz olduğunu hayal edin:

Diyelim ki 1, 2 ve 5 numaralı commit’leri korumak, 3 ve 4 numaralı commit’leri ise squash etmek istiyoruz.

Interaktif rebase kullanırken squash olarak işaretlenen commit’ler, doğrudan kendilerinden önceki commit ile birleştirilir. Bu durumda, Commit4’ü Commit3 ile birleşecek şekilde squash etmek istediğimiz anlamına gelir.
Bunu yapmak için bu iki commit’i kapsayan bir interaktif rebase başlatmalıyız. Bu durumda üç commit yeterlidir, dolayısıyla şu komutu kullanırız:
git rebase -i HEAD~3
Ardından, Commit4’ü s olarak ayarlayarak Commit3 ile squash edilmesini sağlarız:

Bu komutu çalıştırıp commit’leri listeledikten sonra, 3 ve 4 numaralı commit’lerin birlikte squash edildiğini, geri kalanların ise değişmediğini gözlemleriz.

Belirli bir commit’ten itibaren squash etme
git rebase -i HEAD~3 komutunda HEAD kısmı en son commit için bir kısaltmadır. ~3 söz dizimi bir commit’in atasını belirtmek için kullanılır. Örneğin, HEAD~1 HEAD commit’inin ebeveynine karşılık gelir.

Interaktif rebase’de, komutta belirtilen commit’e kadar olan tüm ata commit’ler dikkate alınır. Belirtilen commit’in dahil edilmediğine dikkat edin:

HEAD yerine doğrudan bir commit hash’i de belirtebiliriz. Örneğin, Commit2’nin hash’i dbf3cc118d6d7c08ef9c4a326b26dbb1e3fe9ddf’dir, dolayısıyla şu komut:
git rebase -i dbf3cc118d6d7c08ef9c4a326b26dbb1e3fe9ddf
Commit2’den sonra yapılan tüm commit’leri dikkate alan bir rebase başlatır. Bu nedenle, bir rebase’i belirli bir commit’te başlatmak ve o commit’i de dahil etmek istiyorsak şu komutu kullanabiliriz:
git rebase -i <commit-hash>~1
Commit’leri Squash Ederken Çakışmaları Çözme
Commit’leri squash ettiğimizde, birden çok commit değişikliğini tek bir commit’te birleştiririz; değişiklikler örtüşüyor veya önemli ölçüde farklılaşıyorsa bu çakışmalara yol açabilir. Çakışmaların ortaya çıkabileceği bazı yaygın senaryolar şunlardır:
- Örtüşen değişiklikler: Squash edilecek iki veya daha fazla commit, bir dosyanın aynı satırlarını ya da birbirine çok yakın satırları değiştirdiyse, Git bu değişiklikleri otomatik olarak uzlaştıramayabilir.
- Farklı değişiklik durumları: Bir commit belirli bir kod parçasını eklerken başka bir commit aynı kodu değiştiriyor veya siliyorsa, bu commit’leri squash etmek çözülmesi gereken çakışmalara neden olabilir.
- Yeniden adlandırma ve değiştirme: Bir commit bir dosyayı yeniden adlandırıyor, sonraki commit’ler ise eski ad üzerinde değişiklik yapıyorsa, bu commit’leri squash etmek Git’i şaşırtabilir ve çakışmaya yol açabilir.
- İkili dosyalardaki değişiklikler: İkili dosyalar metin tabanlı diff araçlarıyla iyi birleştirilemez. Aynı ikili dosyayı birden çok commit değiştiriyorsa ve bunları squash etmeye çalışırsak, Git bu değişiklikleri otomatik olarak uzlaştıramadığı için çakışma oluşabilir.
- Karmaşık geçmiş: Commit’ler, arada birden çok birleştirme, dal oluşturma veya rebase içeren karmaşık bir geçmişe sahipse, bunları squash etmek değişikliklerin doğrusal olmaması nedeniyle çakışmalara yol açabilir.
Squash işlemi sırasında Git, her değişikliği tek tek uygulamaya çalışır. Süreçte çakışmalarla karşılaşırsa duraklar ve bunları çözmemize izin verir.
Çakışmalar, <<<<<< ve >>>>>> işaretleriyle belirtilir. Çakışmaları ele almak için dosyaları açıp, hangi kod parçalarını tutmak istediğimizi seçerek her birini manuel olarak çözmemiz gerekir.
Çakışmaları çözdükten sonra git add komutuyla çözülen dosyaları sahnelememiz gerekir. Ardından rebase’e şu komutla devam edebiliriz:
git rebase --continue
Git çakışmaları hakkında daha fazlası için şu eğitici yazıya göz atın: Git’te birleştirme (merge) çakışmaları nasıl çözülür.
Rebase ile Squash’a Alternatifler
git merge --squash komutu, birden çok commit’i tek bir commit’te birleştirmek için git rebase -i’ye alternatif bir yöntemdir. Bu komut özellikle, bir daldaki değişiklikleri ana dala alırken, tüm bireysel commit’leri tek bir commit’te squash etmek istediğimiz durumlarda kullanışlıdır. git merge kullanarak squash etmenin özeti şöyledir:
- Değişiklikleri dahil etmek istediğimiz hedef dala geçeriz.
git merge --squash <branch-name>komutunu,<branch-namekısmını dal adıyla değiştirerek çalıştırırız.git commitile değişiklikleri commit’leriz ve özellik dalındaki tüm değişiklikleri temsil eden tek bir commit oluştururuz.
Örneğin, feature/login-form dalındaki değişiklikleri main’e tek bir commit olarak dahil etmek istediğimizi varsayalım:
git checkout main
git merge --squash feature-branch
git commit -m "Implement login form"
git rebase -i ile karşılaştırıldığında bu yaklaşımın sınırlamaları şunlardır:
- Denetim ayrıntı düzeyi: Bireysel commit’ler üzerinde daha az kontrol. Rebase ile hangi commit’leri birleştireceğimizi seçebilirken, merge tüm değişiklikleri tek bir commit’te birleştirmeye zorlar.
- Ara tarihçe: Merge kullanıldığında, özellik dalındaki bireysel commit geçmişi ana dalda kaybolur. Bu, özelliğin geliştirilmesi sırasında yapılan artımlı değişiklikleri takip etmeyi zorlaştırabilir.
- Commit öncesi gözden geçirme: Tüm değişiklikleri tek bir değişiklik seti olarak aşamaya aldığından, interaktif rebase sırasında olduğu gibi her commit’i tek tek gözden geçirme veya test etme olanağı yoktur.
Sonuç
Geliştirme sürecine sık ve küçük commit’leri dahil etmek iş birliğini ve net dokümantasyonu teşvik eder, ancak projenin geçmişini de kalabalıklaştırabilir. Commit’leri squash etmek, küçük yinelemeli değişikliklerin gürültüsünü ortadan kaldırırken önemli kilometre taşlarını koruyarak denge sağlar.
Git hakkında daha fazla bilgi için şu kaynakları öneririm:
