Git Interactive Rebase ile Commit Düzenleme
Bir sabah geliyorsunuz ofise, geceki deploy öncesi “şu küçük düzeltmeyi de atayım” derken commit mesajını yanlış yazmışsınız, üstüne bir de debug için koyduğunuz console.log‘ları temizlemeyi unutmuşsunuz. Şimdi önünüzde dağınık bir commit geçmişi var ve pull request açmadan önce bunları düzeltmeniz gerekiyor. İşte tam bu noktada git rebase -i hayat kurtarıcı oluyor.
Interactive rebase, Git’in en güçlü özelliklerinden biri. Ama çoğu sysadmin ve geliştirici ya hiç kullanmıyor ya da “tehlikeli” diye uzak duruyor. Oysa doğru senaryolarda ve doğru kullanıldığında commit geçmişinizi tertemiz bir hale getirebilirsiniz. Bu yazıda gerçek dünyada karşılaşacağınız senaryolar üzerinden interactive rebase’i detaylıca ele alacağız.
Interactive Rebase Nedir, Ne Zaman Kullanılır?
git rebase -i komutu, belirttiğiniz bir noktadan itibaren commit geçmişini interaktif olarak yeniden yazmanızı sağlar. Bunu bir kelime işlemcideki “geçmişi düzenle” özelliği gibi düşünebilirsiniz; fakat çok daha güçlü.
Şu durumlar için biçilmiş kaftan:
- Yanlış yazılmış commit mesajlarını düzeltmek
- Küçük, anlamsız commitleri birleştirmek (squash)
- Commit sırasını değiştirmek
- Belirli bir commiti tamamen silmek
- Büyük bir commiti parçalara ayırmak
Önemli kural: Interactive rebase, commit geçmişini yeniden yazar. Bu yüzden remote’a push ettiğiniz commitleri rebase etmekten kaçının. Sadece henüz paylaşılmamış, kendi local branch’inizdeki commitler için kullanın. Ekip ortamında başkalarının üzerine çalıştığı commitleri rebase ederseniz gerçek anlamda kaos yaşatabilirsiniz.
Temel Kullanım Sözdizimi
git rebase -i HEAD~N
Burada N, geriye kaç commit gitmek istediğinizi belirtir. Örneğin son 3 commiti düzenlemek istiyorsanız:
git rebase -i HEAD~3
Belirli bir commit hash’inden itibaren düzenlemek isterseniz:
git rebase -i abc1234
Bu komutu çalıştırdığınızda varsayılan editörünüz (genellikle vim ya da nano) açılır ve şöyle bir ekranla karşılaşırsınız:
pick a1b2c3d Kullanıcı login endpoint eklendi
pick e4f5g6h Validasyon hataları düzeltildi
pick i7j8k9l Gereksiz console.log temizlendi
# Rebase 9x8y7z6..i7j8k9l onto 9x8y7z6 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# d, drop <commit> = remove commit
Her satır bir commiti temsil eder. pick komutunu değiştirerek o commit üzerinde ne yapmak istediğinizi belirtirsiniz.
Senaryo 1: Commit Mesajını Düzeltmek (reword)
En sık karşılaşılan durum: Aceleyle “fix” diye commit atmışsınız ama ne fix’i olduğu belli değil. Ekip standartlarınız conventional commits gerektiriyor ama siz “wip” diye commit mesajı geçmişsiniz.
git rebase -i HEAD~2
Açılan editörde:
pick a1b2c3d fix
pick e4f5g6h wip
pick yerine reword (ya da kısaca r) yazın:
reword a1b2c3d fix
reword e4f5g6h wip
Dosyayı kaydedip çıktığınızda Git her commit için sırayla yeni bir editör penceresi açar ve mesajı değiştirmenizi ister. Oraya doğru mesajı yazıp kaydedersiniz:
feat(auth): kullanıcı şifre sıfırlama endpoint'i eklendi
Sonuç olarak commit hash’leri değişir, içerikler aynı kalır.
Senaryo 2: Commitleri Birleştirmek (squash ve fixup)
Şöyle bir durumu hayal edin: Feature branch’inizde çalışırken “test”, “test2”, “şimdi oldu”, “son düzeltme” gibi commitler atmışsınız. Bunları tek bir temiz commit olarak birleştirmek istiyorsunuz.
git log --oneline HEAD~5
f1a2b3c son düzeltme
d4e5f6g şimdi oldu
h7i8j9k test2
l1m2n3o test
p4q5r6s feat: ödeme modülü başlangıcı
Son 4 commiti p4q5r6s üzerine squash etmek için:
git rebase -i HEAD~5
pick p4q5r6s feat: ödeme modülü başlangıcı
squash l1m2n3o test
squash h7i8j9k test2
squash d4e5f6g şimdi oldu
squash f1a2b3c son düzeltme
squash yerine fixup kullanırsanız alt commitlerin mesajları atılır, sadece ilk commit mesajı kalır. Genellikle “WIP” ya da anlamsız commitleri temizlerken fixup daha pratiktir:
pick p4q5r6s feat: ödeme modülü başlangıcı
fixup l1m2n3o test
fixup h7i8j9k test2
fixup d4e5f6g şimdi oldu
fixup f1a2b3c son düzeltme
squash seçeneğinde ise Git size bir editör açar ve tüm commit mesajlarını birleştirmenizi ister, istediğiniz kısımları silebilirsiniz.
Senaryo 3: Commit Sırasını Değiştirmek
Nadiren ama bazen bir ihtiyaç doğuyor: İki bağımsız commit var ve sıraları önemli bir sebepten dolayı değişmeli. Interactive rebase editöründe sadece satırların sırasını değiştirmeniz yeterli.
pick a111111 feat: veritabanı migration eklendi
pick b222222 feat: API endpoint eklendi
pick c333333 chore: README güncellendi
API endpoint’i en sona taşımak istiyorsanız:
pick a111111 feat: veritabanı migration eklendi
pick c333333 chore: README güncellendi
pick b222222 feat: API endpoint eklendi
Dikkat etmeniz gereken nokta: Sırasını değiştirdiğiniz commitler arasında dosya çakışması varsa conflict çıkabilir. Git sizi uyaracak, çakışmaları çözdükten sonra git rebase --continue ile devam edebilirsiniz.
Senaryo 4: Commit Silmek (drop)
Bir commit’i tamamen geçmişten silmek istiyorsanız drop komutunu kullanırsınız. Ya da daha basitçe, o satırı editörden tamamen silersiniz.
pick a111111 feat: yeni özellik
pick b222222 debug: test için eklendi - SİLİNECEK
pick c333333 feat: başka bir özellik
drop ile:
pick a111111 feat: yeni özellik
drop b222222 debug: test için eklendi - SİLİNECEK
pick c333333 feat: başka bir özellik
Ya da satırı tamamen silip dosyayı kaydedin, sonuç aynı olacaktır. Sildiğiniz commit’teki değişiklikler kalıcı olarak geçmişten kaldırılır.
Senaryo 5: Commiti Parçalara Ayırmak (edit)
Bu biraz daha zahmetli ama gerçek dünyada çok işe yarayan bir senaryo. Tek bir commit içinde hem veritabanı modelini değiştirdiniz hem de API’yi güncellediniz, hem de test yazdınız. Bunları üç ayrı commit’e bölmek istiyorsunuz.
git rebase -i HEAD~1
edit a111111 feat: model, API ve testler eklendi
pick yerine edit yazıp kaydedip çıktığınızda Git o commit’e gelip duracak ve şunu söyleyecek:
Stopped at a111111... feat: model, API ve testler eklendi
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
Şimdi o commit’i geri alıp parçalara bölmeniz gerekiyor:
git reset HEAD~1
Bu komut commit’i geri alır ama değişiklikler working directory’de kalır (staged olmayan halde). Şimdi dosyaları gruplandırarak ayrı ayrı commit atabilirsiniz:
git add models/
git commit -m "feat(db): kullanıcı modeli güncellendi"
git add api/
git commit -m "feat(api): kullanıcı güncelleme endpoint'i eklendi"
git add tests/
git commit -m "test(api): kullanıcı güncelleme endpoint testleri"
Sonra rebase’i devam ettiriyorsunuz:
git rebase --continue
Bir commit üç commit oldu, geçmiş çok daha okunabilir hale geldi.
Otomatik Squash: –fixup ve –squash Bayrakları
Bunu pratikte çok az kişi biliyor ama son derece kullanışlı. Commit atarken --fixup bayrağını kullanırsanız Git o commit’i başka bir commit’e otomatik olarak birleştirmek üzere etiketler.
# Normal commit
git commit -m "feat: kullanıcı profil sayfası eklendi"
# Bu commit'in hash'i: abc1234
# Sonra küçük bir hata düzelttiniz
git add .
git commit --fixup abc1234
Bu şekilde commit mesajı otomatik olarak fixup! feat: kullanıcı profil sayfası eklendi olarak atanır. Daha sonra:
git rebase -i --autosquash HEAD~5
--autosquash bayrağıyla rebase açıldığında Git fixup commitlerini otomatik olarak doğru yere taşır ve fixup komutu olarak işaretler. Editörde elle uğraşmanıza gerek kalmaz. Bunu her zaman kullanmak istiyorsanız global config’e ekleyebilirsiniz:
git config --global rebase.autoSquash true
Rebase Sırasında Conflict Yönetimi
Interactive rebase yaparken commit çakışmaları kaçınılmaz olabilir, özellikle sıra değiştiriyorsanız ya da bazı commitleri drop ediyorsanız. Conflict çıktığında şunları yaparsınız:
# Conflict çıktığında Git şunu söyler:
# CONFLICT (content): Merge conflict in src/app.js
# error: could not apply a111111... feat: login eklendi
# Conflict'leri çözün
vim src/app.js # ya da herhangi bir editör
# Çözüldükten sonra stage'e alın
git add src/app.js
# Rebase'i devam ettirin
git rebase --continue
Eğer bu noktada rebase’den vazgeçmek isterseniz:
git rebase --abort
--abort komutu sizi rebase başlamadan önceki halinize geri götürür, hiçbir şey değişmemiş olur.
Editör Ayarlarını Düzenlemek
Vim kullanmaktan hoşlanmıyorsanız rebase editörünü değiştirebilirsiniz:
# Nano için
git config --global core.editor "nano"
# VS Code için
git config --global core.editor "code --wait"
# Neovim için
git config --global core.editor "nvim"
VS Code’u editör olarak ayarlarsanız rebase ekranı VS Code’da açılır ve çok daha rahat bir deneyim yaşarsınız. Özellikle büyük rebase işlemlerinde fark yaratıyor.
Gerçek Dünya: Ekip Ortamında Interactive Rebase Kültürü
Bir projeye dahil olduğunuzda ya da yeni bir ekiple çalışmaya başladığınızda commit geçmişinin ne kadar önemsendiğini anında anlarsınız. Bazı ekipler her şeyi merge commit’leriyle kabul ederken, bazıları çok sıkı bir commit geçmişi politikası uygular.
CI/CD pipeline’ınızda otomatik changelog oluşturuyorsanız (semantic-release gibi araçlarla), commit mesajlarınızın conventional commits formatında olması şart. Böyle durumlarda feature branch’teki bütün “wip” ve “fixup” commitlerini PR merge’den önce squash etmek standart bir pratik haline gelir.
Şöyle bir senaryo gerçek projelerden:
# Feature branch'te çalışma bitti
git checkout feature/odeme-entegrasyonu
git log --oneline
# Çıktı:
# 9a8b7c6 wip
# 5d4e3f2 neredeyse bitti
# 1g2h3i4 payment service eklendi
# 7j8k9l0 wip 2
# 3m4n5o6 feat: stripe entegrasyonu başlangıcı
# Son 5 commiti düzenle
git rebase -i HEAD~5
Bu adımdan sonra squash ve reword kombinasyonuyla temiz bir commit geçmişi oluşturabilirsiniz:
pick 3m4n5o6 feat: stripe entegrasyonu başlangıcı
squash 7j8k9l0 wip 2
squash 1g2h3i4 payment service eklendi
squash 5d4e3f2 neredeyse bitti
squash 9a8b7c6 wip
Kaydettiğinizde commit mesajını güncelleme imkanınız olur:
feat(payment): Stripe ödeme entegrasyonu tamamlandı
- Stripe SDK entegre edildi
- Ödeme akışı implement edildi
- Webhook handler'lar eklendi
- Hata yönetimi geliştirildi
PR’ınız çok daha profesyonel görünür ve code reviewer’ların işi kolaylaşır.
Force Push ve Güvenli Kullanım
Rebase sonrasında commit hash’leri değiştiği için remote’a normal push yapamazsınız. Force push gerekir:
git push --force-with-lease origin feature/odeme-entegrasyonu
--force yerine --force-with-lease kullanmanızı şiddetle tavsiye ederim. Bu bayrak, remote’da sizin habersiz başka birinin push ettiği commit olup olmadığını kontrol eder. Eğer remote’da sizin bilmediğiniz bir commit varsa push’u reddeder, böylece başkasının çalışmasını silmiş olmazsınız. Saf --force bunu yapmaz ve geri dönüşü zor durumlar yaratabilir.
Kesinlikle yapılmaması gereken: main ya da master branch’e, ya da ekip arkadaşlarının aktif kullandığı herhangi bir branch’e force push yapmak. Bu gerçek bir felakete yol açar.
Sonuç
Interactive rebase ilk bakışta karmaşık görünebilir ama aslında birkaç pratik senaryoyla mantığını kavradıktan sonra son derece doğal bir araç haline geliyor. Önemli olan doğru zamanda doğru komutu kullanmak: mesaj değiştirmek için reword, birleştirmek için squash ya da fixup, silmek için drop, parçalamak için edit.
Tek altın kural şu: Remote’a push edilmiş, ekibin kullandığı commitlere dokunmayın. Bunu aklınızdan çıkarmazsanız interactive rebase size hem zaman kazandıran hem de ekibinize temiz, okunabilir bir proje geçmişi bırakmanızı sağlayan güçlü bir silah olur.
Bir de şunu söyleyeyim: Commit geçmişine önem veren ekiplerde çalışmak bambaşka bir şey. Bir yıl önce yazılan kodun neden öyle yazıldığını commit mesajlarından anlayabilmek, bir bug’ın hangi değişiklikle girdiğini git bisect ve temiz commit geçmişiyle beş dakikada tespit edebilmek… Bunlar küçük detaylar gibi görünse de uzun vadede muazzam bir fark yaratıyor.
