Bir yeniden temel oluşturduktan sonra Git kaydetmeleri aynı dalda yineleniyor


131

Pro Git'te The Perils of Rebasing ile ilgili sunulan senaryoyu anlıyorum . Yazar temel olarak size yinelenen işlemlerden nasıl kaçınılacağını anlatır:

Herkese açık bir arşive gönderdiğiniz taahhütleri yeniden sunmayın.

Size özel durumumu anlatacağım çünkü bunun Pro Git senaryosuna tam olarak uymadığını düşünüyorum ve yine de yinelenen işlemlerle sonuçlanıyorum.

Diyelim ki yerel meslektaşları ile iki uzak şubem var:

origin/master    origin/dev
|                |
master           dev

Dört şubenin tümü aynı taahhütleri içerir ve geliştirmeye şu konularda başlayacağım dev:

origin/master : C1 C2 C3 C4
master        : C1 C2 C3 C4

origin/dev    : C1 C2 C3 C4
dev           : C1 C2 C3 C4

Birkaç işlemden sonra değişiklikleri şu şekilde gönderiyorum origin/dev:

origin/master : C1 C2 C3 C4
master        : C1 C2 C3 C4

origin/dev    : C1 C2 C3 C4 C5 C6  # (2) git push
dev           : C1 C2 C3 C4 C5 C6  # (1) git checkout dev, git commit

masterHızlı bir düzeltme yapmak için geri dönmem gerekiyor :

origin/master : C1 C2 C3 C4 C7  # (2) git push
master        : C1 C2 C3 C4 C7  # (1) git checkout master, git commit

origin/dev    : C1 C2 C3 C4 C5 C6
dev           : C1 C2 C3 C4 C5 C6

Ve devhızlı düzeltmeyi gerçek geliştirmeme dahil etmek için değişiklikleri yeniden temelliyorum:

origin/master : C1 C2 C3 C4 C7
master        : C1 C2 C3 C4 C7

origin/dev    : C1 C2 C3 C4 C5 C6
dev           : C1 C2 C3 C4 C7 C5' C6'  # git checkout dev, git rebase master

Ben GitX ile kaydedilmesini tarihini gösterirseniz / I fark gitk origin/devşimdi iki özdeş kaydedilmesini içerir C5've C6'Git için farklı olan. Şimdi değişiklikleri buna zorlarsam origin/devsonuç:

origin/master : C1 C2 C3 C4 C7
master        : C1 C2 C3 C4 C7

origin/dev    : C1 C2 C3 C4 C5 C6 C7 C5' C6'  # git push
dev           : C1 C2 C3 C4 C7 C5' C6'

Belki Pro Git'teki açıklamayı tam olarak anlamıyorum, bu yüzden iki şeyi bilmek istiyorum:

  1. Git yeniden oluştururken neden bu taahhütleri kopyalıyor? Bunu sadece başvurmak C5ve C6sonra yapmak yerine yapmanın özel bir nedeni var mı C7?
  2. Bundan nasıl kaçınabilirim? Bunu yapmak akıllıca olur mu?

Yanıtlar:


87

Burada rebase kullanmamalısınız, basit bir birleştirme yeterli olacaktır. Bağlandığınız Pro Git kitabı temelde bu durumu açıklıyor. İç işleyiş biraz farklı olabilir, ancak bunu şu şekilde görselleştiriyorum:

  • C5ve C6geçici olarak çekildidev
  • C7 uygulandı dev
  • C5ve C6üstüne oynatılır C7, yeni farklar ve dolayısıyla yeni taahhütler oluşturur

Öyleyse, devşubenizde C5ve C6artık fiilen varolmuyor: şimdi C5've onlar C6'. Size itmek zaman origin/dev, git görür C5've C6'yeni kaydedilmesini ve tarihin sonuna kadar üzerinde çiviler bunları olarak. Aslında, aralarındaki C5ve C5'içindeki farklılıklara bakarsanız origin/dev, içerik aynı olsa da satır numaralarının muhtemelen farklı olduğunu fark edeceksiniz - bu da commit'in karmasını farklı kılıyor.

Pro Git kuralını yeniden ifade edeceğim: yerel deponuz dışında herhangi bir yerde var olan taahhütleri asla yeniden sunmayın . Bunun yerine birleştirmeyi kullanın.


Aynı sorunu yaşıyorum, uzak şube geçmişimi şimdi nasıl düzeltebilirim, şubeyi silmek ve kiraz toplama ile yeniden oluşturmak dışında başka bir seçenek var mı?
Wazery

1
@xdsy: Bebek bir göz bu ve bu .
Justin ᚅᚔᚈᚄᚒᚔ

2
"C5 ve C6 geçici olarak geliştiriciden çıkarıldı ... C7, geliştiriciye uygulandı" diyorsunuz. Eğer durum buysa, o zaman neden C5 ve C6, origin / dev üzerindeki işlemlerin sıralamasında C7'den önce görünüyor?
KJ50

@ KJ50: Çünkü C5 ve C6 zaten zorlandı origin/dev. Yeniden kaynaklandığında dev, geçmişi değiştirilir (C5 / C6 geçici olarak kaldırılır ve C7'den sonra yeniden uygulanır). İtilen depoların geçmişini değiştirmek, ne yaptığınızı bilmediğiniz sürece genellikle Really Bad Idea ™ 'dır. Bu basit durumda, konu bir kuvvet itme yaparak çözülebilir deviçin origin/devkapalı çalışan başkasının rebase sonra ve bildiren origin/devonlar muhtemelen yaklaşık kötü bir gün sahip olmak olduğunu. Yine daha iyi cevap "bunu yapma ... onun yerine birleştirmeyi kullan"
Justin ᚅᚔᚈᚄᚒᚔ

3
Unutulmaması gereken bir şey: C5 ve C5'in karması kesinlikle farklıdır, ancak satır numaralarının farklı olması nedeniyle değil, fark için herhangi birinin yeterli olduğu aşağıdaki iki gerçek için: 1) bahsettiğimiz hash delta farkının karması değil, kesinleştirme sonrasındaki tüm kaynak ağacının karmasıdır ve bu nedenle C5 ', C7'den gelen her şeyi içerirken C5 içermez ve 2) C5'in ebeveyni C5'ten farklıdır ve bu bilgi ayrıca, karma sonucunu etkileyen bir işleme ağacının kök düğümüne dahil edilir.
Özgür Murat

113

Kısa cevap

Koştuğunuz gerçeğini atladınız git push, aşağıdaki hatayı aldınız ve ardından çalışmaya devam ettiniz git pull:

To git@bitbucket.org:username/test1.git
 ! [rejected]        dev -> dev (non-fast-forward)
error: failed to push some refs to 'git@bitbucket.org:username/test1.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Git'in yardımcı olmaya çalışmasına rağmen , 'git pull' tavsiyesi büyük olasılıkla yapmak istediğiniz şey değil .

Eğer sen:

  • Bir "özellik dalı" veya "geliştirici dalı" üzerinde çalışma Tek başına üzerinde çalıştığınızda, git push --forceyeniden ödeme sonrası taahhütlerinizle uzaktan kumandayı güncellemek için çalıştırabilirsiniz ( user4405677'nin cevabına göre ).
  • Aynı anda birden fazla geliştiriciyle bir dalda çalışıyorsanız, muhtemelengit rebase ilk etapta kullanmamalısınız. Adresindeki devdeğişikliklerle güncellemek için , çalıştırmak masteryerine git rebase master devçalıştırmalısınız.git merge master üzerinde iken dev( Justin Yanıt başına kadar ).

Biraz daha uzun bir açıklama

Git'teki her kaydetme karması bir dizi faktöre dayanır ve bunlardan biri, kendisinden önce gelen kaydetmenin karmasıdır.

Kaydetmeleri yeniden sıralarsanız, kaydetme karmalarını değiştirirsiniz; yeniden sıralama (bir şey yaptığında) commit hash değerlerini değiştirir. Bunun üzerine, koşu sonucu git rebase master dev, nerede devile senkronize değildi masteryaratacak, yeni onlardan önce deveklenenler ile aynı içeriğe sahip (ve dolayısıyla hash'ler)master .

Bunun gibi bir duruma birden çok şekilde girebilirsiniz. Düşünebileceğim iki yol:

  • Üzerinde taahhüt alabilirdin masterdevÇalışmanızı temel almak istediğiniz
  • Üzerinde taahhüt alabilirdin devÖnceden bir uzaktan kumandaya aktarılmış olan işlemlere sahip daha sonra bunları değiştirmeye devam edebilirsiniz (yeniden sözcük tamamlama mesajları, yeniden sırala kaydetme, squash kaydetme vb.)

Ne olduğunu daha iyi anlayalım — işte bir örnek:

Bir deponuz var:

2a2e220 (HEAD, master) C5
ab1bda4 C4
3cb46a9 C3
85f59ab C2
4516164 C1
0e783a3 C0

Bir havuzdaki ilk doğrusal işlem kümesi

Daha sonra taahhütleri değiştirmeye devam edersiniz.

git rebase --interactive HEAD~3 # Three commits before where HEAD is pointing

(Burada sözümü almanız gerekecek: Git'te commit'leri değiştirmenin birkaç yolu var. Bu örnekte, zamanını değiştirdim C3, ancak yeni commit'ler ekliyor, commit mesajlarını değiştiriyor, commit'leri yeniden sıralayacaksınız. ezme birlikte taahhüt eder vb.)

ba7688a (HEAD, master) C5
44085d5 C4
961390d C3
85f59ab C2
4516164 C1
0e783a3 C0

Aynı işlem yeni hashlerle yapılır

Kaydetme karmalarının farklı olduğunu fark etmenin önemli olduğu yer burasıdır. Onlarla ilgili bir şeyi (herhangi bir şeyi) değiştirdiğiniz için bu beklenen bir davranıştır. Sorun değil, AMA:

Ana birimin uzaktan kumandayla senkronize olmadığını gösteren bir grafik günlüğü

İtmeye çalışmak size bir hata gösterecektir (ve koşmanız gerektiğine dair ipucu git pull).

$ git push origin master
To git@bitbucket.org:username/test1.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'git@bitbucket.org:username/test1.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Koşarsak git pullşu günlüğü görürüz:

7df65f2 (HEAD, master) Merge branch 'master' of bitbucket.org:username/test1
ba7688a C5
44085d5 C4
961390d C3
2a2e220 (origin/master) C5
85f59ab C2
ab1bda4 C4
4516164 C1
3cb46a9 C3
0e783a3 C0

Veya başka bir yol gösterildi:

Birleştirme işlemini gösteren bir grafik günlüğü

Ve şimdi yerel olarak yinelenen taahhütlerimiz var. Eğer koşarsakgit push olsaydık, onları sunucuya göndeririz.

Bu aşamaya gelmekten kaçınmak için koşabilirdik git push --force(bunun yerine koştuğumuz yere git pull). Bu, yeni karmalarla bizim taahhütlerimizi sorunsuz bir şekilde sunucuya gönderirdi. Bu aşamada sorunu çözmek için, koşmadan önce sıfırlayabiliriz git pull:

Reflog (bak git reflogkarma oldu işlemek ne olduğunu görmek için) önce biz koştu git pull.

070e71d HEAD@{1}: pull: Merge made by the 'recursive' strategy.
ba7688a HEAD@{2}: rebase -i (finish): returning to refs/heads/master
ba7688a HEAD@{3}: rebase -i (pick): C5
44085d5 HEAD@{4}: rebase -i (pick): C4
961390d HEAD@{5}: commit (amend): C3
3cb46a9 HEAD@{6}: cherry-pick: fast-forward
85f59ab HEAD@{7}: rebase -i (start): checkout HEAD~~~
2a2e220 HEAD@{8}: rebase -i (finish): returning to refs/heads/master
2a2e220 HEAD@{9}: rebase -i (start): checkout refs/remotes/origin/master
2a2e220 HEAD@{10}: commit: C5
ab1bda4 HEAD@{11}: commit: C4
3cb46a9 HEAD@{12}: commit: C3
85f59ab HEAD@{13}: commit: C2
4516164 HEAD@{14}: commit: C1
0e783a3 HEAD@{15}: commit (initial): C0

Yukarıda ba7688a, koşmadan önce yaptığımız taahhüt buydu git pull. Elimizdeki bu commit karması ile buna ( git reset --hard ba7688a) geri dönebilir ve sonra çalıştırabiliriz git push --force.

Ve bitirdik.

Ama bekle, yinelenen kayıtlardan yola çıkmaya devam ettim

Eğer taahhütlerin tekrarlandığını bir şekilde fark etmediyseniz ve tekrarlanan taahhütler üzerinde çalışmaya devam ettiyseniz, gerçekten kendinize bir kargaşa yaratmışsınızdır. Karmaşanın boyutu, kopyaların üzerinde sahip olduğunuz işlemlerin sayısı ile orantılıdır.

Bu neye benziyor:

3b959b4 (HEAD, master) C10
8f84379 C9
0110e93 C8
6c4a525 C7
630e7b4 C6
070e71d (origin/master) Merge branch 'master' of bitbucket.org:username/test1
ba7688a C5
44085d5 C4
961390d C3
2a2e220 C5
85f59ab C2
ab1bda4 C4
4516164 C1
3cb46a9 C3
0e783a3 C0

Yinelenen işlemelerin üzerindeki doğrusal işlemeleri gösteren Git günlüğü

Veya başka bir yol gösterildi:

Yinelenen kaydetmelerin üzerindeki doğrusal işlemeleri gösteren bir günlük grafiği

Bu senaryoda, yinelenen taahhütleri kaldırmak, ancak onlara dayandırdığımız taahhütleri saklamak istiyoruz - C6'dan C10'a kadar tutmak istiyoruz. Çoğu şeyde olduğu gibi, bunu yapmanın birkaç yolu vardır:

Ya:

  • Son çoğaltılan kayıtta yeni bir şube oluşturun 1 ,cherry-pick her kayıt (C6'dan C10'a kadar) bu yeni şubeye ve bu yeni dalı standart olarak ele alın.
  • Çalıştırın git rebase --interactive $commit, her iki yinelenen $commitkaydetmeden önceki kaydetme nerede 2 . Burada kopyalar için satırları tamamen silebiliriz.

1 İkisinden hangisini seçtiğiniz önemli değil, ya ba7688ada 2a2e220iyi çalışıyor.

2 Örnekte olacaktır 85f59ab.

TL; DR

Set advice.pushNonFastForwardiçin false:

git config --global advice.pushNonFastForward false

1
Üç nokta "--rebase" seçeneğini (aka "-r") gizlediği sürece, "git pull ..." tavsiyesine uymakta sorun yoktur. ;-)
G. Sylvie Davies

4
Ben kullanarak öneriyoruz git pushs' --force-with-leasedaha iyi bir varsayılan olduğu gibi günümüzde
Whymarrh

4
Ya bu cevap ya da bir zaman makinesi. Teşekkürler!
ZeMoon

Çok düzgün bir açıklama ... Tekrar tekrar yeniden sunmayı denedikten sonra kodumu 5-6 kez kopyalayan benzer bir sorunla karşılaştım ... sadece kodun master ile güncel olduğundan emin olmak için ... ama her basıldığında şubemde yeni taahhütler, kodumu da çoğaltıyor. Eğer şubemde çalışan tek geliştirici bensem, burada force push (kiralama seçeneği ile) yapmanın güvenli olup olmadığını söyleyebilir misiniz? Ya da ustayı benimkiyle birleştirmek daha iyi bir yol mu?
Semih Singhal

12

Adımlarınızı anlatırken önemli bir detayı atladığınızı düşünüyorum. Daha spesifik olarak, geliştirmedeki son adımınız, git pushnormalde hızlı ileri olmayan değişiklikleri zorlayamayacağınız için aslında size bir hata verirdi.

Yani git pull, son itmeden önce yaptınız , bu da ebeveyn olarak C6 ve C6 'ile bir birleştirme taahhüdüyle sonuçlandı, bu nedenle her ikisi de günlükte listelenecek. Daha güzel bir günlük biçimi, yinelenen işlemlerin birleştirilmiş dalları olduklarını daha açık hale getirebilirdi.

Veya bunun yerine bir git pull --rebase(veya --rebaseyapılandırmanız tarafından ima edilmişse açık bir şekilde) yaptınız , bu da orijinal C5 ve C6'yı yerel geliştiricinize geri çekti (ve aşağıdakileri yeni karmalara, C7 'C5' 'C6' olarak yeniden düzenlediniz) ').

Bundan kurtulmanın bir yolu git push -f, hata verdiğinde itmeyi zorlamak ve C5 C6'yı başlangıç ​​noktasından silmek olabilirdi, ancak siz onları silmeden önce başka biri de onları çekmiş olsaydı, çok daha fazla sorun yaşarsınız .. Temelde C5 C6'ya sahip olan herkesin onlardan kurtulmak için özel adımlar atması gerekir. İşte tam da bu yüzden, daha önce yayınlanmış hiçbir şeyi asla yeniden açıklamamanız gerektiğini söylüyorlar. Yine de, "yayınlama" denen küçük bir ekip içindeyse yine de yapılabilir.


1
İhmal edilmesi git pullçok önemlidir. Sizin öneri git push -f, tehlikeli iken, okuyucular aradıklarını olasılıkla gerçek değildir.
Whymarrh

Aslında. Aslında yaptığım soruyu yazdığımda git push --force, sadece Git'in ne yapacağını görmek için. O zamandan beri Git hakkında çok şey öğrendim ve bugünlerde rebasenormal iş akışımın bir parçası. Ancak, git push --force-with-leasebaşkasının çalışmasının üzerine yazmaktan kaçınmak için yaparım .
elitalon

Kullanmak --force-with-leaseiyi bir varsayılandır, cevabımın altına da bir yorum bırakacağım
Whymarrh

2

Benim durumumda bu sorunun Git konfigürasyon probleminin bir sonucu olduğunu öğrendim. (Çekme ve birleştirme dahil)

Problemin tanımı:

Semptomlar: Rebase'den sonra alt dalda yinelenen işlemeler, yeniden yapılandırma sırasında ve sonrasında çok sayıda birleşme anlamına gelir.

İş akışı: Yapmakta olduğum iş akışının adımları şunlardır:

  • "Özellikler dalı" üzerinde çalışın ("Geliştirme dalı" nın alt öğesi)
  • "Özellikler dalında" değişiklikleri kaydet ve gönder
  • "Geliştirme dalını" (Özelliklerin Ana dalı) kontrol edin ve onunla çalışın.
  • Değişiklikleri "Geliştirme dalında" uygulayın ve aktarın
  • "Özellikler-dal" ı kontrol edin ve değişiklikleri depodan alın (Başka birinin iş vermesi durumunda)
  • "Özellikler dalını" "Geliştirme şubesi" üzerine yeniden düzenleyin
  • "Özellik dalı" üzerindeki değişikliklerin gücü

Bu iş akışının bir sonucu olarak, önceki yeniden temelden bu yana tüm "Özellik dalı" işlemlerinin çoğaltılması ... :-(

Sorun, yeniden ödemeden önce alt daldaki değişikliklerin çekilmesinden kaynaklanıyordu. Git varsayılan çekme yapılandırması "birleştirme" dir. Bu, alt dalda gerçekleştirilen işlemlerin endekslerini değiştiriyor.

Çözüm: Git yapılandırma dosyasında, çekmeyi yeniden taban modunda çalışacak şekilde yapılandırın:

...
[pull]
    rebase = preserve
...

Umarım JN Grx'e yardımcı olabilir


1

Akımınızdan farklı bir uzak şubeden çekmiş olabilirsiniz. Örneğin, şubeniz geliştirme takibi geliştirirken Master'dan çekmiş olabilirsiniz. Git, takip edilmeyen bir daldan çekilirse görev bilinciyle yinelenen taahhütleri çekecektir.

Bu olursa, aşağıdakileri yapabilirsiniz:

git reset --hard HEAD~n

nerede n == <number of duplicate commits that shouldn't be there.>

Ardından doğru daldan çektiğinizden emin olun ve ardından çalıştırın:

git pull upstream <correct remote branch> --rebase

Çekmek --rebase, taahhüt geçmişini bulanıklaştırabilecek gereksiz taahhütler eklememenizi sağlayacaktır.

İşte git rebase için biraz el tutma.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.