İki Git deposunu nasıl birleştirirsiniz?


1620

Aşağıdaki senaryoyu düşünün:

Kendi Git deposunda küçük bir deney projesi A geliştirdim. Şimdi olgunlaştı ve A'nın kendi büyük deposu olan daha büyük B projesinin bir parçası olmasını istiyorum. Şimdi A'yı B'nin alt dizini olarak eklemek istiyorum.

A'yı hiçbir tarafta tarih kaybetmeden nasıl B ile birleştirebilirim?


8
İki veri havuzunu bir araya getirmeye çalışıyorsanız, her iki veri havuzunu da saklamaya gerek kalmadan bu soruya bir göz atın: stackoverflow.com/questions/13040958/…
Flimm

Git
repo'yu

Yanıtlar:


436

Başka bir deponun tek bir dalı, geçmişini koruyan bir alt dizinin altına kolayca yerleştirilebilir. Örneğin:

git subtree add --prefix=rails git://github.com/rails/rails.git master

Bu, Rails ana dalının tüm dosyalarının "raylar" dizinine eklendiği tek bir taahhüt olarak görünecektir. Ancak taahhüdün başlığı eski tarih ağacına referans içeriyor:

Taahhütten 'raylar /' ekle <rev>

<rev>SHA-1 taahhüt karması nerede . Geçmişi hala görebilir, bazı değişiklikleri suçlayabilirsiniz.

git log <rev>
git blame <rev> -- README.md

Dizin önekini buradan göremeyeceğinizi unutmayın, çünkü bu gerçek bir eski dal olduğu gibi bırakılmıştır. Bunu olağan bir dosya taşıma taahhüdü gibi ele almalısınız: ona ulaşırken fazladan bir sıçrama yapmanız gerekir.

# finishes with all files added at once commit
git log rails/README.md

# then continue from original tree
git log <rev> -- README.md

Bunu manuel olarak yapmak veya diğer cevaplarda açıklandığı gibi geçmişi yeniden yazmak gibi daha karmaşık çözümler vardır.

Git-subtree komutu resmi git-Contribute'un bir parçasıdır, bazı paket yöneticileri bunu varsayılan olarak yükler (OS X Homebrew). Ancak git'e ek olarak kendiniz yüklemeniz gerekebilir.


2
İşte (Haziran 2013'ten itibaren) Git alt ağacının yükleme talimatları uygulayın: stackoverflow.com/a/11613541/694469 (ve yerini git co v1.7.11.3 ile ... v1.8.3).
KajMagnus

1
Aşağıdaki cevap hakkında bilgi için teşekkürler. Git 1.8.4'ten itibaren 'alt ağaç' hala dahil değildir (en azından Ubuntu 12.04 git ppa'da (ppa: git-core / ppa))
Matt Klein

1
Bundan sonra git log rails/somefile, birleştirme taahhüdü dışında bu dosyanın taahhüt geçmişini görüntülemeyeceğini onaylayabilirim . @Artfulrobot'un önerdiği gibi Greg Hewgill'in cevabını kontrol edin . git filter-branchEklemek istediğiniz repo için de kullanmanız gerekebilir .
Jifeng Zhang

6
Veya Eric Lee'nin "Dosya Geçmişini Kaybetmeden İki Git Deposunu Bir Depoya Birleştirme
Jifeng Zhang

4
Diğerlerinin söylediği gibi, git subtreedüşündüğünüzü yapmayabilir! Daha eksiksiz bir çözüm için buraya bakın .
Paul Draper

1906

Birleştirmek istediğiniz takdirde project-aiçine project-b:

cd path/to/project-b
git remote add project-a path/to/project-a
git fetch project-a --tags
git merge --allow-unrelated-histories project-a/master # or whichever branch you want to merge
git remote remove project-a

Alındığı kaynak: git merge different repositories?

Bu yöntem benim için oldukça iyi çalıştı, daha kısa ve bence çok daha temiz.

Eğer koymak istiyorum project-abir alt dizinin kullanabileceğiniz içine git-filter-repo( filter-branchedilmektedir cesaretini ). Yukarıdaki komutlardan önce aşağıdaki komutları çalıştırın:

cd path/to/project-a
git filter-repo --to-subdirectory-filter project-a

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

Not:--allow-unrelated-histories parametre sadece Git> = 2.9 beri var. Bkz Git - git birleştirme Belgeleri / --allow-ilişkisiz-geçmişleri

Güncelleme : --tagsEtiketleri korumak için @jstadler tarafından önerildiği gibi eklendi .


8
Bu benim için işi yaptı. .Gitignore dosyasında sadece bir çatışma ile ilk kez bir cazibe gibi çalıştı! Taahhüt tarihini mükemmel bir şekilde korudu. Diğer yaklaşımlara kıyasla büyük artı - basitliğe ek olarak - bununla birleştirilmiş repoya sürekli bir referans gerekmemesidir. Ancak dikkat edilmesi gereken bir şey - benim gibi bir iOS geliştiricisiyseniz - hedef repo'nun proje dosyasına çalışma alanına düşmek için çok dikkatli olmaktır.
Max MacLeod

30
Teşekkürler. Benim için çalıştı. Birleştirilmiş dizini bir alt klasöre git mv source-dir/ dest/new-source-dir
Sid

13
git mergeAdım burada başarısız fatal: refusing to merge unrelated histories; --allow-unrelated-historiesbunu dokümanlarda açıklandığı gibi düzeltir .
ssc

19
--allow-unrelated-historiesGit 2.9'da tanıtıldı . Önceki sürümlerde varsayılan davranış buydu.
Douglas Royds

11
Daha kısa: git fetch /path/to/project-a master; git merge --allow-unrelated-histories FETCH_HEAD.
jthill

614

İşte iki olası çözüm:

Altmodüller

A havuzunu daha büyük B projesinde ayrı bir dizine veya (belki daha iyi) klon veri havuzunu A projesinde bir B alt dizinine kopyalayın. Daha sonra bu havuzu B havuzunun alt modülü yapmak için git alt modülünü kullanın .

Bu depo A'da gelişme devam gevşek çiftli depoları, için iyi bir çözümdür, ve gelişme büyük bölümü A. See da ayrı bir bağımsız bir gelişmedir SubmoduleSupport ve GitSubmoduleTutorial Git Wiki sayfalar.

Alt ağaç birleştirme

Alt ağaç birleştirme stratejisini kullanarak A havuzunu B projesinin bir alt dizininde birleştirebilirsiniz . Bu, Markus Prinz tarafından Alt Ağaç Birleştirme ve Siz'de açıklanmıştır .

git remote add -f Bproject /path/to/B
git merge -s ours --allow-unrelated-histories --no-commit Bproject/master
git read-tree --prefix=dir-B/ -u Bproject/master
git commit -m "Merge B project as our subdirectory"
git pull -s subtree Bproject master

( --allow-unrelated-historiesGit> = 2.9.0 için seçenek gereklidir.)

Veya git alt ağaç aracını ( GitHub'daki depo ) apenwarr (Avery Pennarun) tarafından kullanabilirsiniz, örneğin blog yazısında duyurdu Git alt modüllerine yeni bir alternatif: git subtree .


Sanırım sizin durumunuzda (A, daha büyük B projesinin bir parçası olmaktır) doğru çözüm, alt ağaç birleştirmeyi kullanmak olacaktır .


1
Bu çalışır ve geçmişi korur gibi görünüyor, ancak birleştirme yoluyla dosyaları ya da ikiye ayırmak için kullanabileceğiniz gibi değil. Bir adımı kaçırıyor muyum?
jettero

55
bu eksik . Evet, bir sürü taahhüt alırsınız, ancak artık doğru yollara başvurmuyorlar. git log dir-B/somefiletek birleştirme dışında hiçbir şey göstermez. Bkz. Greg Hewgill'in cevabı bu önemli soruna referansta bulunuyor .
artfulrobot

2
ÖNEMLİ: git pull --no-rebase -s subtree Bproject master Bunu yapmazsanız ve otomatik olarak yeniden taban oluşturmak için çekme ayarınız varsa, "Nesne ayrıştırılamadı" ile sonuçlanır. Bkz. Osdir.com/ml/git/2009-07/msg01576.html
Eric Bowman - abstracto -

4
Bu cevap kafa karıştırıcı olabilir, çünkü soru A olduğunda birleştirilen alt ağaç olarak B'ye sahiptir. Kopyalama ve yapıştırma sonucu?
vfclists

11
İki veri havuzunu birbirine yapıştırmaya çalışıyorsanız, alt modüller ve alt ağaç birleştirmeleri kullanmak için yanlış araçtır çünkü dosya geçmişinin tamamını korumazlar (diğer yorumcular tarafından belirtildiği gibi). Bkz. Stackoverflow.com/questions/13040958/… .
Eric Lee

194

Projeyi ayrı olarak sürdürmek istiyorsanız, alt modül yaklaşımı iyidir. Ancak, her iki projeyi de aynı depoda birleştirmek istiyorsanız, yapacak daha fazla işiniz var.

İlk şey git filter-branch, ikinci depodaki her şeyin adlarını, sonuçta bulunmasını istediğiniz alt dizinde olacak şekilde yeniden yazmak için kullanmak olacaktır. Yani yerine foo.c, bar.html, sen olurdu projb/foo.cve projb/bar.html.

Ardından, aşağıdakine benzer bir şey yapabilmeniz gerekir:

git remote add projb [wherever]
git pull projb

git pullBir yapacak git fetchbir takip git merge. Çektiğiniz havuzun henüz bir projb/dizini yoksa çakışma olmamalıdır .

Daha arama benzer bir şey birleştirme yapıldığını gösterir gitkiçine git. Junio ​​C Hamano bu konuda şöyle yazıyor: http://www.mail-archive.com/git@vger.kernel.org/msg03395.html


4
ağaçaltı birleştirme daha iyi bir çözüm olacaktır ve dahil edilen projenin yeniden yazılma tarihini gerektirmez
Jakub Narębski

8
Bunu git filter-branchbaşarmak için nasıl kullanılacağını bilmek istiyorum . Man sayfasında bunun tersi hakkında söyler: alt dizini yapmak / kök olmak, ancak tam tersi değil.
artfulrobot

31
İstenen sonuca ulaşmak için filtre kolunun nasıl kullanılacağını
açıklarsa

14
Filtre dalını nasıl kullanacağımı burada buldum: stackoverflow.com/questions/4042816/…
David Minor

3
Greg'in taslağının uygulanması için bu cevaba bakınız .
Paul Draper

75

git-subtree güzel, ama muhtemelen istediğiniz değil.

Örneğin, projectAB'de oluşturulan dizin ise git subtree,

git log projectA

yalnızca bir taahhüt listeler : birleştirme. Birleştirilen projenin taahhütleri farklı yollar içindir, bu yüzden görünmezler.

Greg Hewgill'in yanıtı, yolların nasıl yeniden yazılacağını söylemese de, en yakın olanıdır.


Çözüm şaşırtıcı derecede basittir.

(1) A'da,

PREFIX=projectA #adjust this

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

Not: Bu, geçmişi yeniden yazar, bu nedenle bu repo A'yı kullanmaya devam etmek istiyorsanız, önce bunun tek kullanımlık bir kopyasını klonlamak (kopyalamak) isteyebilirsiniz.

Not Bene: Dosya adlarında veya yolunda ascii olmayan karakterler (veya beyaz karakterler) kullanmanız durumunda, sed komutunun içindeki yedek komut dosyasını değiştirmeniz gerekir. Bu durumda, "ls-files -s" tarafından oluşturulan bir kaydın içindeki dosya konumu tırnak işareti ile başlar.

(2) Sonra B'de koş

git pull path/to/A

İşte bu kadar! projectAB'de bir dizininiz var. Çalıştırırsanız git log projectA, A'nın tüm taahhütlerini görürsünüz.


Benim durumumda, iki alt dizin istedim projectAve projectB. Bu durumda, (B) adımını da yaptım.


1
Yanıtınızı stackoverflow.com/a/618113/586086 adresinden kopyalamışsınız gibi görünüyor ?
Andrew Mao

1
@ AndrewMao, sanırım öyle ... Aslında hatırlayamıyorum. Bu senaryoyu biraz kullandım.
Paul Draper

6
OS X üzerinde çalışmadığını ve <tab>
Muneeb Ali

2
"$GIT_INDEX_FILE"(iki kez) tırnak içine alınmalıdır, aksi takdirde yol örneğin boşluk içeriyorsa yönteminiz başarısız olur.
Rob W

4
Eğer merak ediyorsanız, osx içine bir <tab> eklemek için yapmanız gerekenCtrl-V <tab>
casey

48

Her iki havuzda da aynı tür dosyalar varsa (farklı projeler için iki Rails deposu gibi), ikincil deponun verilerini geçerli deponuza getirebilirsiniz:

git fetch git://repository.url/repo.git master:branch_name

ve ardından mevcut depoyla birleştirin:

git merge --allow-unrelated-histories branch_name

Git sürümünüz 2.9'dan küçükse kaldırın --allow-unrelated-histories.

Bundan sonra çatışmalar meydana gelebilir. Bunları örneğin ile çözebilirsiniz git mergetool. kdiff3sadece klavye ile kullanılabilir, bu yüzden kodu sadece birkaç dakika okurken 5 çakışma dosyası alır.

Birleştirmeyi bitirmeyi unutmayın:

git commit

25

Birleştirme kullanırken tarih kaybetmeye devam ettim, bu yüzden rebase'i kullandım çünkü benim durumumda iki depo her taahhütte birleşmeyecek kadar farklı:

git clone git@gitorious/projA.git projA
git clone git@gitorious/projB.git projB

cd projB
git remote add projA ../projA/
git fetch projA 
git rebase projA/master HEAD

=> çakışmaları giderin, ardından gerektiği kadar devam edin ...

git rebase --continue

Bunu yapmak, projA'nın tüm taahhütlerini ve ardından projB'nin taahhütlerini içeren bir projeye yol açar


25

Benim durumumda, bir my-plugindepom ve bir main-projectdepom my-pluginvardı ve her zaman için pluginsalt dizininde geliştirilmiş gibi davranmak istedim main-project.

Temel olarak, my-plugindeponun tarihini yeniden yazdım, böylece tüm gelişme plugins/my-pluginalt dizinde gerçekleşti. Sonra, geliştirilmesi öyküsü eklendi my-pluginiçine main-projecttarih ve birlikte iki ağaç birleşti. plugins/my-pluginHavuzda zaten bir dizin bulunmadığından main-project, bu önemsiz bir çatışmasız birleşmeydi. Ortaya çıkan depo, her iki orijinal projeden tüm geçmişi içeriyordu ve iki kökü vardı.

TL; DR

$ cp -R my-plugin my-plugin-dirty
$ cd my-plugin-dirty
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
$ cd ../main-project
$ git checkout master
$ git remote add --fetch my-plugin ../my-plugin-dirty
$ git merge my-plugin/master --allow-unrelated-histories
$ cd ..
$ rm -rf my-plugin-dirty

Uzun versiyon

İlk olarak, my-plugindeponun bir kopyasını oluşturun , çünkü bu deponun tarihini yeniden yazacağız.

Şimdi, my-plugindeponun kök dizinine gidin , ana dalınızı kontrol edin (muhtemelen master) ve aşağıdaki komutu çalıştırın. Tabii ki, gerçek isimlerinizin yerine my-pluginve pluginsne olursa olsun.

$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all

Şimdi bir açıklama için. komutu, ulaşılabilir her komutta git filter-branch --tree-filter (...) HEADçalıştırır . Bunun doğrudan her bir taahhüt için depolanan veriler üzerinde çalıştığına dikkat edin, bu nedenle "çalışma dizini", "dizin", "aşamalandırma" ve benzeri kavramlar hakkında endişelenmemize gerek yoktur.(...)HEAD

filter-branchBaşarısız olan bir komut çalıştırırsanız , .gitdizindeki bazı dosyaların arkasında kalır ve bir daha denediğinizde filter-branch, -fseçeneği sağlamadığınız sürece bu durumdan şikayet eder filter-branch.

Gerçek komutu gelince, ben alıyorum çok şans yoktu bashne istediğini yapmak için, bunun yerine kullandığım zsh -cyapmak için zshbir komut çalıştırmak. İlk önce komutta sözdizimini extended_globsağlayan seçeneği ve bir glob ( ) ile nokta dosyalarını (örneğin ) seçmeme izin veren seçeneği ayarladım .^(...)mvglob_dots.gitignore^(...)

Sonra, kullandığım mkdir -pher iki oluşturmak için komut pluginsve plugins/my-pluginaynı anda.

Son olarak, deponun kök dizinindeki ve yeni oluşturulan klasör dışındaki tüm dosyaları eşleştirmek için zsh"negatif glob" özelliğini ^(.git|plugins)kullanıyorum . (Burada hariç tutmak gerekli olmayabilir, ancak bir dizini kendi içine taşımaya çalışmak bir hatadır.).gitmy-plugin.git

Depomda, ilk işlem herhangi bir dosya içermediğinden, ilk işlemde mvkomut bir hata döndürdü (taşınacak hiçbir şey olmadığından). Bu nedenle, bir katma || trueböylece git filter-branchdeğil iptal olur.

--allSeçenek söyler filter-branchiçin tarihi yeniden yazmaya tüm depo şubeleri ve ekstra --söylemek gerekir gityerine bir seçenek olarak, yeniden yazma için dalları için seçenek listesinin bir parçası olarak yorumlamak filter-branchkendisi.

Şimdi main-projectdeponuza gidin ve birleştirmek istediğiniz dalı kontrol edin. Deponun yerel kopyasını my-plugin(geçmişi değiştirilmiş olarak) aşağıdakilerle bir uzaktan kumanda olarak ekleyin main-project:

$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY

Artık işlem geçmişinizde, kullanarak güzel bir şekilde görselleştirebileceğiniz iki alakasız ağacınız olacak:

$ git log --color --graph --decorate --all

Bunları birleştirmek için şunu kullanın:

$ git merge my-plugin/master --allow-unrelated-histories

2.9.0 Git'ten önce --allow-unrelated-historiesseçeneğin mevcut olmadığını unutmayın. Bu sürümlerden birini kullanıyorsanız, seçeneği atlamanız yeterlidir: --allow-unrelated-historiesönleyen hata mesajı 2.9.0'da da eklenmiştir.

Herhangi bir birleştirme çatışması olmamalıdır. Bunu yaparsanız, muhtemelen filter-branchkomutun düzgün çalışmadığı veya zaten bir plugins/my-plugindizin olduğu anlamına gelir main-project.

Gelecekteki katkıda bulunanlar için, iki köklü bir depo yapmak için neler olduğunu merak eden açıklayıcı bir taahhüt mesajı girdiğinizden emin olun.

Yukarıdaki git logkomutu kullanarak, iki kök işleme sahip olması gereken yeni tamamlama grafiğini görselleştirebilirsiniz . O Not sadece masterdal birleştirilecektir . Bu my-plugin, main-projectağaca birleştirmek istediğiniz diğer dallar üzerinde önemli çalışmalarınız my-pluginvarsa, bu birleştirmeleri yapana kadar uzaktan kumandayı silmekten kaçınmanız gerektiği anlamına gelir . Bunu yapmazsanız, bu şubelerden gelen taahhütler yine de main-projectdepoda olacaktır, ancak bazılarına ulaşılamayacak ve nihai çöp toplamaya duyarlı olacaktır. (Ayrıca, uzaktan kumandanın silinmesi uzaktan izleme dallarını kaldırdığından onlara SHA tarafından başvurmanız gerekecektir.)

İsteğe bağlı olarak, saklamak istediğiniz her şeyi birleştirdikten sonra uzaktan kumandayı aşağıdakileri kullanarak my-pluginkaldırabilirsiniz my-plugin:

$ git remote remove my-plugin

Artık my-plugingeçmişini değiştirdiğiniz deponun kopyasını güvenle silebilirsiniz . Benim durumumda, my-pluginbirleştirme işlemi tamamlandıktan ve itildikten sonra gerçek depoya bir kullanımdan kaldırma bildirimi ekledim .


Mac OS X El Capitan üzerinde git --version 2.9.0ve ile test edilmiştir zsh --version 5.2. Kilometreniz değişebilir.

Referanslar:


1
Nereden --allow-unrelated-historiesgeliyor?
xpto

3
@MarceloFilho Check man git-merge. Git merge komutu varsayılan olarak ortak bir ata paylaşmayan geçmişleri birleştirmeyi reddeder. Bu seçenek, hayatlarını bağımsız olarak başlatan iki projenin geçmişlerini birleştirirken bu güvenliği geçersiz kılmak için kullanılabilir. Bu çok nadir bir durum olduğundan, bunu varsayılan olarak etkinleştirecek hiçbir yapılandırma değişkeni yoktur ve eklenmez.
Radon Rosborough

Kullanılabilir olmalı mı git version 2.7.2.windows.1?
xpto

2
@MarceloFilho Bu, 2.9.0'da eklendi, ancak eski sürümlerde seçeneği geçmek zorunda kalmamalısınız (sadece çalışacak). Github.com/git/git/blob/…
Radon Rosborough

Bu iyi çalıştı. Birleştirme işleminden önce ağaç dalını ağaçta istediğim yere yeniden yazmak için filtre dalını kullanabildim. Geçmişi ana dalın yanına taşımanız gerekiyorsa daha fazla iş olduğunu düşünüyorum.
KodDr

9

Aynı şeyi günlerdir yapmaya çalışıyorum, git 2.7.2 kullanıyorum. Alt ağaç tarihi korumaz.

Eski projeyi bir daha kullanmayacaksanız bu yöntemi kullanabilirsiniz.

Önce B dalını ve dalda çalışmanızı öneririm.

Dallanmayan adımlar şunlardır:

cd B

# You are going to merge A into B, so first move all of B's files into a sub dir
mkdir B

# Move all files to B, till there is nothing in the dir but .git and B
git mv <files> B

git add .

git commit -m "Moving content of project B in preparation for merge from A"


# Now merge A into B
git remote add -f A <A repo url>

git merge A/<branch>

mkdir A

# move all the files into subdir A, excluding .git
git mv <files> A

git commit -m "Moved A into subdir"


# Move B's files back to root    
git mv B/* ./

rm -rf B

git commit -m "Reset B to original state"

git push

Şimdi A alt dizinindeki dosyalardan herhangi birini kaydederseniz, geçmişin tamamını alacaksınız

git log --follow A/<file>

Bunu yapmama yardımcı olan yazı buydu:

http://saintgimp.org/2013/01/22/merging-two-git-repositories-into-one-repository-without-losing-file-history/


8

Bir repo A'daki bir daldaki dosyaları A repo alt ağacına koymak ve aynı zamanda geçmişi korumak istiyorsanız , okumaya devam edin. (Aşağıdaki örnekte, repo B'nin ana şubesinin repo A'nın ana şubesine birleştirilmesini istediğimizi varsayıyorum.)

Repo A'da, repo B'yi kullanılabilir hale getirmek için önce aşağıdakileri yapın:

git remote add B ../B # Add repo B as a new remote.
git fetch B

Şimdi A dediğimiz repo'da yepyeni bir şube (sadece bir taahhütle) oluşturuyoruz new_b_root. Ortaya çıkan taahhüt, repo B'nin ana dalının ilk taahhüdünde işlenen, ancak adlı bir alt dizine yerleştirilen dosyalara sahip olacaktır path/to/b-files/.

git checkout --orphan new_b_root master
git rm -rf . # Remove all files.
git cherry-pick -n `git rev-list --max-parents=0 B/master`
mkdir -p path/to/b-files
git mv README path/to/b-files/
git commit --date="$(git log --format='%ai' $(git rev-list --max-parents=0 B/master))"

Açıklama: --orphanÖdeme komutuna ilişkin seçenek, A'nın ana dalından dosyaları denetler, ancak herhangi bir taahhüt oluşturmaz. Herhangi bir taahhüdü seçebilirdik, çünkü daha sonra tüm dosyaları yine de temizliyoruz. Daha sonra, henüz taahhütte bulunmadan ( -n), B'nin ana dalından ilk taahhüdü seçeriz. (Kiraz toplama, düz bir kasanın yapamadığı orijinal taahhüt mesajını korur.) Ardından, tüm dosyaları repo B'den koymak istediğimiz alt ağacı yaratırız. kiraz ağacına alınız. Yukarıdaki örnekte, yalnızca READMEtaşınacak bir dosya vardır . Sonra B-repo kök taahhüdümüzü taahhüt ediyoruz ve aynı zamanda orijinal taahhüdün zaman damgasını da koruyoruz.

Şimdi, B/masteryeni oluşturulanların üzerine yeni bir dal oluşturacağız new_b_root. Yeni şube diyoruz b:

git checkout -b b B/master
git rebase -s recursive -Xsubtree=path/to/b-files/ new_b_root

Şimdi bşubemizi birleştiriyoruz A/master:

git checkout master
git merge --allow-unrelated-histories --no-commit b
git commit -m 'Merge repo B into repo A.'

Son olarak, Buzak ve geçici dalları kaldırabilirsiniz :

git remote remove B
git branch -D new_b_root b

Son grafik şöyle bir yapıya sahip olacaktır:

resim açıklamasını buraya girin


Harika cevap, teşekkürler! Andresch Serj'den "git subtree" veya "merge --allow-unlate-histories" ile diğer cevaplarda alt dizinin günlüğe sahip olmadığını gerçekten kaçırdım.
Ilendir

8

Stack OverFlow, vb. Hakkında birçok bilgi topladım ve sorunu benim için çözen bir senaryoyu bir araya getirmeyi başardım.

Uyarı, her deponun sadece 'geliştirme' dalını dikkate alması ve onu tamamen yeni bir havuzda ayrı bir dizinde birleştirmesidir.

Etiketler ve diğer şubeler yoksayılır - bu istediğiniz şey olmayabilir.

Komut dosyası, özellik dallarını ve etiketleri bile işler - yeni projede yeniden adlandırılır, böylece nereden geldiklerini bilirsiniz.

#!/bin/bash
#
################################################################################
## Script to merge multiple git repositories into a new repository
## - The new repository will contain a folder for every merged repository
## - The script adds remotes for every project and then merges in every branch
##   and tag. These are renamed to have the origin project name as a prefix
##
## Usage: mergeGitRepositories.sh <new_project> <my_repo_urls.lst>
## - where <new_project> is the name of the new project to create
## - and <my_repo_urls.lst> is a file contaning the URLs to the respositories
##   which are to be merged on separate lines.
##
## Author: Robert von Burg
##            eitch@eitchnet.ch
##
## Version: 0.3.2
## Created: 2018-02-05
##
################################################################################
#

# disallow using undefined variables
shopt -s -o nounset

# Script variables
declare SCRIPT_NAME="${0##*/}"
declare SCRIPT_DIR="$(cd ${0%/*} ; pwd)"
declare ROOT_DIR="$PWD"
IFS=$'\n'

# Detect proper usage
if [ "$#" -ne "2" ] ; then
  echo -e "ERROR: Usage: $0 <new_project> <my_repo_urls.lst>"
  exit 1
fi


## Script variables
PROJECT_NAME="${1}"
PROJECT_PATH="${ROOT_DIR}/${PROJECT_NAME}"
TIMESTAMP="$(date +%s)"
LOG_FILE="${ROOT_DIR}/${PROJECT_NAME}_merge.${TIMESTAMP}.log"
REPO_FILE="${2}"
REPO_URL_FILE="${ROOT_DIR}/${REPO_FILE}"


# Script functions
function failed() {
  echo -e "ERROR: Merging of projects failed:"
  echo -e "ERROR: Merging of projects failed:" >>${LOG_FILE} 2>&1
  echo -e "$1"
  exit 1
}

function commit_merge() {
  current_branch="$(git symbolic-ref HEAD 2>/dev/null)"
  if [[ ! -f ".git/MERGE_HEAD" ]] ; then
    echo -e "INFO:   No commit required."
    echo -e "INFO:   No commit required." >>${LOG_FILE} 2>&1
  else
    echo -e "INFO:   Committing ${sub_project}..."
    echo -e "INFO:   Committing ${sub_project}..." >>${LOG_FILE} 2>&1
    if ! git commit -m "[Project] Merged branch '$1' of ${sub_project}" >>${LOG_FILE} 2>&1 ; then
      failed "Failed to commit merge of branch '$1' of ${sub_project} into ${current_branch}"
    fi
  fi
}


# Make sure the REPO_URL_FILE exists
if [ ! -e "${REPO_URL_FILE}" ] ; then
  echo -e "ERROR: Repo file ${REPO_URL_FILE} does not exist!"
  exit 1
fi


# Make sure the required directories don't exist
if [ -e "${PROJECT_PATH}" ] ; then
  echo -e "ERROR: Project ${PROJECT_NAME} already exists!"
  exit 1
fi


# create the new project
echo -e "INFO: Logging to ${LOG_FILE}"
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..."
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
cd ${ROOT_DIR}
mkdir ${PROJECT_NAME}
cd ${PROJECT_NAME}
git init
echo "Initial Commit" > initial_commit
# Since this is a new repository we need to have at least one commit
# thus were we create temporary file, but we delete it again.
# Deleting it guarantees we don't have conflicts later when merging
git add initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
git rm --quiet initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
echo


# Merge all projects into the branches of this project
echo -e "INFO: Merging projects into new repository..."
echo -e "INFO: Merging projects into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do

  if [[ "${url:0:1}" == '#' ]] ; then
    continue
  fi

  # extract the name of this project
  export sub_project=${url##*/}
  sub_project=${sub_project%*.git}

  echo -e "INFO: Project ${sub_project}"
  echo -e "INFO: Project ${sub_project}" >>${LOG_FILE} 2>&1
  echo -e "----------------------------------------------------"
  echo -e "----------------------------------------------------" >>${LOG_FILE} 2>&1

  # Fetch the project
  echo -e "INFO:   Fetching ${sub_project}..."
  echo -e "INFO:   Fetching ${sub_project}..." >>${LOG_FILE} 2>&1
  git remote add "${sub_project}" "${url}"
  if ! git fetch --tags --quiet ${sub_project} >>${LOG_FILE} 2>&1 ; then
    failed "Failed to fetch project ${sub_project}"
  fi

  # add remote branches
  echo -e "INFO:   Creating local branches for ${sub_project}..."
  echo -e "INFO:   Creating local branches for ${sub_project}..." >>${LOG_FILE} 2>&1
  while read branch ; do
    branch_ref=$(echo $branch | tr " " "\t" | cut -f 1)
    branch_name=$(echo $branch | tr " " "\t" | cut -f 2 | cut -d / -f 3-)

    echo -e "INFO:   Creating branch ${branch_name}..."
    echo -e "INFO:   Creating branch ${branch_name}..." >>${LOG_FILE} 2>&1

    # create and checkout new merge branch off of master
    if ! git checkout -b "${sub_project}/${branch_name}" master >>${LOG_FILE} 2>&1 ; then failed "Failed preparing ${branch_name}" ; fi
    if ! git reset --hard ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi
    if ! git clean -d --force ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi

    # Merge the project
    echo -e "INFO:   Merging ${sub_project}..."
    echo -e "INFO:   Merging ${sub_project}..." >>${LOG_FILE} 2>&1
    if ! git merge --allow-unrelated-histories --no-commit "remotes/${sub_project}/${branch_name}" >>${LOG_FILE} 2>&1 ; then
      failed "Failed to merge branch 'remotes/${sub_project}/${branch_name}' from ${sub_project}"
    fi

    # And now see if we need to commit (maybe there was a merge)
    commit_merge "${sub_project}/${branch_name}"

    # relocate projects files into own directory
    if [ "$(ls)" == "${sub_project}" ] ; then
      echo -e "WARN:   Not moving files in branch ${branch_name} of ${sub_project} as already only one root level."
      echo -e "WARN:   Not moving files in branch ${branch_name} of ${sub_project} as already only one root level." >>${LOG_FILE} 2>&1
    else
      echo -e "INFO:   Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..."
      echo -e "INFO:   Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..." >>${LOG_FILE} 2>&1
      mkdir ${sub_project}
      for f in $(ls -a) ; do
        if  [[ "$f" == "${sub_project}" ]] ||
            [[ "$f" == "." ]] ||
            [[ "$f" == ".." ]] ; then
          continue
        fi
        git mv -k "$f" "${sub_project}/"
      done

      # commit the moving
      if ! git commit --quiet -m  "[Project] Move ${sub_project} files into sub directory" ; then
        failed "Failed to commit moving of ${sub_project} files into sub directory"
      fi
    fi
    echo
  done < <(git ls-remote --heads ${sub_project})


  # checkout master of sub probject
  if ! git checkout "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
    failed "sub_project ${sub_project} is missing master branch!"
  fi

  # copy remote tags
  echo -e "INFO:   Copying tags for ${sub_project}..."
  echo -e "INFO:   Copying tags for ${sub_project}..." >>${LOG_FILE} 2>&1
  while read tag ; do
    tag_ref=$(echo $tag | tr " " "\t" | cut -f 1)
    tag_name_unfixed=$(echo $tag | tr " " "\t" | cut -f 2 | cut -d / -f 3)

    # hack for broken tag names where they are like 1.2.0^{} instead of just 1.2.0
    tag_name="${tag_name_unfixed%%^*}"

    tag_new_name="${sub_project}/${tag_name}"
    echo -e "INFO:     Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..."
    echo -e "INFO:     Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..." >>${LOG_FILE} 2>&1
    if ! git tag "${tag_new_name}" "${tag_ref}" >>${LOG_FILE} 2>&1 ; then
      echo -e "WARN:     Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}"
      echo -e "WARN:     Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}" >>${LOG_FILE} 2>&1
    fi
  done < <(git ls-remote --tags --refs ${sub_project})

  # Remove the remote to the old project
  echo -e "INFO:   Removing remote ${sub_project}..."
  echo -e "INFO:   Removing remote ${sub_project}..." >>${LOG_FILE} 2>&1
  git remote rm ${sub_project}

  echo
done


# Now merge all project master branches into new master
git checkout --quiet master
echo -e "INFO: Merging projects master branches into new repository..."
echo -e "INFO: Merging projects master branches into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do

  if [[ ${url:0:1} == '#' ]] ; then
    continue
  fi

  # extract the name of this project
  export sub_project=${url##*/}
  sub_project=${sub_project%*.git}

  echo -e "INFO:   Merging ${sub_project}..."
  echo -e "INFO:   Merging ${sub_project}..." >>${LOG_FILE} 2>&1
  if ! git merge --allow-unrelated-histories --no-commit "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
    failed "Failed to merge branch ${sub_project}/master into master"
  fi

  # And now see if we need to commit (maybe there was a merge)
  commit_merge "${sub_project}/master"

  echo
done


# Done
cd ${ROOT_DIR}
echo -e "INFO: Done."
echo -e "INFO: Done." >>${LOG_FILE} 2>&1
echo

exit 0

Ayrıca http://paste.ubuntu.com/11732805 adresinden de alabilirsiniz.

Önce her bir deponun URL'sine sahip bir dosya oluşturun, örneğin:

git@github.com:eitchnet/ch.eitchnet.parent.git
git@github.com:eitchnet/ch.eitchnet.utils.git
git@github.com:eitchnet/ch.eitchnet.privilege.git

Ardından projenin adını ve komut dosyasının yolunu veren komut dosyasını çağırın:

./mergeGitRepositories.sh eitchnet_test eitchnet.lst

Komut dosyasının kendisi, ne yaptığını açıklaması gereken birçok yorum içeriyor.


Okuyucuları bir yanıta yönlendirmek yerine, lütfen yanıtı buraya gönderin (diğer bir deyişle bu yorumda söylediklerinizi bu yanıta düzenleyin).
josliber

1
Tabii, sadece kendimi tekrarlamamanın daha iyi olduğunu düşündüm ... =)
eitch

Bu sorunun diğeriyle aynı olduğunu düşünüyorsanız, sorunun altındaki "işaret" bağlantısını kullanarak ve diğer soruyu belirterek soruyu yinelenen olarak işaretleyebilirsiniz. Bu yinelenen bir soru değilse de, aynı cevabın her iki sorunu da çözmek için kullanılabileceğini düşünüyorsanız, aynı cevabı her iki konuya da gönderin (şimdi yaptığınız gibi). Katkınız için teşekkürler!
josliber

İnanılmaz! Windows bash isteminde çalışmadı, ancak ubuntu çalıştıran bir Vagrant kutusu kusursuz bir şekilde koştu. Ne zaman tasarrufu!
17'de

Mutlu olmak hizmet =)
eitch

7

Aslında bundan çok sonra olduğunu biliyorum, ama burada bulduğum diğer cevaplardan memnun değildim, bu yüzden şunu yazdım:

me=$(basename $0)

TMP=$(mktemp -d /tmp/$me.XXXXXXXX)
echo 
echo "building new repo in $TMP"
echo
sleep 1

set -e

cd $TMP
mkdir new-repo
cd new-repo
    git init
    cd ..

x=0
while [ -n "$1" ]; do
    repo="$1"; shift
    git clone "$repo"
    dirname=$(basename $repo | sed -e 's/\s/-/g')
    if [[ $dirname =~ ^git:.*\.git$ ]]; then
        dirname=$(echo $dirname | sed s/.git$//)
    fi

    cd $dirname
        git remote rm origin
        git filter-branch --tree-filter \
            "(mkdir -p $dirname; find . -maxdepth 1 ! -name . ! -name .git ! -name $dirname -exec mv {} $dirname/ \;)"
        cd ..

    cd new-repo
        git pull --no-commit ../$dirname
        [ $x -gt 0 ] && git commit -m "merge made by $me"
        cd ..

    x=$(( x + 1 ))
done

2
Tam da aradığım şey buydu. Teşekkürler! Ancak, 22 numaralı satırı değiştirmek zorunda kaldım:if [[ $dirname =~ ^.*\.git$ ]]; then
heyman

2
^. * blarg $ israfa açgözlü RE. .Blarg $ demek ve ön bağlantıyı atlamak daha iyidir.
jettero

7

İki veri havuzunu birbirine yapıştırmaya çalışıyorsanız, alt modüller ve alt ağaç birleştirmeleri kullanmak için yanlış araçtır, çünkü dosya geçmişinin tamamını korumazlar (diğer yanıtlarda da belirtildiği gibi). Bu yanıta bakın burada basit ve bunu yapmak için doğru yolu.


1
Çözümünüz yalnızca yeni depo için iyi çalışıyor, ancak başka bir dosyadaki repoyu dosya çakışmalarıyla birleştirmeye ne dersiniz?
Andrey Izman

6

Benzer bir zorluk yaşadım, ancak benim durumumda, repo A'da kod tabanının bir sürümünü geliştirdik, daha sonra ürünün yeni sürümü için yeni bir repo, repo B'ye klonladık. Repo A'daki bazı hataları düzelttikten sonra, değişiklikleri repo B'ye çevirmemiz gerekiyordu.

  1. B deposuna repo A'yı işaret eden bir uzaktan kumanda ekleme (git remote add ...)
  2. Geçerli dalı çekme (hata düzeltmeleri için master kullanmıyorduk) (git pull remoteForRepoA bugFixBranch)
  3. Birleşmeleri github'a itmek

Bir tedavi çalıştı :)


5

@Smar'a benzer ancak PRIMARY ve SECONDARY olarak ayarlanmış dosya sistemi yollarını kullanır:

PRIMARY=~/Code/project1
SECONDARY=~/Code/project2
cd $PRIMARY
git remote add test $SECONDARY && git fetch test
git merge test/master

Sonra elle birleştirirsiniz.

( Anar Manafov'un gönderisinden uyarlanmıştır )


5

2 depo birleştirme

git clone ssh://<project-repo> project1
cd project1
git remote add -f project2 project2
git merge --allow-unrelated-histories project2/master
git remote rm project2

delete the ref to avoid errors
git update-ref -d refs/remotes/project2/master

4

Üç veya daha fazla projeyi tek bir taahhütte birleştirmek istediğinizde , diğer cevaplarda ( remote add -f, merge) açıklanan adımları uygulayın . Ardından, (yumuşak) dizini eski kafaya sıfırlar (birleştirme işleminin olmadığı yerde). Tüm dosyaları ( git add -A) ekleyin ve uygulayın ("A, B, C ve D projelerini tek bir projede birleştirme) mesajı.

Şimdi, .git/info/graftsaşağıdaki içerikle oluşturun:

<commit-id of master> <list of commit ids of all parents>

Koş git filter-branch -- head^..head head^2..head head^3..head. Üçten fazla şubeniz varsa, sadece şubeleriniz kadar ekleyin head^n..head. Etiketleri güncellemek için ekleyin --tag-name-filter cat. Bunu her zaman eklemeyin, çünkü bu bazı taahhütlerin yeniden yazılmasına neden olabilir. Detaylar için filtre dalının kılavuz sayfasına bakınız , "greftler" için arama yapınız.

Şimdi, son taahhüdünüzde doğru ebeveynler var.


1
Bekle, neden üç projeyi tek bir taahhütte birleştirmek istesin?
Steve Bennett

Depo, depo-istemci ve modeler ile ayrı git projeleri olarak başladım. Bu iş arkadaşları için zordu, bu yüzden onlara tek bir git projesinde katıldım. Yeni projenin "kökünün" diğer üç projeden kaynaklanabilmesi için tek bir birleştirme taahhüdü almak istedim .
koppor

4

A'yı B içinde birleştirmek için:

1) A projesinde

git fast-export --all --date-order > /tmp/ProjectAExport

2) B projesinde

git checkout -b projectA
git fast-import --force < /tmp/ProjectAExport

Bu branşta yapmanız ve yapmanız gereken tüm işlemleri yapın.

C) Sonra ustaya geri dönün ve iki dal arasında klasik bir birleşme:

git checkout master
git merge projectA

2

Bu işlev uzaktan repoyu yerel repo dizinine klonlar, birleştirmeden sonra tüm taahhütler kaydedilir, git logorijinal taahhütleri ve uygun yolları gösterecektir:

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

Küçük değişiklikler yaparsanız, birleştirilmiş repo dosyalarını / dizinlerini farklı yollara taşıyabilirsiniz, örneğin:

repo="https://github.com/example/example"
path="$(pwd)"

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

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

GIT_ADD_STORED=""

function git-mv-store
{
    from="$(echo "$1" | sed 's/\./\\./')"
    to="$(echo "$2" | sed 's/\./\\./')"

    GIT_ADD_STORED+='s,\t'"$from"',\t'"$to"',;'
}

# NOTICE! This paths used for example! Use yours instead!
git-mv-store 'public/index.php' 'public/admin.php'
git-mv-store 'public/data' 'public/x/_data'
git-mv-store 'public/.htaccess' '.htaccess'
git-mv-store 'core/config' 'config/config'
git-mv-store 'core/defines.php' 'defines/defines.php'
git-mv-store 'README.md' 'doc/README.md'
git-mv-store '.gitignore' 'unneeded/.gitignore'

git filter-branch --index-filter '
    git ls-files -s |
    sed "'"$GIT_ADD_STORED"'" |
    GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
    mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
' HEAD

GIT_ADD_STORED=""

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"

Uyarılar
Yolların yerine geçer sed, bu nedenle birleştirildikten sonra doğru yollarda hareket ettiğinden emin olun. Parametre sadece Git> = 2.9 beri var.
--allow-unrelated-histories


1

Verilen komut, önerebileceğim en iyi çözümdür.

git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master

1

Projeleri hafifçe manuel olarak birleştiriyorum, bu da birleştirme çatışmalarıyla uğraşmaktan kaçınmamı sağlıyor.

önce, diğer projedeki dosyaları istediğiniz gibi kopyalayın.

cp -R myotherproject newdirectory
git add newdirectory

tarihin bir sonraki çekimi

git fetch path_or_url_to_other_repo

git'e son getirilen şeyin tarihinde birleşmesini söyle

echo 'FETCH_HEAD' > .git/MERGE_HEAD

şimdi taahhüt et, ancak normalde taahhüt edersin

git commit

0

Küçük bir projeyi daha büyük bir alt dizine taşımak istedim. Küçük projemin fazla işi olmadığı için kullandım git format-patch --output-directory /path/to/patch-dir. Sonra daha büyük projede kullandım git am --directory=dir/in/project /path/to/patch-dir/*.

Bu hissediyor yolu bir filtre-şube daha az korkutucu ve yol daha süpürge. Kabul edildiğinde, tüm durumlar için geçerli olmayabilir.

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.