git merge: farklı bir dosyaya taşınan koda değişiklikleri uygulama


132

Şu anda oldukça kalın bir git birleştirme manevrası yapmaya çalışıyorum. Karşılaştığım bir sorun, şubemdeki bazı kodlarda bazı değişiklikler yapmam, ancak meslektaşımın bu kodu şubesindeki yeni bir dosyaya taşıması. Bunu yaptığımda git merge my_branch his_branchgit yeni dosyadaki kodun eski ile aynı olduğunu fark etmedi ve bu yüzden değişikliklerimin hiçbiri orada değil.

Değişikliklerimi yeni dosyalardaki koda yeniden uygulamanın en kolay yolu nedir? Hangi taahhütlerin yeniden uygulanması gerektiğini bulmakta çok fazla sorun yaşamayacağım (sadece kullanabilirim git log --stat). Ancak söyleyebileceğim kadarıyla git'in değişiklikleri yeni dosyalara yeniden uygulayabilmesini sağlamanın bir yolu yok. Şu anda gördüğüm en kolay şey değişiklikleri manuel olarak yeniden uygulamak, ki bu iyi bir fikir gibi görünmüyor.

Git'in dosyaları değil blobları tanıdığını biliyorum, bu yüzden ona "bu kesin kod değişikliğini, nerede olduğu değil, şimdi bu yeni dosyada olduğu yer dışında bu commit'den uygula" demenin bir yolu olmalı.


2
Tam olarak aynı değil, ama burada geçerli olabilecek iyi cevaplayıcılarla benzer bir soru var: stackoverflow.com/questions/2701790/…
Mariano Desanze

4
Cevapların hiçbiri git'in neden otomatik olarak böyle birleştirme yapamadığını açıklamıyor . Yeniden adları tespit edecek ve uygun birleştirme işlemini otomatik olarak gerçekleştirecek kadar akıllı olması gerektiğini düşündüm.
John

Belki soru sorulduğunda bu doğru değildi, ancak git'in modern sürümleri (1.9.5 kullanıyorum), yeniden adlandırılan ve taşınan dosyalardaki değişiklikleri birleştirebilir. --rename-thresholdGerekli olan benzerlik miktarını değiştirme seçeneği bile var .
Todd Owen

1
@ToddOwen, bir yukarı akış şubesinden bir vanilya rebase yapıyorsanız işe yarayacak, ancak dosyaları yeniden adlandıran commit içermeyen bir dizi değişikliği kiraz toplarsanız veya geri yüklerseniz yine de sorun yaşarsınız. .
GuyPaddock

Önce bir geri ödeme yaparsanız, bu sorun olur mu?
hbogert

Yanıtlar:


132

Benzer bir sorunla karşılaştım ve çalışmamı hedef dosya organizasyonuyla eşleşecek şekilde yeniden sıralayarak çözdüm.

Diyelim ki original.txtşubenizde ( localdalda) değişiklik yaptınız , ancak ana dalda original.txtbaşka bir şubeye kopyalandı diyelim copy.txt. Bu kopya, commit adını verdiğimiz bir işlemde yapılmıştır CP.

Üzerinde yapılan tüm yerel değişikliklerinizi, taahhütlerinizi Ave Başağıda yaptığınız değişiklikleri original.txtyeni dosyaya uygulamak istiyorsunuz copy.txt.

 ---- X -----CP------ (master)
       \ 
        \--A---B--- (local)

İle movedeğişikliklerinizin başlangıç ​​noktasında tek kullanımlık bir dal oluşturun git branch move X. Yani, birleştirmek istediğiniz taahhütlerden önceki moveşubeyi commit'ye koyun X; büyük olasılıkla, bu, değişikliklerinizi uygulamak için ayrıldığınız taahhüttür. Kullanıcının @digory doo'nun aşağıda yazdığı gibi, git merge-base master localbulmak için yapabilirsiniz X.

 ---- X (move)-----CP----- (master)
       \ 
        \--A---B--- (local)

Bu dalda aşağıdaki yeniden adlandırma komutunu verin:

git mv original.txt copy.txt

Bu, dosyayı yeniden adlandırır. Not copy.txthenüz bu noktada ağacında mevcut değildi.
Değişikliğinizi taahhüt edin (bu işleme adını veriyoruz MV).

        /--MV (move)
       /
 ---- X -----CP----- (master)
       \ 
        \--A---B--- (local)

Artık çalışmalarınızı şunların üstüne yeniden borçlandırabilirsiniz move:

git rebase move local

Bu sorunsuz çalışmalıdır ve değişiklikleriniz copy.txtyerel şubenizde uygulanır .

        /--MV (move)---A'---B'--- (local)
       /
 ---- X -----CP----- (master)

Şimdi, MVana şubenizin geçmişinde mutlaka commit istemezsiniz veya buna ihtiyaç duymazsınız , çünkü taşıma işlemi CPana daldaki commit'deki kopyalama işlemiyle çatışmaya yol açabilir .

Aşağıdaki gibi, taşıma işlemini iptal ederek yalnızca çalışmanızı yeniden kredilendirmeniz gerekir:

git rebase move local --onto CP

... diğer dalda tanıtıldığı kayıt CPnerede copy.txt. Bu copy.txt, CPtaahhüt üzerine tüm değişiklikleri yeniden oluşturur . Şimdi, localşubeniz tıpkı her zaman değiştirmişsiniz gibi copy.txtdeğil original.txt, başkalarıyla birleşmeye devam edebilirsiniz.

                /--A''---B''-- (local)
               /
 -----X-------CP----- (master)

Değişikliklerin uygulanması CPveya başka şekilde copy.txtmevcut olmaması ve değişikliklerin tekrar uygulanması önemlidir original.txt.

Umarım bu açıktır. Bu cevap geç gelir, ancak bu başka biri için faydalı olabilir.


2
Bu çok fazla iş ama bence prensipte çalışması gerekiyor. Yine de birleştirme konusunda yeniden ödemeden daha iyi şansa sahip olacağınızı düşünüyorum.
asmeurer

7
Bu çözüm, yamaları düzenlemenin veya kullanmanın patch(potansiyel olarak çok fazla çalışma gerektirir) aksine yalnızca temel git komutlarını içerir ve bu yüzden onu göstermenin ilginç olabileceğini düşündüm. Ayrıca, benim durumumda, cevabı yazmak için değişiklikleri uygulamaktan daha fazla zaman aldığımı unutmayın. Hangi adımda bir birleştirme kullanmayı tavsiye edersiniz? ve neden? Gördüğüm tek fark, yeniden ödeme MVyaparak, yalnızca birleştirmelerle mümkün olmayan , daha sonra atılan (commit ) geçici bir taahhütte bulunmam.
karanfil

1
Rebase ile, birleştirme çatışmalarıyla başa çıkma şansınız daha yüksektir, çünkü her bir taahhütle ilgilenirsiniz, oysa bir birleştirme ile her şeyi bir kerede ele alırsınız, yani bir geri ödemeyle gerçekleşmiş olabilecek bazı değişikliklerin birleştirme ile olmayacağı anlamına gelir. Yapacağım şey birleştirmek, ardından birleştirilmiş dosyayı manuel olarak taşımak. Belki de cevabın fikrini yanlış anladım.
asmeurer

4
Yaklaşımı açıklığa kavuşturmak için bazı ASCII ağaçları ekledim. Bu durumda birleştirmeye karşı yeniden düzenlemeye bakarsak: Yapmak istediğim, şubemdeki 'original.txt' üzerindeki tüm değişiklikleri almak ve bunları ana daldaki 'copy.txt' dosyasına uygulamak, çünkü bir nedenle 'orijinal. txt 'bir noktada' copy.txt 'içine kopyalandı (ve taşınmadı). Bu kopyadan sonra, ana dalda 'original.txt' de geliştirilmiş olabilir. Doğrudan birleştirirsem, original.txt üzerindeki yerel değişikliklerim, ana daldaki değiştirilmiş original.txt'ye uygulanacak ve birleştirilmesi zor olacak. Saygılarımızla.
coredump

1
Her iki durumda da, bu çözümün işe yarayacağına inanıyorum (şükür şu anda denemek için bir durumum olmasa da), bu yüzden şimdilik cevap olarak işaretleyeceğim.
asmeurer

31

Yamayı oluşturmak için her zaman git diff(veya git format-patch) tuşunu kullanabilir , ardından yama içindeki dosya adlarını manuel olarak düzenleyebilir ve git apply(veya git am) ile uygulayabilirsiniz .

Kısacası, otomatik olarak çalışmasının tek yolu, git'in yeniden adlandırma algılamasının eski ve yeni dosyaların aynı şey olduğunu anlayabilmesidir - ki bu, sizin durumunuzda gerçekten değil, sadece bir yığın gibi görünüyor. Git'in dosyaları değil blobları kullandığı doğrudur, ancak bir blob, dosya adı ve meta veriler eklenmeden yalnızca tüm dosyanın içeriğidir. Dolayısıyla, iki dosya arasında taşınan bir kod yığınınız varsa, bunlar gerçekten aynı blob değildir - blob içeriğinin geri kalanı farklıdır, yalnızca ortak yığın.


1
Şimdiye kadarki en iyi cevap bu. git format-patchBir taahhüt için nasıl çalışacağımı bulamadım . Bunu yaparsam git format-patch SHA1, tüm geçmiş için bir sürü yama dosyası oluşturur. Ama sanırım git show SHA1 > diff.patchişe yarayacak.
asmeurer

1
@asmeurer: -1seçeneği kullanın. Biçim yaması için normal çalışma modu, bir düzeltme aralığıdır origin/master..master, böylece bir yama serisini kolayca hazırlayabilirsiniz.
Cascabel

1
Aslında başka bir not. git applyve git amçok seçici çünkü aynı satır numaralarını istiyorlar. Ancak şu anda UNIX patchkomutuyla başarılı oluyorum .
asmeurer

24

Burada, yeniden adlandırma ve düzenleme ile bir birleştirme çakışmasıyla karşılaşmanın ve bunu doğru 3 birleştirme kaynak dosyalarını tanıyan birleştirme aracı ile çözmenin bir birleştirme çözümü.

  • Yeniden adlandırıldığını ve düzenlendiğini fark ettiğiniz 'silinmiş dosya' nedeniyle birleştirme başarısız olduktan sonra:

    1. Birleştirmeyi iptal edersiniz.
    2. Şubenizdeki yeniden adlandırılmış dosyaları tamamlayın.
    3. Ve tekrar birleşin.

Walk-yoluyla:

Bir file.txt oluşturun:

$ git init
Initialized empty Git repository in /tmp/git-rename-and-modify-test/.git/

$ echo "A file." > file.txt
$ git add file.txt
$ git commit -am "file.txt added."
[master (root-commit) 401b10d] file.txt added.
 1 file changed, 1 insertion(+)
 create mode 100644 file.txt

Daha sonra düzenleyeceğiniz bir dal oluşturun:

$ git branch branch-with-edits
Branch branch-with-edits set up to track local branch master.

Yeniden adlandırmayı oluşturun ve ana bilgisayarda düzenleyin:

$ git mv file.txt renamed-and-edited.txt
$ echo "edits on master" >> renamed-and-edited.txt 
$ git commit -am "file.txt + edits -> renamed-and-edited.txt."
[master def790f] file.txt + edits -> renamed-and-edited.txt.
 2 files changed, 2 insertions(+), 1 deletion(-)
 delete mode 100644 file.txt
 create mode 100644 renamed-and-edited.txt

Dallara geçiş yapın ve orada da düzenleyin:

$ git checkout branch-with-edits 
Switched to branch 'branch-with-edits'
Your branch is behind 'master' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)
$ 
$ echo "edits on branch" >> file.txt 
$ git commit -am "file.txt edited on branch."
[branch-with-edits 2c4760e] file.txt edited on branch.
 1 file changed, 1 insertion(+)

Master'ı birleştirme girişimi:

$ git merge master
CONFLICT (modify/delete): file.txt deleted in master and modified in HEAD. Version HEAD of file.txt left in tree.
Automatic merge failed; fix conflicts and then commit the result.

Çatışmanın çözülmesinin zor olduğuna ve dosyaların yeniden adlandırıldığına dikkat edin. İptal et, yeniden adlandırmayı taklit et:

$ git merge --abort
$ git mv file.txt renamed-and-edited.txt
$ git commit -am "Preparing for merge; Human noticed renames files were edited."
[branch-with-edits ca506da] Preparing for merge; Human noticed renames files were edited.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename file.txt => renamed-and-edited.txt (100%)

Tekrar birleştirmeyi deneyin:

$ git merge master
Auto-merging renamed-and-edited.txt
CONFLICT (add/add): Merge conflict in renamed-and-edited.txt
Recorded preimage for 'renamed-and-edited.txt'
Automatic merge failed; fix conflicts and then commit the result.

Harika! Birleştirme sonuçları, mergetool ile çözülebilecek 'normal' bir çatışmaya neden olur:

$ git mergetool
Merging:
renamed-and-edited.txt

Normal merge conflict for 'renamed-and-edited.txt':
  {local}: created file
  {remote}: created file
$ git commit 
Recorded resolution for 'renamed-and-edited.txt'.
[branch-with-edits 2264483] Merge branch 'master' into branch-with-edits

İlginç. Bu sorunla bir sonraki karşılaşmamda bunu denemem gerekecek.
asmeurer

1
Bu çözümde, bana öyle geliyor ki ilk adım, iptal edilen birleştirme, yalnızca hangi dosyaların uzaktan yeniden adlandırıldığını ve düzenlendiğini bulmak için yararlıdır. Onları önceden biliyorsanız. Bu adımı atlayabilirsiniz ve temelde çözüm, dosyaları yerel olarak manuel olarak yeniden adlandırmak ve ardından çakışmaları her zamanki gibi birleştirip çözmektir.

1
Teşekkür ederim. Durumum , git mv ile taşındım B.txt -> C.txtve A.txt -> B.txtgit birleştirme çakışmalarını otomatik olarak doğru şekilde eşleştiremedi (eski B.txtve yeni arasında birleştirme çakışmaları alıyordu B.txt). Bu yöntemi kullanarak, birleştirme çakışmaları artık doğru dosyalar arasındadır.
cib

Bu yalnızca tüm dosya taşındığında çalışır, ancak git bu durumu genellikle otomatik olarak algılamalıdır. Zor durum, bir dosyanın yalnızca bir kısmının taşınmasıdır.
Robin Green
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.