git rebase: nasıl çalışır, interaktif mod ve commit'leri birleştirme
git rebase, mevcut branch'inizin commit'lerini yeni bir base'in üzerine taşır: Git commit'lerinizi geçici olarak kenara alır, branch'i hedefin ucuna yerleştirir ve değişiklikleri yeni commit'ler olarak yeniden uygular. Böylece birleştirme commit'i olmadan doğrusal geçmiş elde edilir; git rebase -i ile commit'ler birleştirilebilir (squash), yeniden adlandırılabilir veya silinebilir. Yalnızca henüz başkalarıyla paylaşmadığınız commit'leri yeniden yazın.
git rebase ne yapar
git rebase, branch'inizi geçmişte başka bir noktaya «taşır». Diyelim feature branch'ini bir hafta önce main'den ayırdınız ve o zamandan beri main ilerledi. Rebase sonrası branch'iniz, işe tam şu anda, main'in en güncel commit'inden başlamışsınız gibi görünür.
git switch feature
git fetch origin
git rebase origin/main
Perde arkasında üç adım vardır. Git branch'lerin ortak atasını bulur, ondan sonraki her commit'inizin değişikliklerini sırayla kaydeder, branch'i origin/main'in üzerine taşır ve kaydettiği değişiklikleri birer birer yeniden uygular. Sonuçta aynı içerik ve mesajlara sahip ama farklı hash'li yeni commit'ler oluşur.
Önce: A---B---C---F---G main
\
D---E feature
Sonra: A---B---C---F---G main
\
D'---E' feature
D' ve E' üzerindeki tırnak bir süs değildir. Bunlar farklı hash'lere ve farklı ebeveynlere sahip ayrı nesnelerdir. Rebase'in en önemli kuralı da buradan çıkar.
Altın kural: yayımlanmış olanı rebase etmeyin
Rebase geçmişi yeniden yazar. Commit'ler yalnızca sizdeyse bu güvenlidir. Ama branch'i meslektaşlarınız indirdiyse, rebase sonrası onlarda eski D ve E kalır, sizde ise yeni D' ve E' olur; bir sonraki pull'da geçmiş çoğalır ve çakışmaları yeniden çözmek gerekir.
Pratik kural: rebase'i kendi yerel branch'lerinizde ve yalnızca bir kişinin çalıştığı kişisel feature branch'lerinde yapın. Ortak branch'leri — main, develop, release branch'lerini — asla rebase etmeyin.
Rebase'den sonra branch'i nasıl gönderirsiniz
Branch sunucuda zaten varsa sıradan git push reddedilir: yerel geçmiş artık uzaktaki geçmişin devamı değildir. Güvenli biçimde zorla göndermek gerekir:
git push --force-with-lease
--force'un aksine --force-with-lease, sunucuda sizin görmediğiniz commit'ler belirdiyse üzerine yazmayı reddeder. Böylece başkasının işini kazara silmezsiniz.
rebase sırasında çakışmalar
Commit'ler birer birer uygulandığı için çakışma herhangi birinde çıkabilir — üstelik art arda birkaç kez. Git durur ve hangi commit'in uygulanamadığını bildirir.
$ git rebase origin/main
Auto-merging src/api.js
CONFLICT (content): Merge conflict in src/api.js
error: could not apply 4e1f9a2... Sorgular için önbellek ekle
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
Yapılacaklar sırası:
- Çakışan dosyayı açın, doğru sürümü seçin ve
<<<<<<<,=======,>>>>>>>işaretlerini kaldırın. - Dosyayı çözülmüş olarak işaretleyin:
git add src/api.js. Burada commit yapmanız gerekmez. - Devam edin:
git rebase --continue. Git bir sonraki commit'i uygular.
Süreci yönetmek için diğer komutlar:
| Komut | Ne yapar |
|---|---|
git rebase --continue | Çakışma çözüldükten sonra devam eder |
git rebase --skip | Mevcut commit'i tamamen atlar (değişiklikleri kaybolur) |
git rebase --abort | Rebase'i iptal eder ve branch'i başlangıçtaki hâline döndürür |
«Bizim» ve «onların» taraflarındaki karışıklığa dikkat edin. Rebase sırasında HEAD, branch'i taşıdığınız base'dir; «gelen» değişiklikler ise sizin kendi commit'leriniz. Bu yüzden rebase sırasında git checkout --ours main'deki sürümü, --theirs sizinkini alır. Merge'de bunun tersi geçerlidir.
Tekrarlayan çakışmalar: rerere
Uzun süren bir branch'i düzenli olarak rebase ediyor ve aynı çakışmayı tekrar tekrar çözüyorsanız git config --global rerere.enabled true ayarını açın. Git çözümünüzü hatırlar ve bir dahaki sefere onu kendisi uygular.
İnteraktif rebase: yayımlamadan önce geçmişi düzenleyin
Rebase'in en değerli yanı interaktif modudur. Commit'lerinizi incelemeye sunmadan önce düzene sokmanıza olanak tanır: «fix», «fix typo», «bir düzeltme daha» gibi commit'leri tek anlamlı bir commit'te birleştirebilir, mesajları yeniden yazabilir, sırayı değiştirebilir ya da gereksizleri atabilirsiniz.
git rebase -i HEAD~4 # son 4 commit
git rebase -i origin/main # main'de olmayan tüm branch commit'leri
Git, commit'leri eski olandan yeniye doğru listeleyen bir editör açar (bu, git log'un tersi sıradır):
pick 1a2b3c4 Giriş formu ekle
pick 5d6e7f8 fix
pick 9a0b1c2 E-posta doğrulaması
pick 3d4e5f6 doğrulamadaki yazım hatası
# Commands:
# p, pick = commit'i olduğu gibi kullan
# r, reword = commit'i kullan, ama mesajını değiştir
# e, edit = commit'i kullan, ama değiştirmek için dur
# s, squash = önceki commit'e birleştir
# f, fixup = squash gibi, ama yalnızca önceki commit'in mesajını tut
# d, drop = commit'i sil
Satırın başındaki kelimeyi değiştirip dosyayı kaydedip editörü kapatırsanız Git planı yukarıdan aşağıya uygular.
| Komut | Etkisi |
|---|---|
pick | Commit'i olduğu gibi bırakır |
reword | Değişiklikleri tutar, ama mesajı yeniden yazar |
edit | Commit'te durur; içeriği değiştirmek (git commit --amend) ya da birkaç commit'e bölmek için |
squash | Önceki commit'e katar, mesajları birleştirir ve düzenletir |
fixup | Önceki commit'e katar, bu commit'in mesajını atar |
drop | Commit'i değişiklikleriyle birlikte siler |
Satırları yer değiştirmek commit'lerin sırasını değiştirir; bir satırı silmek ise drop ile aynıdır.
Git squash: commit'leri tek commit'te birleştirme
Git'te git squash diye ayrı bir komut yoktur. Commit'leri «squash» etmenin üç yolu vardır ve interaktif rebase en esnek olanıdır.
Yöntem 1. İnteraktif rebase
Yukarıdaki örnekte dört commit'i iki temiz commit'e çevirelim:
pick 1a2b3c4 Giriş formu ekle
fixup 5d6e7f8 fix
pick 9a0b1c2 E-posta doğrulaması
fixup 3d4e5f6 doğrulamadaki yazım hatası
Bir grubun ilk commit'i pick olarak kalmalıdır: squash ve fixup commit'i bir öncekine katar, en başta olanın katılacağı bir öncekisi yoktur.
Yöntem 2. fixup commit'leri ve autosquash
Hangi commit'i düzelteceğinizi önceden biliyorsanız, düzeltmeyi baştan doğru işaretle oluşturun:
git commit --fixup=1a2b3c4
git rebase -i --autosquash origin/main
Git, fixup commit'ini doğru yere kendisi koyar ve fixup olarak işaretler; geriye planı kaydetmek kalır. Bu davranışı varsayılan yapmak için git config --global rebase.autoSquash true kullanın.
Yöntem 3. Tüm branch'i tek commit'e sıkıştırma
git reset --soft $(git merge-base HEAD origin/main)
git commit -m "E-posta ile doğrulamalı giriş"
reset --soft, branch işaretçisini ayrılma noktasına geri alır, ama tüm değişiklikleri index'te bırakır; geriye tek bir commit yapmak kalır. Alternatif olarak ana branch'e birleştirirken git merge --squash ya da pull request'teki «Squash and merge» düğmesi kullanılabilir.
rebase'in kullanışlı varyantları
git pull --rebase
Sıradan pull, uzak branch'i sizinkine birleştirir ve meslektaşlarınızla paralel commit yaptıysanız bir merge commit'i oluşturur. git pull --rebase ise bunun yerine yerel commit'lerinizi taze uzak commit'lerin üzerine yerleştirir. Varsayılan yapmak için: git config --global pull.rebase true. Bu, rebase'in en güvenli kullanım alanıdır: yerel commit'lerinizi henüz kimse görmemiştir.
--onto: branch'in bir kısmını taşımak
feature-b'yi feature-a'dan ayırdınız, ama şimdi feature-b'nin feature-a'nın commit'leri olmadan doğrudan main'den çıkmasını istiyorsunuz:
git rebase --onto main feature-a feature-b
Okunuşu şöyledir: feature-a'da olmayan feature-b commit'lerini alın ve main'in üzerine yerleştirin.
--exec: her commit'i kontrol etmek
git rebase -i --exec "npm test" origin/main
Uygulanan her commit'ten sonra testler çalışır; biri başarısız olursa rebase sorunlu commit'te durur. Geçmişteki her commit'in derlenmesi önemliyse çok kullanışlıdır.
--update-refs: birbirine bağlı branch yığını
Birbirine bağlı branch zinciri tutuyorsanız, --update-refs bayrağı (Git 2.38+) üstteki branch'i yeniden taşırken aradaki branch işaretçilerini de günceller.
rebase nasıl geri alınır
İşlem sürüyorsa git rebase --abort. Rebase tamamlandı ve sonuç beğenilmediyse eski commit'ler yok olmamıştır: referans günlüğü onları tutar.
$ git reflog
e5f6a7b HEAD@{0}: rebase (finish): returning to refs/heads/feature
e5f6a7b HEAD@{1}: rebase (pick): E-posta doğrulaması
...
c3d4e5f HEAD@{5}: commit: doğrulamadaki yazım hatası
$ git reset --hard HEAD@{5}
git reflog içinde rebase başlamadan önceki kaydı bulun ve branch'i oraya sıfırlayın. Rebase hemen bittiyse kısa yol da vardır: git reset --hard ORIG_HEAD. Reflog yerelde tutulur ve varsayılan olarak yaklaşık 90 gün kayıt saklar; bu yüzden başarısız bir rebase'den sonra panik yapmanız gerekmez.
Sık yapılan hatalar
- Ortak bir branch'i rebase edip force push yapmak. Meslektaşlarınızın geçmişi dağılır. Çözüm: ekiple konuşun, doğru kopyayı kimin reflog'undan geri yükleyeceğinize karar verin ve sunucuda korumalı branch'lerde force push'u yasaklayın.
--forceyerine--force-with-leasekullanmamak. Bu, aynı branch'e bir meslektaşınızın gönderdiği commit'i silebilir.- İnteraktif planın ilk satırında squash kullanmak. Git «cannot 'squash' without a previous commit» hatası verir.
- Rebase'i kirli bir çalışma dizinini ile başlatmak. Git başlamayı reddeder. Değişiklikleri commit edin,
git stash'e alın ya dagit rebase --autostashkullanın. - Çakışmayı çözüp
git rebase --continueyerinegit commitçalıştırmak. Fazladan bir commit oluşur;--continuesonrası bunu yeniden interaktif rebase ile temizleyebilirsiniz.
Kısaca
git rebase, commit'lerinizi yeni bir base'in üzerine taşır ve yeniden oluşturur; böylece geçmiş doğrusal ve temiz olur. İnteraktif mod taslak commit'leri düzgün bir geçmişe çevirir: squash ve fixup değişiklikleri birleştirir, reword mesajları düzeltir, drop gereksizleri atar. Tek kural: yalnızca henüz başkalarıyla paylaşmadığınız şeyi rebase edin ve sonra kendi uzak branch'inizi --force-with-lease ile gönderin. Rebase ve geçmiş temizliği Git kursunda uygulamalı olarak çalışılmaktadır.
Sık sorulan sorular
git rebase kodun içeriğini değiştirir mi?
Çakışma yoksa dosyaların nihai içeriği birleştirmeyle aynıdır, ama commit'ler yeni hash'ler ve yeni ebeveynlerle yeniden oluşturulur. Çakışmada başka bir seçenek yaparsanız ara commit'ler orijinalinden farklı olur.
Branch'i deponun ilk commit'ine rebase edebilir miyim?
git rebase -i --root kullanın: plana kök commit dahil tüm commit'ler girer. Bu nadiren gerekir, örneğin en ilk commit'in mesajını değiştirmek için.
Rebase commit'lerin yazarını ve tarihini korur mu?
Yazar ve yazar tarihi korunur, ama committer ve commit tarihi güncel olanla değişir. --committer-date-is-author-date bayrağı commit tarihini yazar tarihine eşitler.
Rebase birleştirme commit'lerini de taşıyabilir mi?
Varsayılan olarak rebase geçmişi düzleştirir ve merge commit'lerini atar. Birleştirme yapısını korumak için git rebase --rebase-merges kullanın.
Rebase'in şu anda devam edip etmediğini nasıl anlarım?
git status «interactive rebase in progress» ya da «You are currently rebasing» satırını gösterir ve hangi komutların kullanılabileceğini söyler. Birçok kabuk ve IDE bunu komut satırı istemiyle de belirtir.
Bu konu “Sıfırdan Güvenilir Geliştiriciye Git” kursunda uygulamalı olarak ayrıntılı işleniyor.
Kursa git