Soru Alt dizini ayrı Git deposuna ayırma (taşıma)


Benim bir Git bir dizi alt dizin içeren bir depo. Şimdi alt dizinlerden birinin diğeriyle alakasız olduğunu ve ayrı bir depoya ayrıldığını buldum.

Alt dizin içindeki dosyaların geçmişini tutarken bunu nasıl yapabilirim?

Sanırım bir klon yapabilirim ve her bir klonun istenmeyen kısımlarını kaldırabilirim, ama sanırım bu daha eski bir revizyonu kontrol ederken bana tam bir ağaç verirdi. Bu kabul edilebilir olabilir, ama bunu yapmayı tercih ederim. iki havuzun paylaşılan bir geçmişi yoktur.

Sadece açıklığa kavuşturmak için aşağıdaki yapıya sahibim:

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

Ama bunun yerine şunu isterim:

XYZ/
    .git/
    XY1/
    XY2/
ABC/
    .git/
    ABC/

1595
2017-12-11 13:57


Menşei


Bu şimdi ile önemsiz git filter-branch cevabımı aşağıya bakın. - jeremyjjbrown
@jeremyjjbrown haklı. Bunu yapmak artık zor değil, ancak Google'da doğru cevabı bulmak zor çünkü tüm eski cevaplar sonuçlara hükmediyor. - Agnel Kurian


Cevaplar:


Güncelleştirme: Bu süreç o kadar yaygın ki, git ekibi yeni bir araçla çok daha kolaylaştı. git subtree. Buraya bakın: Alt dizini ayrı Git deposuna ayırma (taşıma)


Deponuzu klonlamak ve sonra kullanmak git filter-branch Yeni repo'nuzda istediğiniz alt dizini, çöp toplama amacıyla işaretlemek için.

  1. Yerel deponuzu klonlamak için:

    git clone /XYZ /ABC
    

    (Not: depo, sabit linkler kullanılarak klonlanacaktır, ancak bu, hard-link dosyaları kendiliğinden değiştirilmeyeceği için bir problem değildir - yeni olanlar oluşturulacaktır.)

  2. Şimdi, yeniden yazmak istediğimiz ilginç dalları koruyalım ve daha sonra oraya itmemek için orijini kaldırmalı ve eski taahhütlerin orijin tarafından referans gösterilmemesini sağlamalıyız:

    cd /ABC
    for i in branch1 br2 br3; do git branch -t $i origin/$i; done
    git remote rm origin
    

    ya da tüm uzak şubeler için:

    cd /ABC
    for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
    git remote rm origin
    
  3. Artık alt projeyle ilgisi olmayan etiketleri de kaldırmak isteyebilirsiniz; Bunu daha sonra da yapabilirsiniz, ancak repo'unuzu tekrar eritmeniz gerekebilir. Ben yapmadım ve bir WARNING: Ref 'refs/tags/v0.1' is unchanged tüm etiketler için (hepsi alt proje ile ilgisiz oldukları için); ek olarak, bu tür etiketleri kaldırdıktan sonra daha fazla alan geri alınacaktır. Görünüşe göre git filter-branch Diğer etiketleri yeniden yazabilmeli, ancak bunu doğrulayamadım. Tüm etiketleri kaldırmak isterseniz git tag -l | xargs git tag -d.

  4. Daha sonra filtre dalını kullanın ve diğer dosyaları hariç tutmak için sıfırlayın, böylece bunlar budanabilir. Ayrıca ekleyelim --tag-name-filter cat --prune-empty boş öğeleri kaldırmak ve etiketleri yeniden yazmak için (bunun imzasını atmak zorunda kalacağını unutmayın):

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC -- --all
    

    Veya alternatif olarak, sadece HEAD şubesini yeniden yazmak ve etiketleri ve diğer dalları yoksaymak için:

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
  5. Daha sonra, alanın gerçekten geri kazanılabilmesi için yedek reflogları silin (şimdi operasyonun yıkıcı olmasına rağmen)

    git reset --hard
    git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
    git reflog expire --expire=now --all
    git gc --aggressive --prune=now
    

    ve şimdi tüm geçmişi korunarak ABC alt dizininin yerel bir git deposu var.

Not: Çoğu kullanım için, git filter-branch gerçekten eklenen parametresi olmalı -- --all. Evet bu gerçekten --uzay--  all. Bu, komutun son parametreleri olmalıdır. Matli'nin keşfettiği gibi, bu yeni repoda yer alan proje dallarını ve etiketlerini koruyor.

Düzenleme: Örneğin, deponun aslında küçülttüğünden emin olmak için aşağıdaki yorumlardan çeşitli öneriler eklenmiştir (ki bu her zaman daha önce görülmemişti).


1155
2017-07-25 17:10



Çok iyi cevap. Teşekkürler! Ve tam olarak ne istediğimi elde etmek için filtre-şube komutuna "- -" ekledim. - matli
Neden ihtiyacın var --no-hardlinks? Bir hardlink'in kaldırılması diğer dosyayı etkilemez. Git nesneleri de değişmez. Yalnızca ihtiyacınız olan sahip / dosya izinlerini değiştirdiyseniz --no-hardlinks. - vdboor
Tavsiye ederim ek bir adım "git uzak rm kökeni" olacaktır. Yanılmıyorsam, bu, itirafların orijinal depoya geri gitmesini engeller. - Tom
Eklemek için başka bir komut filter-branch olduğu --prune-empty, şimdi boş olan işlemleri kaldırmak için. - Seth Johnson
Paul gibi, yeni repo'mda proje etiketleri istemedim, bu yüzden kullanmadım -- --all. Bende koştum git remote rm origin, ve git tag -l | xargs git tag -d önce git filter-branch Komut. Bu daraldı benim .git 60M ila ~ 300K arasında dizin. Boyut küçültmeyi elde etmek için bu komutların her ikisini de çalıştırmam gerektiğine dikkat edin. - saltycrane


Kolay Yolu ™

Bu, gitmenin üst düzey yöneticilerinin bunu gerçekten kolaylaştırdığı, ancak daha yeni bir sürümüne sahip olmanız gerektiği gibi yaygın ve kullanışlı bir uygulama olduğu ortaya çıkıyor (> = 1.7.11 Mayıs 2012). Bakın apandis En son git nasıl kurulur. Ayrıca, bir gerçek dünya örneği içinde örneklerde altında.

  1. Eski repo hazırlayın

    pushd <big-repo>
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    popd
    

    Not:  <name-of-folder> lider veya takip eden karakterler içermemelidir. Örneğin, adlı klasör subproject Olarak geçirilmelidir subproject, DEĞİL ./subproject/

    Windows kullanıcıları için not: Klasör derinliği> 1 olduğunda, <name-of-folder> * nix tarzı klasör ayırıcı (/) olmalıdır. Örneğin, adlı klasör path1\path2\subproject Olarak geçirilmelidir path1/path2/subproject

  2. Yeni repo oluştur

    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Yeni repo'yu Github'a veya nerede olursanız olun

    git remote add origin <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
  4. Temizlemek, arzu edildiği takdirde

    popd # get out of <new-repo>
    pushd <big-repo>
    
    git rm -rf <name-of-folder>
    

    Not: Bu, depodaki tüm tarihi referansları bırakır. apandis Aşağıda gerçekten bir şifre almış olmanızdan endişe ediyorsanız veya dosya boyutunuzu azaltmanız gerekiyorsa .git Klasör.

...

Bakış

Bunlar yukarıdakiyle aynı adımlarAncak, kullanmak yerine benim depom için tam adımları izleyerek <meta-named-things>.

İşte düğümde JavaScript tarayıcı modülleri uygulamak için sahip olduğum bir proje:

tree ~/Code/node-browser-compat

node-browser-compat
├── ArrayBuffer
├── Audio
├── Blob
├── FormData
├── atob
├── btoa
├── location
└── navigator

Tek bir klasör ayırmak istiyorum btoaayrı bir git deposuna

pushd ~/Code/node-browser-compat/
git subtree split -P btoa -b btoa-only
popd

Şimdi yeni bir şubem var btoa-only, sadece bunun için taahhütler var btoa ve yeni bir depo oluşturmak istiyorum.

mkdir ~/Code/btoa/
pushd ~/Code/btoa/
git init
git pull ~/Code/node-browser-compat btoa-only

Ardından Github veya bitbucket üzerinde yeni bir repo oluşturuyorum veya her neyse ve ekleyelim. origin (btw, "origin" sadece bir sözleşmedir, komutun bir parçası değildir - buna "remote-server" diyebilirsiniz ya da ne istersen)

git remote add origin git@github.com:node-browser-compat/btoa.git
git push origin -u master

Mutlu gün!

Not: Bir ile bir repo oluşturduysanız README.md, .gitignore ve LICENSEİlk önce çekmeniz gerekecek:

git pull origin -u master
git push origin -u master

Son olarak, klasörü daha büyük repodan çıkarmak istiyorum

git rm -rf btoa

...

apandis

OS X'deki son git

Git'in son sürümünü almak için:

brew install git

OS X için demlemek için:

http://brew.sh

Ubuntu'daki son git

sudo apt-get update
sudo apt-get install git
git --version

Bu işe yaramazsa (ubuntu'nun çok eski bir versiyonuna sahipsiniz)

sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git

Eğer hala çalışmıyorsa, deneyin

sudo chmod +x /usr/share/doc/git/contrib/subtree/git-subtree.sh
sudo ln -s \
/usr/share/doc/git/contrib/subtree/git-subtree.sh \
/usr/lib/git-core/git-subtree

Yorumlardan rui.araujo için teşekkürler.

geçmişini temizle

Git dosyaları varsayılan olarak kaldırmadan onları gerçekten kaldırmaz, sadece artık orada olmadıklarını taahhüt eder. Tarihsel referansları gerçekten kaldırmak isterseniz (örn. Bir şifreniz var), bunu yapmanız gerekir:

git filter-branch --prune-empty --tree-filter 'rm -rf <name-of-folder>' HEAD

Bundan sonra dosya veya klasörün artık git geçmişinde görünmediğini kontrol edebilirsiniz.

git log -- <name-of-folder> # should show nothing

Sen yine de github'a "itme" silemez ve benzerleri. Eğer denerseniz, bir hata alırsınız ve git pull yapmadan önce git push - ve sonra geçmişinde herşeye sahip olursun.

Yani geçmişi "github, bitbucket, vs'den silmek" anlamına gelen "origin" ten silmek istiyorsanız - repo'yu silmeniz ve repo'nun budanmış bir kopyasını yeniden yüklemeniz gerekir. Fakat bekle - fazlası var! - Paroladan kurtulmak ya da böyle bir şeyden kurtulmaktan gerçekten endişe duyuyorsanız, yedeklemeye karar vermeniz gerekir (aşağıya bakın).

yapma .git daha küçük

Yukarıda belirtilen silme geçmişi komutu hala bir grup yedek dosyanın arkasına bırakır - çünkü git, repo'unuzu kazara bozmamanıza yardımcı olmak için her şeyden çok iyidir. Günler ve aylar boyunca yetim dosyaları silecek, ancak istemediğiniz bir şeyi yanlışlıkla silmiş olduğunuzu fark ettiğinizde onları bir süreliğine bırakır.

Eğer gerçekten istiyorsan boş çöp için klon boyutunu küçült Bir repo hemen tüm bu gerçekten garip şeyleri yapmak zorunda:

rm -rf .git/refs/original/ && \
git reflog expire --all && \
git gc --aggressive --prune=now

git reflog expire --all --expire-unreachable=0
git repack -A -d
git prune

Bu, yapmanız gerekenleri bilmediğiniz sürece, bu adımları gerçekleştirmemenizi tavsiye ederim - sadece yanlış alt dizini yapabilmeniz durumunda, biliyor musunuz? Repo'yu bastığınızda yedek dosyalar klonlanmamalı, sadece yerel kopyanızda olacaklar.

Kredi


1124
2018-06-05 13:15



git subtree hala 'contrib' klasörünün bir parçasıdır ve tüm dağıtımlarda varsayılan olarak yüklenmez. github.com/git/git/blob/master/contrib/subtree - onionjake
@krlmlr sudo chmod + x /usr/share/doc/git/contrib/subtree/git-subtree.sh sudo ln -s / usr/share/doc/git/contrib/subtree/git-subtree.sh / usr / lib / git-core / git-subtree Ubuntu 13.04 üzerinde etkinleştirmek için - rui.araujo
Bir kamu havuzuna bir parola ittiyseniz, parolayı değiştirmelisiniz, kamu repo'undan kaldırmaya çalışmayın ve hiç kimsenin görmediğini ummayın. - Miles Rout
Bu içeriği ile yeni bir repo yapmak gibi görünüyor ABC/ama yeni repo klasörü içermiyor ABC/ kendisi, soru olarak sordu. Bunu nasıl yapardın? - woojoo666
Bu çözüm tarihi korumaz. - Cœur


Paul'un cevabı / ABC içeren yeni bir depo oluşturur, ancak / XYZ içinden / ABC'yi kaldırmaz. Aşağıdaki komut / XYZ içinden / ABC'yi kaldıracaktır:

git filter-branch --tree-filter "rm -rf ABC" --prune-empty HEAD

Tabii ki, önce bir 'clone --no-hardlinks' deposunda test edin ve Paul listelerini reset, gc ve prune komutları ile takip edin.


131
2017-10-19 21:10



şunu yap git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch ABC" --prune-empty HEAD ve olacak çok Daha hızlı. index-filter indekste çalışırken, filtre-filtre çıktı ve aşaması her şey için her şey. - fmarc
bazı durumlarda depo XYZ tarihini berbat overkill ... sadece basit bir "rm -rf ABC; git rm -r ABC; git commit -m 'kendi repo içine ABC çıkarıldı" çoğu insan için daha iyi çalışır. - Evgeny
Muhtemelen birden çok kez yaparsanız, örneğin, iki dizini ayrıldıktan sonra kaldırmak için -f (force) komutunu kullanmak isteyebilirsiniz. Aksi takdirde "Yeni bir yedek oluşturulamıyor" alacaksınız. - Brian Carlton
Eğer yapıyorsan --index-filter yöntem, ayrıca bunu yapmak isteyebilirsiniz git rm -q -r -fBöylece her bir çağrı, silen her dosya için bir satır basmaz. - Eric Naeseth
Pavlus'un cevabını düzenlemeyi öneririm çünkü sadece Paul çok titizdir. - Erik Aronesty


Eski depoyu yeni depodan düzgün bir şekilde silmek için, daha sonra biraz daha fazla çalışma yapmanız gerektiğini öğrendim. filter-branch adım.

  1. Klonu ve filtreyi yapın:

    git clone --no-hardlinks foo bar; cd bar
    git filter-branch --subdirectory-filter subdir/you/want
    
  2. Eski tarihe yapılan tüm referansları kaldırın. “Kökeni” klonunuzu takip ediyordu ve “orijinal”, filtre-dalının eski şeyleri kurtardığı yerdir:

    git remote rm origin
    git update-ref -d refs/original/refs/heads/master
    git reflog expire --expire=now --all
    
  3. Şimdi bile, geçmişiniz fsck'nin dokunmayacağı bir paket dosyasında sıkışmış olabilir. Parçalara ayırma, yeni bir paket dosyası oluşturma ve kullanılmayan nesneleri silme:

    git repack -ad
    

Var bunun bir açıklaması içinde filtre dalı için manuel.


94
2018-06-09 15:41



Sanırım bir şey gibi git gc --aggressive --prune=now hala kayıp değil mi? - Albert
@Albert Repack komutu bununla ilgilenir ve herhangi bir gevşek nesne olmazdı. - Josh Lee
sadece repack benim için çalışmadı, git gc yapmak için gerekli - jsvnm
Evet, git gc --aggressive --prune=now yeni repo'yu azaltmış - Tomek Wyderka
Basit ve zarif. Teşekkürler! - Marco Pelegrini


Düzenleme: Bash komut dosyası eklendi.

Burada verilen cevaplar benim için sadece kısmen çalıştı; Çok sayıda büyük dosya önbellekte kalmıştır. Sonunda ne çalıştı (freenode'da #git saat sonra):

git clone --no-hardlinks file:///SOURCE /tmp/blubb
cd blubb
git filter-branch --subdirectory-filter ./PATH_TO_EXTRACT  --prune-empty --tag-name-filter cat -- --all
git clone file:///tmp/blubb/ /tmp/blooh
cd /tmp/blooh
git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

Önceki çözümlerle, depo boyutu 100 MB civarındaydı. Bu onu 1.7 MB'ye getirdi. Belki birilerine yardım eder :)


Aşağıdaki betik betiği görevi otomatik hale getirir:

!/bin/bash

if (( $# < 3 ))
then
    echo "Usage:   $0 </path/to/repo/> <directory/to/extract/> <newName>"
    echo
    echo "Example: $0 /Projects/42.git first/answer/ firstAnswer"
    exit 1
fi


clone=/tmp/${3}Clone
newN=/tmp/${3}

git clone --no-hardlinks file://$1 ${clone}
cd ${clone}

git filter-branch --subdirectory-filter $2  --prune-empty --tag-name-filter cat -- --all

git clone file://${clone} ${newN}
cd ${newN}

git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

38
2017-08-20 14:11





Artık bu kadar karmaşık değil, sadece git filtre-dalı istemediğiniz alt dizinleri taramak ve sonra yeni kumandayı itmek için bir klonunuzun repo'suna komut verin.

git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .

21
2018-03-22 20:55



Bu bir çekicilik gibi çalıştı. Yukarıdaki örnekteki YOUR_SUBDIR, TUTMAK istediğiniz alt dizindir, diğer her şey kaldırılacak - J.T. Taylor
Güncellemeler size dayalı yorum. - jeremyjjbrown
Bu soruya cevap vermiyor. Dergilerden diyor The result will contain that directory (and only that) as its project root. ve aslında bu sizin elde edeceğiniz şeydir, yani orijinal proje yapısı korunmaz. - NicBright
@NicBright Sorunu XYZ ve ABC ile neyin yanlış olduğunu göstermek için sorudaki gibi gösterebilir misiniz? - Adam
@jeremyjjbrown klonlanmış repo yeniden kullanmak mümkün ve yeni bir repo kullanmak, yani benim soru burada stackoverflow.com/questions/49269602/... - Qiulang


Güncelleştirme: Git-subtree modülü git ekibi o çekirdeğe çekti ve bunu yapmak için çok yararlı oldu git subtree. Buraya bakın: Alt dizini ayrı Git deposuna ayırma (taşıma)

git-subtree bunun için yararlı olabilir

http://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt (Kaldırıldı)

http://psionides.jogger.pl/2010/02/04/sharing-code-between-projects-with-git-subtree/


19
2017-08-06 15:26



git-subtree artık gitmenin bir parçası olmasına rağmen Git'in bir parçası, her zaman varsayılan olarak yüklü değil. Homebrew git formülü tarafından kurulduğunu biliyorum, ancak onun adam sayfası olmadan. apenwarr bu yüzden onun versiyonunu geçersiz kılar. - echristopherson