Git üst işaretçisini farklı bir üst öğeye ayarlama


220

Geçmişte bir ebeveyne işaret eden bir taahhüdüm olsa da, işaret ettiği ebeveyni değiştirmek istersem bunu nasıl yapabilirim?

Yanıtlar:


404

Kullanma git rebase. Git'teki jenerik "taahhüt (ler) i ve bunları farklı bir üst (taban) üzerine çiz" komutudur.

Ancak bilinmesi gereken bazı şeyler:

  1. Taahhütlü SHA'lar ebeveynlerini içerdiğinden, belirli bir taahhüdün ebeveynini değiştirdiğinizde, SHA'sı değişecektir - kalkınma çizgisinden sonra gelen (bundan daha yeni) tüm taahhütlerin SHA'ları değişecektir .

  2. Başka insanlarla çalışıyorsanız ve söz konusu taahhüdü daha önce çektikleri yere zaten ittiyseniz, taahhüdü değiştirmek muhtemelen bir Bad Idea ™ olur. Bunun nedeni 1 numaradır ve bu nedenle diğer kullanıcıların depolarının SHA'larınızın artık "aynı" taahhütlerle eşleşmemesi nedeniyle ne olduğunu anlamaya çalışırken karşılaşacakları karışıklık. (Ayrıntılar için bağlantılı kılavuz sayfasının "YUKARI AKIM TAVANINDAN KURTARMA" bölümüne bakın.)

Bununla birlikte, şu anda yeni bir ebeveyne taşımak istediğiniz bazı taahhütleri olan bir şubedeyseniz, şöyle bir şey olurdu:

git rebase --onto <new-parent> <old-parent>

Bu her şeyi hareket edecek sonra <old-parent> üstüne oturup cari dalında <new-parent>yerine.


Git rebase --onto'yu ebeveynleri değiştirmek için kullanıyorum ama sadece tek bir taahhütle bitmiş gibi görünüyor. Üzerine bir soru koydum: stackoverflow.com/questions/19181665/…
Eduardo Mello

7
Aha, bu yüzden ne --ontoiçin kullanılır! Bu cevabı okuduktan sonra bile dokümantasyon her zaman tamamen karanlıktı !
Daniel

6
Bu satır: Şimdiye kadar okuduğum diğer her soru ve belgenin git rebase --onto <new-parent> <old-parent>nasıl kullanılamayacağına rebase --ontodair her şeyi anlattım.
jpmc26

3
Benim için bu komut şöyle olmalıdır:, rebase this commit on that oneveya git rebase --on <new-parent> <commit>. Yaşlı ebeveyni burada kullanmak benim için bir anlam ifade etmiyor.
Samir Aguiar

1
@kopranb Eski ebeveyn, kafanızdan uzak kafanıza giden yolun bir kısmını atmak istediğinizde önemlidir. Tanımladığınız vaka tarafından ele alınmaktadır git rebase <new-parent>.
clacke

35

Sonraki taahhütlerin yeniden temellendirilmesinden kaçınmanız gerektiği ortaya çıkarsa (örneğin, bir geçmiş yeniden yazma işleminin yapılamayacağı için), git yerine kullanabilirsiniz (Git 1.6.5 ve sonraki sürümlerde kullanılabilir).

# …---o---A---o---o---…
#
# …---o---B---b---b---…
#
# We want to transplant B to be "on top of" A.
# The tree of descendants from B (and A) can be arbitrarily complex.

replace_first_parent() {
    old_parent=$(git rev-parse --verify "${1}^1") || return 1
    new_parent=$(git rev-parse --verify "${2}^0") || return 2
    new_commit=$(
      git cat-file commit "$1" |
      sed -e '1,/^$/s/^parent '"$old_parent"'$/parent '"$new_parent"'/' |
      git hash-object -t commit -w --stdin
    ) || return 3
    git replace "$1" "$new_commit"
}
replace_first_parent B A

# …---o---A---o---o---…
#          \
#           C---b---b---…
#
# C is the replacement for B.

Yukarıdaki değiştirme oluşturulduğunda, B nesnesine yönelik tüm istekler aslında C nesnesini döndürür. C'nin içeriği, ilk ebeveyn (aynı ebeveynler (ilk hariç), aynı ağaç hariç, B içeriğiyle tam olarak aynıdır, aynı taahhüt mesajı).

Değiştirmeler varsayılan olarak etkindir ancak git--no-replace-objects seçeneğine (komut adından önce) veya ortam değişkenine ayarlanarak etkinleştirilebilir . Değiştirmeler iterek paylaşılabilir (normalin yanı sıra ).GIT_NO_REPLACE_OBJECTSrefs/replace/*refs/heads/*

Commit-munging'i beğenmediyseniz ( yukarıdaki sed ile yapılır ), daha yüksek seviye komutlarını kullanarak değiştirme taahhüdünüzü oluşturabilirsiniz:

git checkout B~0
git reset --soft A
git commit -C B
git replace B HEAD
git checkout -

Büyük fark, eğer B bir birleştirme taahhüdü ise, bu dizinin ek ebeveynleri yaymamasıdır.


Ve sonra, değişiklikleri mevcut bir depoya nasıl itersiniz? Yerel olarak çalışıyor gibi görünüyor, ama git push bundan sonra hiçbir şey
itmiyor

2
@BaptisteWicht: Değiştirmeler ayrı bir refs kümesinde saklanır. refs/replace/Ref hiyerarşisini zorlamanız (ve getirmeniz) gerekir . Sadece bir kez yapmanız gerekiyorsa git push yourremote 'refs/replace/*', kaynak havuzda ve git fetch yourremote 'refs/replace/*:refs/replace/*'hedef depolarda yapabilirsiniz. Bunu birkaç kez yapmanız gerekiyorsa, bu refspec'leri bir remote.yourremote.pushyapılandırma değişkenine (kaynak havuzunda) ve bir remote.yourremote.fetchyapılandırma değişkenine (hedef depolarında) ekleyebilirsiniz .
Chris Johnsen

3
Bunu yapmanın daha basit bir yolu kullanmaktır git replace --grafts. Bunun ne zaman eklendiğinden emin değilim, ancak tüm amacı taahhüt edilen B ile aynı olan ancak belirtilen ebeveynlerle bir değiştirme oluşturmaktır .
Paul Wagland

32

Git'teki bir taahhüdü değiştirmenin, onu izleyen tüm taahhütlerin değiştirilmesi gerektiğini unutmayın. Tarihin bu bölümünü yayınladıysanız cesaretiniz kırılır ve birileri tarih üzerindeki çalışmalarını değişimden önceki haline getirmiş olabilir.

Amber'in yanıtındagit rebase bahsedilen alternatif çözüm , bir taahhüdün üst öğesini değiştirmek için greft mekanizmasını kullanmak ( Git Terimler Sözlüğü'ndeki Git greftlerinin tanımına ve Git Depo Düzeni dokümantasyonundaki dosyanın belgelerine bakın), bazı geçmiş görüntüleyiciyle doğru bir şey yaptığını kontrol etmektir ( , ve vb.) kullanın ve kalıcı hale getirmek için (kılavuzunun "Örnekler" bölümünde açıklandığı gibi) kullanın (ve ardından grefti çıkarın ve isteğe bağlı olarak yedeklenen veya yeniden depolanan orijinal referansları kaldırın ):.git/info/graftsgitkgit log --graphgit filter-branchgit filter-branch

echo "$ commit-id $ graft-id" >> .git / info / greft
git filter-branch $ graft-id..HEAD

NOT !!! Bu çözüm Rebase çözümden farklı o git rebaserebase ediyorum / nakli değişiklikleri , greftler tabanlı çözüm basitçe Reparent onaylatabilirsiniz olacağını ise olduğu gibi , eski ebeveyn ve yeni ebeveyn arasındaki farklılıkları dikkate almayan!


3
Anladığım kadarıyla ( git.wiki.kernel.org/index.php/GraftPoint'ten ) git replacegit greftlerinin yerini aldı (git 1.6.5 veya üstü olduğu varsayılarak).
Alexander Bird

4
@ Thr4wn: Büyük ölçüde doğrudur, ancak geçmişi yeniden yazmak istiyorsanız, greftler + git-filter-branch (veya etkileşimli rebase veya hızlı dışa aktarma + örneğin reposurgeon) bunu yapmanın yoludur. Geçmişi korumak istiyorsanız / gitmeniz gerekiyorsa , git-replace greftlerden çok daha üstündür.
Jakub Narębski

@ JakubNarębski, rewrite vs preserve history ne demek istediğinizi açıklayabilir misiniz ? Neden git-replace+ kullanamıyorum git-filter-branch? Benim testte, filter-branchtüm ağaç rebasing, değiştirme saygı gibiydi.
cdunn2001

1
@ cdunn2001: git filter-branchgreftlere ve replasmanlara saygılı olarak tarihi yeniden yazar (ancak yeniden yazılan geçmiş replasmanları tartışmalıdır); itme, ileri doğru olmayan değişikliklere neden olur. git replaceyerinde devredilebilir yedekler oluşturur; push hızlı ileri olacaktır, ancak refs/replacedüzeltmeleri geçmişe sahip olmak için değiştirmeleri aktarmak için push yapmanız gerekir . HTH
Jakub Narębski

23

Yukarıdaki cevapları açıklığa kavuşturmak ve utanmazca kendi senaryomu eklemek için:

Bu, "yeniden" veya "tövbe etmek" isteyip istemediğinize bağlıdır. Bir Rebase tarafından önerildiği gibi, Amber , dolaşır diffs . Bir inkarcı , Jakub ve Chris'in önerdiği gibi , tüm ağacın anlık görüntüleri etrafında hareket eder . Tövbe etmek istiyorsanız git reparent, işi manuel olarak yapmak yerine kullanmanızı öneririm .

karşılaştırma

Resmin solda olduğunu ve sağdaki resim gibi görünmesini istediğinizi varsayalım:

                   C'
                  /
A---B---C        A---B---C

Hem yeniden basma hem de yeniden oluşturma aynı resmi üretecektir, ancak tanımı C'farklıdır. İle git rebase --onto A B, C'tarafından getirilen herhangi bir değişiklik içermez B. İle git reparent -p A, C'özdeş olacak C(hariç B, tarihte olmayacak).


1
+1 reparentSenaryoyu denedim; güzel çalışıyor; şiddetle tavsiye edilir . Ayrıca yeni bir branchetiket oluşturmanızı ve checkoutaramadan önce bu şubeye (şube etiketi hareket edeceği için) öneririm .
Rhubbarb

Ayrıca şunu belirtmek gerekir: (1) orijinal düğüm bozulmadan kalır ve (2) yeni düğüm orijinal düğümün alt öğelerini devralmaz.
Rhubbarb

0

Kesinlikle @ Jakub'ın cevabı birkaç saat önce OP ile aynı şeyi denediğimde bana yardımcı oldu.
Ancak git replace --graftşimdi greftlerle ilgili daha kolay bir çözümdür. Ayrıca, bu çözümle ilgili büyük bir sorun, filtre şubesinin, HEAD'ın şubesiyle birleştirilmeyen her dalı kaybetmemi sağlamasıydı. Sonra git filter-repoişi mükemmel ve kusursuz bir şekilde yaptı.

$ git replace --graft <commit> <new parent commit>
$ git filter-repo --force

Daha fazla bilgi için: Dokümanlardaki "Geçmişi yeniden aşılama" bölümüne bakın

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.