Normal bir git rebaseinde olduğu gibi, git with --preserve-merges
ilk olarak taahhüt grafiğinin bir bölümünde yapılan taahhütlerin bir listesini tanımlar ve daha sonra bu taahhütleri başka bir bölümün üstüne tekrar oynatır. --preserve-merges
Kaygı ile ilgili farklılıklar tekrar için seçilir ve yeniden oynatmanın birleştirme taahhütlerinde nasıl çalıştığı.
Normal ve birleştirme koruyucu rebase arasındaki temel farklar hakkında daha açık olmak için:
- Birleştirme koruyan rebase (bazı) birleştirme işlemlerini yeniden yürütmeye istekliyken, normal rebase birleştirme işlemlerini tamamen yok sayar.
- Birleştirme taahhütlerini yeniden oynatmaya istekli olduğu için, birleştirme koruma rebase bir birleştirme taahhüdünü yeniden oynatmanın ne anlama geldiğini tanımlamalı ve bazı ekstra kırışıklıklar ile uğraşmalıdır.
- En ilginç kısım, kavramsal olarak, belki de yeni taahhüdün birleştirme ebeveynlerinin ne olması gerektiğini seçmektir.
- Birleştirme işlemlerinin yeniden yürütülmesi ayrıca belirli taahhütlerin (
git checkout <desired first parent>
) açıkça kontrol edilmesini gerektirir , oysa normal rebase bunun için endişelenmesine gerek yoktur.
- Birleştirmeyi koruyan rebase, yeniden oynatma için daha sığ bir dizi işi dikkate alır:
- Özellikle, yalnızca en son birleştirme üslerinden (yani son iki dalın ayrıştığından beri) yapılan taahhütlerin tekrarını dikkate alırken, normal yeniden taban iki dalın ilk kez ayrıldığı taahhütleri tekrarlayabilir .
- Geçici ve belirsiz olmak için, bunun nihayetinde birleştirme taahhüdüne "dahil edilmiş olan" eski taahhütleri "tekrar etmenin bir yolu olduğuna inanıyorum.
İlk önce, rebase'in ne yaptığını "yeterince tam olarak" anlatmaya çalışacağım --preserve-merges
ve sonra bazı örnekler vereceğim. Daha faydalı görünüyorsa, elbette örnekler ile başlanabilir.
"Kısa" da Algoritma
Yabancı otlara gerçekten girmek istiyorsanız, git kaynağını indirin ve dosyayı keşfedin git-rebase--interactive.sh
. (Rebase, Git'in C çekirdeğinin bir parçası değildir, aksine bash ile yazılır. Ve perde arkasında "interaktif rebase" ile kod paylaşır.)
Ama burada bunun özü olduğunu düşündüğüm çizimi çizeceğim. Düşünülecek şey sayısını azaltmak için birkaç özgürlük aldım. (örneğin, hesaplamaların yapıldığı kesin sırayı% 100 doğrulukla yakalamaya çalışmıyorum ve daha az merkezi görünen bazı konuları görmezden gelmiyorum, örneğin şubeler arasında önceden seçilmiş komisyonlar hakkında ne yapacağım).
İlk olarak, birleştirme korumalı olmayan bir tabanın oldukça basit olduğunu unutmayın. Az ya da çok:
Find all commits on B but not on A ("git log A..B")
Reset B to A ("git reset --hard A")
Replay all those commits onto B one at a time in order.
Rebase --preserve-merges
nispeten karmaşıktır. İşte oldukça önemli görünen şeyleri kaybetmeden yapabildiğim kadar basit:
Find the commits to replay:
First find the merge-base(s) of A and B (i.e. the most recent common ancestor(s))
This (these) merge base(s) will serve as a root/boundary for the rebase.
In particular, we'll take its (their) descendants and replay them on top of new parents
Now we can define C, the set of commits to replay. In particular, it's those commits:
1) reachable from B but not A (as in a normal rebase), and ALSO
2) descendants of the merge base(s)
If we ignore cherry-picks and other cleverness preserve-merges does, it's more or less:
git log A..B --not $(git merge-base --all A B)
Replay the commits:
Create a branch B_new, on which to replay our commits.
Switch to B_new (i.e. "git checkout B_new")
Proceeding parents-before-children (--topo-order), replay each commit c in C on top of B_new:
If it's a non-merge commit, cherry-pick as usual (i.e. "git cherry-pick c")
Otherwise it's a merge commit, and we'll construct an "equivalent" merge commit c':
To create a merge commit, its parents must exist and we must know what they are.
So first, figure out which parents to use for c', by reference to the parents of c:
For each parent p_i in parents_of(c):
If p_i is one of the merge bases mentioned above:
# p_i is one of the "boundary commits" that we no longer want to use as parents
For the new commit's ith parent (p_i'), use the HEAD of B_new.
Else if p_i is one of the commits being rewritten (i.e. if p_i is in R):
# Note: Because we're moving parents-before-children, a rewritten version
# of p_i must already exist. So reuse it:
For the new commit's ith parent (p_i'), use the rewritten version of p_i.
Otherwise:
# p_i is one of the commits that's *not* slated for rewrite. So don't rewrite it
For the new commit's ith parent (p_i'), use p_i, i.e. the old commit's ith parent.
Second, actually create the new commit c':
Go to p_1'. (i.e. "git checkout p_1'", p_1' being the "first parent" we want for our new commit)
Merge in the other parent(s):
For a typical two-parent merge, it's just "git merge p_2'".
For an octopus merge, it's "git merge p_2' p_3' p_4' ...".
Switch (i.e. "git reset") B_new to the current commit (i.e. HEAD), if it's not already there
Change the label B to apply to this new branch, rather than the old one. (i.e. "git reset --hard B")
--onto C
Tartışmalı bir temele çok benzer olmalıdır. B'nin HEAD'ında kesin yürütmeyi başlatmak yerine, C'nin HEAD'ında kesin yürütmeyi başlatın. (Ve B_new yerine C_new kullanın.)
örnek 1
Örneğin, taahhüt grafiği alın
B---C <-- master
/
A-------D------E----m----H <-- topic
\ /
F-------G
m, E ve G ebeveynleriyle birleştirme taahhüdüdür.
Diyelim ki konuyu (H) masterın (C) üstüne, normal, birleştirme korumalı olmayan yeniden taban kullanarak yeniden oluşturduk. (Örneğin, ödeme konusu; rebase master .) Bu durumda, git yeniden oynatma için aşağıdaki işlemleri seçer:
- D seç
- E seç
- F seç
- G seç
- H seç
ve sonra kesinleştirme grafiğini şu şekilde güncelleyin:
B---C <-- master
/ \
A D'---E'---F'---G'---H' <-- topic
(D ', D'nin vb. Tekrarlanan eşdeğeridir.)
Tekrarlama için birleştirme taahhüdü m'nin seçilmediğine dikkat edin.
Bunun yerine --preserve-merges
C'nin üstünde bir H teması yaptıysak (Örneğin, ödeme konusu; rebase --preserve-merges master .) Bu yeni durumda git, yeniden oynatma için aşağıdaki taahhütleri seçer:
- D seç
- E seç
- F'yi seçin ('subtopic' dalında D'ye)
- G'yi seçin ('subtopic' dalında F'ye)
- 'subtopic' dalını konuya birleştir
- H seç
Şimdi m edildi çalınması için seçilen. Ayrıca, E ve G birleştirme ebeveynlerinin, m birleştirme işleminden önce dahil edilmek üzere seçildiğine dikkat edin.
Sonuçta ortaya çıkan tamamlama grafiği:
B---C <-- master
/ \
A D'-----E'----m'----H' <-- topic
\ /
F'-------G'
Yine, D ', D'nin kiraz tarafından seçilmiş (yani yeniden oluşturulmuş) bir versiyonudur. E', vb. İçin aynıdır. Master'da olmayan her taahhüt tekrar oynatılmıştır. Hem E hem de G (m'nin birleşik ebeveynleri) m 'nin ebeveynleri olarak hizmet etmek için E' ve G 'olarak yeniden yaratılmıştır (rebase'den sonra ağaç geçmişi hala aynı kalır).
ÖRNEK 2
Normal rebase'den farklı olarak, birleştirme koruyucu rebase yukarı akış kafasında birden fazla çocuk oluşturabilir.
Örneğin:
B---C <-- master
/
A-------D------E---m----H <-- topic
\ |
------- F-----G--/
H'yi (konu) C (master) üzerine yeniden tabanlaştırırsak, rebase için seçilen taahhütler şunlardır:
- D seç
- E seç
- F seç
- G seç
- m seç
- H seç
Ve sonuç şöyle:
B---C <-- master
/ | \
A | D'----E'---m'----H' <-- topic
\ |
F'----G'---/
ÖRNEK 3
Yukarıdaki örneklerde, orijinal birleştirme işleminin sahip olduğu orijinal ebeveynlerden ziyade, hem birleştirme taahhüdü hem de iki ebeveyni yeniden yürütülür. Bununla birlikte, diğer bazlarda, tekrar birleştirme birleştirme taahhüdü, birleştirme işleminden önce zaten taahhüt grafiğinde olan ebeveynlerle sonuçlanabilir.
Örneğin:
B--C---D <-- master
/ \
A---E--m------F <-- topic
Konuyu master'a yeniden birleştirirsek (birleştirmeleri koruyarak), yeniden oynatma taahhütleri
- birleştirme taahhüdü seç
- F seç
Yeniden yazılan tamamlama grafiği şöyle görünecektir:
B--C--D <-- master
/ \
A-----E---m'--F'; <-- topic
Burada tekrarlanan birleştirme taahhüdü m ', tamamlama grafiğinde önceden var olan ebeveynleri, yani D (master'ın HEAD'i) ve E'yi (orijinal birleştirme taahhüdünün m ebeveynlerinden biri) alır.
Örnek 4
Birleştirme koruyan rebase, bazı "boş taahhüt" davalarında karışabilir. En azından bu sadece git'in bazı eski sürümleri için geçerlidir (örneğin 1.7.8.)
Bu taahhüt grafiğini alın:
A--------B-----C-----m2---D <-- master
\ \ /
E--- F--\--G----/
\ \
---m1--H <--topic
Hem m1 hem de m2'nin B ve F'deki tüm değişiklikleri içermesi gerektiğini unutmayın.
git rebase --preserve-merges
H (konu) öğesini D (master) üzerine yapmaya çalışırsak , tekrar oynamak için aşağıdaki taahhütler seçilir:
M1'de birleştirilen değişikliklerin (B, F) zaten D'ye dahil edilmesi gerektiğini unutmayın. (Bu değişiklikler zaten m2'ye dahil edilmelidir, çünkü m2 B ve F'nin çocuklarını birleştirir.) Bu nedenle, kavramsal olarak, m1'i D muhtemelen ya bir işlem yapılmamalı ya da boş bir taahhüt oluşturmalıdır (yani ardışık revizyonlar arasındaki farkın boş olduğu bir işlem).
Ancak bunun yerine git, D'nin üstünde m1'i tekrar oynatma girişimini reddedebilir. Bunun gibi bir hata alabilirsiniz:
error: Commit 90caf85 is a merge but no -m option was given.
fatal: cherry-pick failed
Birinin git'e bir bayrak vermeyi unuttuğu anlaşılıyor, ancak altta yatan sorun git'in boş taahhütler oluşturmayı sevmemesidir.
git --rebase-merges
eski olanın yerini alacakgit --preserve-merges
. Aşağıdaki cevabımı