Birçok alt dizini yeni, ayrı bir Git deposuna ayırın


135

Bu soru alt dizini ayrı Git deposuna ayır

Tek bir alt dizini ayırmak yerine, bir çift ayırmak istiyorum. Örneğin, geçerli dizin ağacım şöyle görünür:

/apps
  /AAA
  /BBB
  /CCC
/libs
  /XXX
  /YYY
  /ZZZ

Bunun yerine bunu istiyorum:

/apps
  /AAA
/libs
  /XXX

Çalışmayacak --subdirectory-filterargümanı git filter-branch, ilk çalıştırıldığında verilen dizin hariç her şeyden kurtulduğu için işe yaramaz. --index-filterTüm istenmeyen dosyalar için argümanı kullanmanın işe yaradığını düşündüm (sıkıcı da olsa), ancak bir kereden fazla çalıştırmayı denersem, aşağıdaki mesajı alıyorum:

Cannot create a new backup.
A previous backup already exists in refs/original/
Force overwriting the backup with -f

Herhangi bir fikir? TIA

Yanıtlar:


155

Bir alt kabukla uğraşmak ve ext glob kullanmak (kynan'ın önerdiği gibi) yerine, bu çok daha basit yaklaşımı deneyin:

git filter-branch --index-filter 'git rm --cached -qr --ignore-unmatch -- . && git reset -q $GIT_COMMIT -- apps/AAA libs/XXX' --prune-empty -- --all

Tarafından belirtildiği gibi void.pointer onun / onu içinde açıklama , bu hariç her şeyi kaldıracak apps/AAAve libs/XXXşimdiki deposundan.

Boş birleştirme taahhütlerini budama

Bu, birçok boş birleşmenin ardında kalıyor. Tarafından tarif edildiği gibi Bunlar başka pass kaldırılabilir raphinesse onun içinde cevap :

git filter-branch --prune-empty --parent-filter \
'sed "s/-p //g" | xargs -r git show-branch --independent | sed "s/\</-p /g"'

⚠️ Uyarı : Yukarıdaki zorunluluk kullanımı GNU sürümü sedve xargsaksi hepsi kaydedilmesini kaldıracak xargsbaşarısız olur. brew install gnu-sed findutilsve daha sonra kullanmak gsedve gxargs:

git filter-branch --prune-empty --parent-filter \
'gsed "s/-p //g" | gxargs git show-branch --independent | gsed "s/\</-p /g"' 

4
ek olarak, --ignore-unmatch bayrağı git rm'ye geçirilmelidir, aksi takdirde benim için ilk taahhütte başarısız oldu (depo benim durumumda git svn klonu ile oluşturuldu)
Pontomedon

8
Karışımda etiketler olduğunu varsayarsak, muhtemelen --tag-name-filter catparametrelerinize eklemelisiniz
Yonatan

16
Bu uzun komutun ne yaptığını açıklayan daha fazla bilgi ekleyebilir misiniz?
Burhan Ali

4
Bunun git bash, vay be kullanarak Windows üzerinde mükemmel çalışması hoş bir sürpriz!
Dai

3
@BurhanAli Tarihin her taahhüdü için, saklamak istedikleriniz dışındaki tüm dosyaları siler. Her şey bittiğinde, yalnızca o tarihle birlikte ağacın yalnızca belirttiğiniz kısmı ile kalırsınız.
void.pointer

39

Basit git komutlarıyla manuel adımlar

Plan, tek tek dizinleri kendi depolarına ayırmak ve sonra birleştirmektir. Aşağıdaki manuel adımlar, geek-to-scriptleri değil, anlaşılması kolay komutları kullanmıştır ve ekstra N alt klasörlerinin başka bir tek depoda birleştirilmesine yardımcı olabilir.

bölmek

Orijinal deponuzun: orijinal_popo olduğunu varsayalım.

1 - Bölünmüş uygulamalar:

git clone original_repo apps-repo
cd apps-repo
git filter-branch --prune-empty --subdirectory-filter apps master

2 - Split libs

git clone original_repo libs-repo
cd libs-repo
git filter-branch --prune-empty --subdirectory-filter libs master

2'den fazla klasörünüz varsa devam edin. Şimdi iki yeni ve geçici git deponuz olacak.

Fethet birleştirme uygulamalar ve kütüphanelerini tarafından

3 - Yepyeni bir repo hazırlayın:

mkdir my-desired-repo
cd my-desired-repo
git init

Ve en az bir taahhütte bulunmanız gerekecek. Aşağıdaki üç satır atlanırsa, ilk repo, deponuzun kökünün hemen altında görünecektir:

touch a_file_and_make_a_commit # see user's feedback
git add a_file_and_make_a_commit
git commit -am "at least one commit is needed for it to work"

Geçici dosya işlendiğinde, mergesonraki bölümdeki komut beklendiği gibi duracaktır.

Kullanıcının geri bildirimlerini alarak, gibi rastgele bir dosya eklemek yerine, a_file_and_make_a_commita .gitignoreveya README.mdbenzeri eklemeyi seçebilirsiniz .

4 - Önce uygulama deposunu birleştir:

git remote add apps-repo ../apps-repo
git fetch apps-repo
git merge -s ours --no-commit apps-repo/master # see below note.
git read-tree --prefix=apps -u apps-repo/master
git commit -m "import apps"

Şimdi yeni deponuzda uygulamalar dizinini görmelisiniz . git logtüm ilgili tarihsel taahhüt mesajlarını göstermelidir.

Not: Chris Git yeni sürümü (> = 2.9) için yapılan yorumlar, aşağıda belirtildiği gibi, belirtmeniz gerekir --allow-unrelated-historiesilegit merge

5 - Sonraki libs repo'yu aynı şekilde birleştirin:

git remote add libs-repo ../libs-repo
git fetch libs-repo
git merge -s ours --no-commit libs-repo/master # see above note.
git read-tree --prefix=libs -u libs-repo/master
git commit -m "import libs"

Birleştirilecek 2'den fazla deponuz varsa devam edin.

Başvuru: Başka bir deponun alt dizinini git ile birleştir


4
Git 2.9'dan beri birleştirme komutlarında --allow-unlatelated-history kullanmanız gerekir. Aksi takdirde, bu benim için iyi iş gibi görünüyor.
Chris

1
Genius! Bunun için çok teşekkür ederim. Çok büyük bir depoda bir ağaç filtresi kullanarak baktığım ilk cevaplar, git yeniden yazma işlemlerini tamamlamak için 26 saatten fazla sürmeyi tahmin ediyordu. Bu basit ama tekrarlanabilir yaklaşım ile çok daha mutlu ve 4 alt klasör başarıyla beklenen taahhüt geçmişi ile yeni bir repo taşındı.
shuttsy

1
İlk komutu, ekleyen .gitignoreve README.mddosya ekleyen bir "İlk işleme" için kullanabilirsiniz .
Jack Miller

2
Ne yazık ki bu yaklaşım, git merge .. git read-treeadımda eklenen dosyalar için izleme geçmişini kırıyor gibi görünüyor , çünkü bunları yeni eklenen dosyalar olarak kaydediyor ve tüm git guis'm önceki taahhütleriyle bağlantı kurmuyor.
Dai

1
@ksadjad, Fikrim yok, dürüst olmak gerekirse. Manuel birleştirmenin merkezi noktası, yeni repo oluşturmak için dizinleri seçmek ve taahhüt geçmişlerini korumaktır. Bir komitenin dirA, dirB, dirDrop ve sadece dirA ve dirB dosyalarına yeni repo için seçildiği böyle bir durumun nasıl işleneceğinden emin değilim, taahhüt geçmişinin orijinal ile ilgili olması gerekir.
chfw

27

Neden filter-branchbirden fazla koşmak istiyorsun ? Hepsini tek bir taramada yapabilirsiniz, bu yüzden zorlamanıza gerek yok (bunun extglobçalışması için kabuğunuzda etkinleştirmeniz gerektiğini unutmayın ):

git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch $(ls -xd apps/!(AAA) libs/!(XXX))" --prune-empty -- --all

Bu, istenmeyen alt dizinlerdeki tüm değişikliklerden kurtulmalı ve tüm dallarınızı ve taahhütlerinizi saklamalıdır (sadece budanmış alt dizinlerdeki dosyaları etkilemedikleri sürece --prune-empty) - yinelenen taahhütlerle ilgili bir sorun vb.

Bu işlemden sonra istenmeyen dizinler tarafından izlenmemiş olarak listelenir git status.

$(ls ...)Gerekli st olduğunu extglobkullanır kabuğundan yerine endeksi filtresi tarafından değerlendirilir shyerleşiği eval(burada extglobmevcut değildir). Bkz Ben GIT'de kabuk seçenekleri etkinleştirmek nasıl? daha ayrıntılı bilgi için.


1
İlginç fikir. Benzer bir sorunum var ama işe yaramadım, bkz. Stackoverflow.com/questions/8050687/…
manol

Repo boyunca hem dosya hem de klasör serpme olmasına rağmen, ihtiyacım olan şey bu ... Teşekkürler :)
notlesh

1
hm. extglob açıkken bile parantezimin yakınında bir hata alıyorum: sözdizimi hatası beklenmedik belirteci yakınında `` '' komutum şöyle görünüyor: git filter-branch -f --index-filter "git rm -r -f --cached - -ignore-unmatch src / css / themes /! (some_theme *) "--prune-empty - --tüm src / css / themes /! (some_theme *) içeren bir ls, extglob'un göründüğü diğer tüm temaları döndürür çalışıyor ...
robdodson

2
@MikeGraf Bunun istenen sonucu vereceğini düşünmüyorum: kaçmak gerçek bir "!" vb.
kynan

1
@ david-smiley'nin (daha yeni) yanıtı çok benzer bir yaklaşım kullanır, ancak yalnızca gitkomutlara lsgüvenme avantajına sahiptir ve bu nedenle @Bae'nin keşfettiği gibi, işletim sistemleri arasında nasıl yorumlandığına dair farklılıklara duyarlı değildir .
Jeremy Caney

20

Burada kendi sorumu cevaplıyorum ... bir çok deneme yanılma sonrasında.

Bunu git subtreeve kombinasyonunu kullanarak yapmayı başardım git-stitch-repo. Bu talimatlar aşağıdakilere dayanmaktadır:

İlk olarak, kendi ayrı depolarına koymak istediğim dizinleri çıkardım:

cd origRepo
git subtree split -P apps/AAA -b aaa
git subtree split -P libs/XXX -b xxx

cd ..
mkdir aaaRepo
cd aaaRepo
git init
git fetch ../origRepo aaa
git checkout -b master FETCH_HEAD

cd ..
mkdir xxxRepo
cd xxxRepo
git init
git fetch ../origRepo xxx
git checkout -b master FETCH_HEAD

Daha sonra yeni bir boş depo oluşturdum ve son ikisini içine aldım / diktim:

cd ..
mkdir newRepo
cd newRepo
git init
git-stitch-repo ../aaaRepo:apps/AAA ../xxxRepo:libs/XXX | git fast-import

Bu, iki dal oluşturur master-Ave master-Bher biri dikişli depolardan birinin içeriğini tutar. Bunları birleştirmek ve temizlemek için:

git checkout master-A
git pull . master-B
git checkout master
git branch -d master-A 
git branch -d master-B

Şimdi bunun nasıl / ne zaman olduğundan emin değilim, ancak ilk checkoutve sonrasından sonra pullkod sihirli bir şekilde ana dalda birleşiyor (burada neler olduğuna dair herhangi bir fikir takdir ediliyor!)

Her şey beklendiği gibi ben bakmak eğer dışında çalışmış gibi görünüyor newRepotarihini işlemek değişiklik kümesi hem etkilenen zaman, çiftleri vardır apps/AAAve libs/XXX. Yinelenenleri kaldırmanın bir yolu varsa, o zaman mükemmel olurdu.


Burada bulduğunuz temiz araçlar. "Checkout" ile ilgili bilgiler: "git pull", "git fetch && git merge" ile aynıdır. "Getirme" bölümü zararsızdır çünkü "yerel olarak getiriliyor". Bu yüzden bu ödeme komutunun "git merge master-B" ile aynı olduğunu düşünüyorum, ki bu biraz daha belirgindir. Bkz kernel.org/pub/software/scm/git/docs/git-pull.html
phord

1
Ne yazık ki, git-stitch-repo aracı günümüzde kötü bağımlılıklar nedeniyle bozuldu.
Henrik

@Henrik Tam olarak hangi problemi yaşıyorsunuz? export PERL5LIB="$PERL5LIB:/usr/local/git/lib/perl5/site_perl/"Git.pm'i bulabilmesi için bash yapılandırmamı eklemek zorunda kalmama rağmen benim için çalışıyor. Sonra cpan ile kurdum.

git subtree addBu görevi gerçekleştirmek için kullanmak mümkündür . Bkz. Stackoverflow.com/a/58253979/1894803
laconbass

7

Tam olarak bu sorunu çözmek için bir git filtresi yazdım. Git_filter'in fantastik ismine sahiptir ve github'da bulunur:

https://github.com/slobobaby/git_filter

Mükemmel libgit2'ye dayanmaktadır.

Ben büyük bir depo birçok taahhüt (~ 100000) ile bölmek gerekiyordu ve git filtre-şube dayalı çözümler çalıştırmak için birkaç gün sürdü. git_filter aynı şeyi yapmak için bir dakika alır.


7

'Git splits' git uzantısını kullan

git splitsjkeating çözümünegit branch-filter dayanarak, git uzantısı olarak oluşturduğum bir sarıcı olan bir bash betiğidir .

Tam olarak bu durum için yapıldı. Hatanız için git splits -f, yedeklemeyi kaldırmaya zorlama seçeneğini kullanmayı deneyin . Çünkü git splitsyeni bir dal üzerinde çalışan yedekleme gereksiz yani, o anki şube yeniden olmayacaktır. Daha fazla ayrıntı için benioku dosyasına bakın ve repo kopyasında / kopyasında kullandığınızdan emin olun (her ihtimale karşı!) .

  1. Yüklemek git splits .
  2. Dizinleri yerel bir şubeye bölme #change into your repo's directory cd /path/to/repo #checkout the branch git checkout XYZ
    #split multiple directories into new branch XYZ git splits -b XYZ apps/AAA libs/ZZZ

  3. Bir yerde boş bir repo oluşturun. xyzGitHub'da yolu olan boş bir repo oluşturduğumuzu varsayacağız :git@github.com:simpliwp/xyz.git

  4. Yeni repoya zorla. #add a new remote origin for the empty repo so we can push to the empty repo on GitHub git remote add origin_xyz git@github.com:simpliwp/xyz.git #push the branch to the empty repo's master branch git push origin_xyz XYZ:master

  5. Yeni oluşturulan uzak repoyu yeni bir yerel dizine kopyalayın
    #change current directory out of the old repo cd /path/to/where/you/want/the/new/local/repo #clone the remote repo you just pushed to git clone git@github.com:simpliwp/xyz.git


Bölmeye dosya eklemek ve daha sonra güncellemek mümkün görünmüyor, değil mi?
Alex

Bu, tonlarca taahhütle
repoma

git-split , --subdirectory-filter ile karşılaştırıldığında son derece yavaş git --index filtresi kullanıyor gibi görünüyor . Bazı depolar için hala geçerli bir seçenek olabilir, ancak büyük depolar için (birden fazla gigabayt, 6 haneli işlem) --index filtresinin, özel bulut donanımında bile etkili bir şekilde çalışması haftalar sürer.
Jostein Kjønigsen

6
git clone git@example.com:thing.git
cd thing
git fetch
for originBranch in `git branch -r | grep -v master`; do
    branch=${originBranch:7:${#originBranch}}
    git checkout $branch
done
git checkout master

git filter-branch --index-filter 'git rm --cached -qr --ignore-unmatch -- . && git reset -q $GIT_COMMIT -- dir1 dir2 .gitignore' --prune-empty -- --all

git remote set-url origin git@example.com:newthing.git
git push --all

Diğer tüm yorumları okumak beni doğru yolda tuttu. Ancak, çözümünüz işe yarıyor. Tüm dalları içe aktarır ve birden çok dizinle çalışır! Harika!
jschober

1
forDiğer benzer cevaplar dahil olmadığı için döngü, kabul değer. Klonunuzdaki her dalın yerel bir kopyası filter-branchyoksa, bunları yeniden yazma işleminin bir parçası olarak dikkate almazsınız; bu, diğer dallarda tanıtılan, ancak henüz geçerli dalınızla birleştirilmemiş dosyaları hariç tutabilir. (Yine de git fetch, güncel kaldıklarından emin olmak için daha önce kontrol ettiğiniz şubelerde yapmaya değer .)
Jeremy Caney

5

Kolay bir çözüm: git-filter-repo

Benzer bir sorunum vardı ve burada listelenen çeşitli yaklaşımları inceledikten sonra git-filter-repo'yu keşfettim . Buradaki resmi git belgelerinde git-filter-branch'a alternatif olarak önerilmektedir .

Varolan bir depodaki dizin alt kümesinden yeni bir havuz oluşturmak için şu komutu kullanabilirsiniz:

git filter-repo --path <file_to_remove>

Birden fazla dosya / klasörü zincirleyerek filtreleyin:

git filter-repo --path keepthisfile --path keepthisfolder/

Bu nedenle, orijinal soruyu cevaplamak için git-filter-repo ile aşağıdaki komuta ihtiyacınız var:

git filter-repo --path apps/AAA/ --path libs/XXX/

Bu kesinlikle harika bir cevap. Diğer tüm çözümlerle ilgili sorun, bir dizinin TÜM dallarının içeriğini ayıklamak mümkün olamazdı. Ancak git filter-repo, klasörü tüm dallardan geri aldı ve geçmişi mükemmel bir şekilde yeniden yazdı, ihtiyacım olmayan her şeyin tüm ağacını temizlemek gibi.
Teodoro

3

Evet. Bu uyarıyı geçersiz kılmak -fiçin sonraki çağrılarda bayrağı kullanarak yedeklemenin üzerine yazmaya zorlayın filter-branch. :) Aksi takdirde bir çözüm olduğunu düşünüyorum (yani, bir anda istenmeyen bir dizini ortadan kaldırmak filter-branch).


-4

İletinin önerdiği gibi, refs / original dizinindeki .git dizininin altında bulunan yedeklemeyi silin. Dizin gizlidir.

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.