Dosya geçmişini bozmadan iki Git deposunu birleştir


226

İki Git deposunu yepyeni, üçüncü bir depoda birleştirmem gerekiyor. Ben (örneğin bir alt ağaç birleştirme kullanarak bunu nasıl birçok açıklamaları buldum Jakub Narębski cevabı üzerine iki Git havuzlarını birleştirme nasıl? ) Ve tüm dosyaların alt ağaç birleştirme taahhüt zaman bu talimatları izleyerek çoğunlukla dışında çalışır eski depolardan yeni eklenen dosyalar olarak kaydedilir. Yaptığımda eski depolardaki taahhüt geçmişini görebiliyorum git log, ama eğer yaparsam git log <file>bu dosya için sadece bir taahhüt gösteriyor - alt ağaç birleştirmesi. Yukarıdaki cevabın yorumlarına bakılırsa, bu problemi görmede yalnız değilim ama bunun için yayınlanmış bir çözüm bulamadım.

Depoları birleştirmenin ve tek tek dosya geçmişini olduğu gibi bırakmanın herhangi bir yolu var mı?


Git'i kullanmıyorum, ancak Mercurial'da ilk önce birleştirilecek depoların dosya yollarını düzeltmek için gerekirse bir dönüştürme yapacağım ve daha sonra değişiklik kümelerini almak için bir repo'yu hedefe zorla çekip farklı dalların birleşmesi. Bu test edilir ve çalışır;) Belki de Git için de bir çözüm bulmaya yardımcı olur ... alt ağaç birleştirme yaklaşımına kıyasla Sanırım dönüştürme adımı, yalnızca bir yol eşlemek yerine tarihin yeniden yazıldığı yerde farklıdır (anlıyorsam) doğru şekilde). Bu daha sonra dosya yollarında herhangi özel bir işlem yapılmadan sorunsuz birleştirme sağlar.
Lucero

Bu soruyu da yararlı buldum stackoverflow.com/questions/1683531/…
nacross

Bir takip sorusu yarattım. İlginç olabilir: İki Git deposunu birleştirin ve ana geçmişi saklayın: stackoverflow.com/questions/42161910/…
Dimitri Dewaele

Benim için çalışan otomatik çözüm stackoverflow.com/a/30781527/239408
xverges

Yanıtlar:


269

İki deposu birbirine yapıştırmaya ve harici bir bağımlılığı yönetmek yerine bu şekilde görünmeye çalışıyorsanız, cevabın çok daha basit olduğu ortaya çıkıyor. Eski depolarınıza uzaktan kumanda eklemeniz, yeni ana verilerinizle birleştirmeniz, dosyaları ve klasörleri bir alt dizine taşımanız, taşımayı tamamlamanız ve tüm ek depolar için tekrarlamanız yeterlidir. Alt modüller, alt ağaç birleşmeleri ve süslü baslar biraz farklı bir sorunu çözmeyi amaçlıyor ve yapmaya çalıştığım şeyler için uygun değil.

İki deposu birbirine yapıştırmak için bir Powershell betiği:

# Assume the current directory is where we want the new repository to be created
# Create the new repository
git init

# Before we do a merge, we have to have an initial commit, so we'll make a dummy commit
git commit --allow-empty -m "Initial dummy commit"

# Add a remote for and fetch the old repo
git remote add -f old_a <OldA repo URL>

# Merge the files from old_a/master into new/master
git merge old_a/master --allow-unrelated-histories

# Move the old_a repo files and folders into a subdirectory so they don't collide with the other repo coming later
mkdir old_a
dir -exclude old_a | %{git mv $_.Name old_a}

# Commit the move
git commit -m "Move old_a files into subdir"

# Do the same thing for old_b
git remote add -f old_b <OldB repo URL>
git merge old_b/master --allow-unrelated-histories
mkdir old_b
dir exclude old_a,old_b | %{git mv $_.Name old_b}
git commit -m "Move old_b files into subdir"

Açıkçası bunun yerine old_b'yi old_a (yeni birleşik repo haline gelir) ile birleştirebilirsiniz - betiği uygun şekilde değiştirin.

Devam eden özellik dallarını da getirmek istiyorsanız, bunu kullanın:

# Bring over a feature branch from one of the old repos
git checkout -b feature-in-progress
git merge -s recursive -Xsubtree=old_a old_a/feature-in-progress

Sürecin açık olmayan tek kısmı bu - bir alt ağaç birleşmesi değil, Git'e hedefi yeniden adlandırdığımızı ve Git'in her şeyi doğru bir şekilde hizalamasına yardımcı olan normal özyinelemeli birleştirme argümanı.

Burada biraz daha ayrıntılı bir açıklama yazdım .


16
bu çözüm git mvçok iyi çalışmıyor. daha sonra git logtaşınan dosyalardan birinde a kullandığınızda , yalnızca işlemden taahhüt alırsınız. önceki tüm geçmişler kaybolur. bunun nedeni git mvgerçekten git rm; git addama bir adımda olmasıdır .
mholm815

15
Git'teki diğer herhangi bir taşıma / yeniden adlandırma işlemi ile aynıdır: komut satırından yaparak git log --followtüm geçmişi edinebilirsiniz veya tüm GUI araçları bunu sizin için otomatik olarak yapar. Alt ağaç birleştirme ile bildiğim kadarıyla tek tek dosyaların geçmişini alamazsınız , bu nedenle bu yöntem daha iyidir.
Eric Lee

3
@EricLee Old_b repo birleştirildiğinde bir sürü birleşme çatışması yaşıyorum. Bu beklenen mi? ÇATIŞMA alıyorum (yeniden adlandır / sil)
Jon

9
"Dir -exclude old_a |% {git mv $ _. Name old_a}" girişiminde bulunduğumda, sh.exe dosyasını alıyorum: dir: komut bulunamadı ve sh.exe ": git: komut bulunamadı. Bu çalışır kullanarak: ls -I old_a | xargs -I '{}' git mv '{}' old_a /
George

5
Bu 1(bir numara) için lsve sermaye 'gözü' için xargs. Bu ipucu için teşekkürler!
Dominique Vial

149

İşte hiçbir geçmişi yeniden yazmayan bir yol, bu nedenle tüm taahhüt kimlikleri geçerliliğini korur. Sonuç, ikinci repo'nun dosyalarının bir alt dizine gelmesidir.

  1. İkinci repoyu uzaktan kumanda olarak ekleyin:

    cd firstgitrepo/
    git remote add secondrepo username@servername:andsoon
    
  2. İkinci raporun tüm taahhütlerini indirdiğinizden emin olun:

    git fetch secondrepo
    
  3. İkinci deponun şubesinden yerel bir şube oluşturun:

    git branch branchfromsecondrepo secondrepo/master
    
  4. Tüm dosyalarını bir alt dizine taşıyın:

    git checkout branchfromsecondrepo
    mkdir subdir/
    git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} subdir/
    git commit -m "Moved files to subdir/"
    
  5. İkinci dalı ilk repo'nun ana dalına birleştirin:

    git checkout master
    git merge --allow-unrelated-histories branchfromsecondrepo
    

Deponuzda birden fazla kök işlemi olacaktır, ancak bu bir sorun oluşturmamalıdır.


1
Adım 2 benim için çalışmıyor: ölümcül: Geçerli bir nesne adı değil: 'secondrepo / master'.
Keith

@Keith: İkinci repoyu "secondrepo" adlı bir uzak olarak eklediğinizden ve bu repoda "master" adlı bir şube bulunduğundan emin olun (uzak bir repodaki dalları komutla görüntüleyebilirsiniz git remote show secondrepo)
Flimm

Onu aşağı çekmek için bir getirme yapmak zorunda kaldım. 1 ve 2 arasında git fetre secondrepo
sksamuel

@monkjack: Cevabımı git getirme adımını içerecek şekilde düzenledim. Cevabı gelecekte kendiniz düzenlemekten çekinmeyin.
Mart'ta Flimm

4
@MartijnHeemels Git'in eski sürümü için, atlayın --allow-unrelated-histories. Bu yanıt gönderisinin geçmişine bakın.
Flimm

8

Birkaç yıl geçti ve iyi temelli yukarı-oylanmış çözümler var ama benimkini paylaşmak istiyorum çünkü biraz farklıydı çünkü 2 uzak depoyu, önceki depolardan geçmişi silmeden yeni bir depoda birleştirmek istedim.

  1. Github'da yeni bir havuz oluşturun.

    resim açıklamasını buraya girin

  2. Yeni oluşturulan depoyu indirin ve eski uzak depoyu ekleyin.

    git clone https://github.com/alexbr9007/Test.git
    cd Test
    git remote add OldRepo https://github.com/alexbr9007/Django-React.git
    git remote -v
    
  3. Yeni bir şube oluşturmak için eski depodaki tüm dosyaları getir.

    git fetch OldRepo
    git branch -a
    

    resim açıklamasını buraya girin

  4. Ana dalda, eski repoyu yeni oluşturulan ile birleştirmek için birleştirme yapın.

    git merge remotes/OldRepo/master --allow-unrelated-histories
    

    resim açıklamasını buraya girin

  5. OldRepo'dan eklenen tüm yeni oluşturulan içeriği depolamak için yeni bir klasör oluşturun ve dosyalarını bu yeni klasöre taşıyın.

  6. Son olarak, dosyaları birleştirilmiş depolardan yükleyebilir ve OldRepo'yu GitHub'dan güvenle silebilirsiniz.

Umarım bu, uzak depoları birleştirmekle uğraşan herkes için yararlı olabilir.


1
Git tarihini korumak için benim için çalışan tek çözüm bu. Eski repo ile olan uzak bağlantıyı kaldırmayı unutmayın git remote rm OldRepo.
Harubiyori

7

lütfen kullanmaya bir göz atın

git rebase --root --preserve-merges --onto

hayatlarında erken iki tarihi birbirine bağlamak için.

Çakışan yollarınız varsa, bunları düzeltin

git filter-branch --index-filter

günlüğü kullandığınızda, "kopyaları daha zor bul"

git log -CC

bu şekilde yoldaki dosya hareketlerini bulacaksınız.


Git belgeleri yeniden baslamamanızı
Stephen Turner

7

Ben @Flimm gelen çözümgit alias böyle bir (benim eklendi ~/.gitconfig) içine döndü :

[alias]
 mergeRepo = "!mergeRepo() { \
  [ $# -ne 3 ] && echo \"Three parameters required, <remote URI> <new branch> <new dir>\" && exit 1; \
  git remote add newRepo $1; \
  git fetch newRepo; \
  git branch \"$2\" newRepo/master; \
  git checkout \"$2\"; \
  mkdir -vp \"${GIT_PREFIX}$3\"; \
  git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} \"${GIT_PREFIX}$3\"/; \
  git commit -m \"Moved files to '${GIT_PREFIX}$3'\"; \
  git checkout master; git merge --allow-unrelated-histories --no-edit -s recursive -X no-renames \"$2\"; \
  git branch -D \"$2\"; git remote remove newRepo; \
}; \
mergeRepo"

12
Sadece merak ediyorum: bunu gerçekten bir takma ada ihtiyaç duyacak kadar sık ​​mı yapıyorsunuz?
Parker Coates

1
Hayır, nasıl yapılacağını asla hatırlamıyorum, bu yüzden takma ad hatırlamamın bir yoludur.
Fredrik Erlandsson

1
Evet .. ama bilgisayarları değiştirmeyi ve takma adlarınızı hareket ettirmeyi unutmayı deneyin;)
quetzalcoatl

1
Değeri nedir $GIT_PREFIX?
neowulf33

github.com/git/git/blob/… 'GIT_PREFIX', orijinal geçerli dizinden 'git rev-parse --show-prefix' çalıştırılarak döndürülür. Bkz. Linkgit: git-rev-parse [1].
Fredrik Erlandsson

3

Bu işlev uzaktan repoyu yerel repo dizinine kopyalar:

function git-add-repo
{
    repo="$1"
    dir="$(echo "$2" | sed 's/\/$//')"
    path="$(pwd)"

    tmp="$(mktemp -d)"
    remote="$(echo "$tmp" | sed 's/\///g'| sed 's/\./_/g')"

    git clone "$repo" "$tmp"
    cd "$tmp"

    git filter-branch --index-filter '
        git ls-files -s |
        sed "s,\t,&'"$dir"'/," |
        GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
    ' HEAD

    cd "$path"
    git remote add -f "$remote" "file://$tmp/.git"
    git pull "$remote/master"
    git merge --allow-unrelated-histories -m "Merge repo $repo into master" --edit "$remote/master"
    git remote remove "$remote"
    rm -rf "$tmp"
}

Nasıl kullanılır:

cd current/package
git-add-repo https://github.com/example/example dir/to/save

Farkına varmak. Bu komut dosyası komutları yeniden yazabilir, ancak tüm yazarları ve tarihleri ​​kaydeder, yeni komisyonların başka bir karması olacaktır ve değişiklikleri uzak sunucuya aktarmaya çalışırsanız, yalnızca zorla anahtarı ile mümkün olabilir, ayrıca sunucudaki taahhütleri yeniden yazacaktır. Bu yüzden lütfen lansmandan önce yedekleyin.

Kar!


Ben bash yerine zsh ve git v2.13.0 kullanıyorum. Ne denediğim önemli değil git filter-branch --index-filter, işe başlayamadım. Genellikle .new dizin dosyası yok bir hata iletisi alıyorum. Bu çanlar çalıyor mu?
Patrick Beard

@PatrickBeard zsh bilmiyorum, git-add-repo.shyukarıdaki fonksiyonu ile ayrılmış dosya oluşturabilirsiniz , dosyanın sonunda bu satırı koyun git-add-repo "$@". Bundan sonra zsh gibi kullanabilirsiniz cd current/git/packagevebash path/to/git-add-repo.sh https://github.com/example/example dir/to/save
Andrey Izman

Sorun burada tartışıldı: stackoverflow.com/questions/7798142/… mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE" bazen başarısız oluyor, bu yüzden bir eklemeniz gerekiyor if test.
Patrick Beard

1
Bu yöntemi kullanmazdım! Senaryoyu safça ve kelimesi kelimesine denedim (sadece bu kısım için kendimi suçlayabilirim) ve yerel git repo'yu tıkadı. Geçmiş çoğunlukla doğru görünüyordu, ancak Github'a geri gitme, korkunç "RPC başarısız oldu; 55 curl SSL_write (), SYSCALL, errno = 32" hatası verdi. Onarmaya çalıştım, ama tamir edilemez bir şekilde kırıldı. Yeni bir yerel repoda işleri yeniden inşa etmek zorunda kaldım.
Mason Freed

@MasonFreed bu komut dosyası her iki deponun karışımı ile yeni bir git geçmişi oluşturur, bu nedenle eski repoya
itilemez

2

Her iki git geçmişini birleştirerek tek bir git geçmişine sahip bir repoyu başka bir repoya gömmek için adımları izleyin.

  1. Birleştirmek istediğiniz depoların ikisini de klonlayın.

git clone git@github.com: kullanıcı / parent-repo.git

git clone git@github.com: kullanıcı / child-repo.git

  1. Çocuk deposuna git

cd child-repo /

  1. Aşağıdaki komutu çalıştırın, yolu my/new/subdir(3 kez) alt repo olmasını istediğiniz dizin yapısıyla değiştirin .

git filter-branch - prune-empty --tree-filter 'eğer [! -e / new / subdir]; sonra mkdir -p benim / yeni / altdir git ls-tree - only name $ GIT_COMMIT | xargs -I dosyaları mv dosyaları my / new / subdir fi '

  1. Ana depoya git

cd ../parent-repo/

  1. Ana repoya uzaktan kumanda ekleyin, alt repoya giden yolu işaret edin

git remote çocuk-uzaktan ekle ../child-repo/

  1. Çocuk deposunu getir

git fetch çocuk kumandası

  1. Geçmişleri birleştir

git merge - allow-ilgisiz-geçmişler child-remote / master

Üst repodaki git günlüğünü şimdi işaretlerseniz, alt repo işlemlerinin birleştirilmesi gerekir. Ayrıca, kesinleştirme kaynağından gelen etiketi de görebilirsiniz.

Aşağıdaki makale, her iki git geçmişini birleştirerek tek bir git geçmişine sahip bir repoyu başka bir repoya yerleştirmeme yardımcı oldu.

http://ericlathrop.com/2014/01/combining-git-repositories/

Bu yardımcı olur umarım. Mutlu Kodlama!


3. sözdizimi hatası ile benim için başarısız oldu. Noktalı virgül eksik. Düzeltmegit filter-branch --prune-empty --tree-filter ' if [ ! -e my/new/subdir ]; then mkdir -p my/new/subdir; git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files my/new/subdir; fi'
Yuri L

1

Eğer depo birleştirmek istediğiniz Say aiçine b(Ben birbirlerine yanında yer konum varsayarak):

cd b
git remote add a ../a
git fetch a
git merge --allow-unrelated-histories a/master
git remote remove a

aBir alt dizine koymak istiyorsanız, yukarıdaki komutlardan önce aşağıdakileri yapın:

cd a
git filter-repo --to-subdirectory-filter a
cd ..

Eğer gerek Bunun için git-filter-repoyüklü ( filter-branchedilmektedir cesaretini ).

Birini bir alt dizine koyarak 2 büyük havuzu birleştirme örneği: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

Burada daha fazlası .

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.