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


1758

Bir dizi alt dizin içeren bir Git deposu var. Şimdi alt dizinlerden birinin diğeri ile ilgisiz olduğunu ve ayrı bir depoya ayrılması gerektiğini buldum.

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

Sanırım bir klon yapabilir ve her bir klonun istenmeyen kısımlarını kaldırabilirim, ama sanırım bu eski bir revizyonu kontrol ederken bana tam ağacı verecekti. Bu kabul edilebilir olabilir, ancak 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 bunu istiyorum:

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

7
Şimdi bu git filter-branchbenim cevabımı görmek ile bu önemsiz .
jeremyjjbrown

8
@jeremyjjbrown haklı. Bunu yapmak artık zor değil, ancak Google'da doğru cevabı bulmak zor, çünkü tüm eski cevaplar sonuçlara hakim.
Agnel Kurian

Yanıtlar:


1228

Güncelleme : Bu süreç o kadar yaygın ki, git ekibi yeni bir araçla çok daha basit hale getirdi git subtree. Buraya bakın: Alt dizini ayrı Git deposuna ayırın (taşıyın)


Deponuzu klonlamak ve daha sonra git filter-branchyeni deponuzdaki alt dizin dışında her şeyi çöp toplamak için işaretlemek için kullanın .

  1. Yerel deponuzu klonlamak için:

    git clone /XYZ /ABC
    

    (Not: depo sabit bağlantılar kullanılarak klonlanacaktır, ancak sabit bağlantılı dosyalar kendi başlarına değiştirilmeyeceği için bu bir sorun değildir - yenileri oluşturulacaktır.)

  2. Şimdi, yeniden yazmak istediğimiz ilginç dalları koruyalım ve sonra oraya itmekten kaçınmak ve eski taahhütlere kaynak tarafından referans verilmeyeceğinden emin olmak için menşei kaldıralım:

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

    veya 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. Şimdi, alt projeyle hiçbir ilişkisi olmayan etiketleri de kaldırmak isteyebilirsiniz; bunu daha sonra da yapabilirsiniz, ancak repoyu tekrar budamanız gerekebilir. Bunu yapmadım ve WARNING: Ref 'refs/tags/v0.1' is unchangedtüm etiketler için bir var (hepsi alt projeyle ilgisi olmadığı için); ayrıca, bu tür etiketler kaldırıldıktan sonra daha fazla alan geri kazanılacaktır. Görünüşe göre git filter-branchdiğer etiketleri yeniden yazabilmelidir, ancak bunu doğrulayamadım. Tüm etiketleri kaldırmak istiyorsanız, kullanın git tag -l | xargs git tag -d.

  4. Daha sonra filtre dalını kullanın ve budanmaları bulabilmek için diğer dosyaları dışlamak için sıfırlayın. --tag-name-filter cat --prune-emptyBoş taahhütleri kaldırmak ve etiketleri yeniden yazmak için de ekleyelim (bunun imzasını çıkarmak zorunda kalacağını unutmayın):

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

    veya alternatif olarak, yalnızca HEAD dalını yeniden yazmak ve etiketleri ve diğer dalları yok saymak için:

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
  5. Ardından, alanın gerçekten geri kazanılabilmesi için yedek reflog'ları silin (şimdi işlem 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 ABC alt dizininin tüm geçmişi korunmuş bir yerel git deposuna sahipsiniz.

Not: Çoğu kullanım için git filter-branchgerçekten eklenen parametreye sahip olmalıdır -- --all. Evet, bu gerçekten --space-- all. Bu komutun son parametreleri olmalıdır. Matli'nin keşfettiği gibi, bu proje dallarını ve etiketleri yeni repoya dahil ediyor.

Düzenleme: örneğin deponun aslında küçültülmüş olduğundan emin olmak için aşağıdaki yorumlardan çeşitli öneriler eklenmiştir (her zaman böyle değildi).


29
Çok iyi bir cevap. Teşekkürler! Ve tam olarak istediğimi elde etmek için filtre dalı komutuna "- --all" ekledim.
matli

12
Neden ihtiyacın var --no-hardlinks? Bir sabit bağlantının kaldırılması diğer dosyayı etkilemez. Git nesneleri de değiştirilemez. Yalnızca ihtiyacınız olan sahip / dosya izinlerini değiştirirseniz --no-hardlinks.
vdboor

67
Tavsiye ederim ek bir adım "git remote rm origin" olacaktır. Bu, yanılmıyorsam, orijinal depoya geri dönmekten alıkoyar.
Tom

13
Eklenecek başka bir komut filter-branchise --prune-empty, şimdi boş olan taahhütleri kaldırmaktır.
Seth Johnson

8
Paul gibi, yeni depomda proje etiketleri istemiyordum, bu yüzden kullanmadım -- --all. Ben de koştum git remote rm originve komuttan git tag -l | xargs git tag -dönce git filter-branch. Bu, .gitdizinimi 60M'den ~ 300K'ya düşürdü. Boyutu küçültmek için bu komutların her ikisini de çalıştırmam gerektiğini unutmayın.
saltycrane

1321

Easy Way ™

Bunun öyle yaygın ve kullanışlı bir uygulama olduğu anlaşılıyor ki, Git'in derebileri bunu gerçekten kolaylaştırdı, ancak Git'in daha yeni bir sürümüne sahip olmanız gerekiyor (> = 1.7.11 Mayıs 2012). En son Git'in nasıl kurulacağı ile ilgili eke bakın . Ayrıca, orada gerçek dünya örneği de izlenecek altında.

  1. Eski repoyu hazırla

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

    Not: <name-of-folder> önde gelen veya arkadaki karakterleri İÇERMEMELİDİR. Örneğin, adlı klasör subprojectZORUNLU subprojectDEĞİL olarak geçirilmelidir ZORUNLU./subproject/

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

  2. Yeni repo oluştur

    mkdir ~/<new-repo> && cd ~/<new-repo>
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Yeni repoyu GitHub'a veya herhangi bir yere bağlayın

    git remote add origin <git@github.com:user/new-repo.git>
    git push -u origin master
    
  4. Temizleme içinde <big-repo>, eğer arzu edilen

    git rm -rf <name-of-folder>
    

    Not : Bu, depodaki tüm geçmiş referansları bırakır.Girişte bir şifre işlemeyle ilgili endişeleriniz varsa veya klasörünüzün dosya boyutunu küçültmeniz gerekiyorsa aşağıdaki Ek'e bakın .git.

...

Bakış

Bunlar yukarıdakiyle aynı adımlardır , ancak depom için kullanmak yerine tam adımımı izlerler <meta-named-things>.

Düğümde JavaScript tarayıcı modüllerini uygulamak için sahip olduğum bir proje:

tree ~/node-browser-compat

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

Tek bir klasörü btoaayrı bir Git deposuna bölmek istiyorum

cd ~/node-browser-compat/
git subtree split -P btoa -b btoa-only

Artık yeni bir şubem var, btoa-onlysadece taahhütleri var btoave yeni bir depo oluşturmak istiyorum.

mkdir ~/btoa/ && cd ~/btoa/
git init
git pull ~/node-browser-compat btoa-only

Sonra GitHub veya Bitbucket'te yeni bir repo oluşturuyorum ya da her neyse origin

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

Mutlu gün!

Not: Eğer bir ile repo oluşturduysanız README.md, .gitignoreve LICENSEöncelikle çekmek gerekir:

git pull origin master
git push origin master

Son olarak, klasörü daha büyük depodan kaldırmak isteyeceğim

git rm -rf btoa

...

apandis

MacOS'ta en son Git

Homebrew kullanarak Git'in en son sürümünü edinmek için :

brew install git

Ubuntu'da en son Git

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

Bu işe yaramazsa (Ubuntu'nun çok eski bir sürümüne sahipsiniz),

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

Bu hala işe yaramazsa, 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'ya teşekkürler.

Geçmişinizi temizleme

Varsayılan olarak dosyaları Git'ten kaldırmak aslında onları kaldırmaz, sadece artık orada olmadıklarını taahhüt eder. Geçmişteki referansları gerçekten kaldırmak istiyorsanız (yani bir şifre vermişseniz), bunu yapmanız gerekir:

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

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

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

Ancak, silme işlemlerini GitHub ve benzerine "itemez" . Eğer denerseniz bir hata alırsınız vegit pull önce yapmanızgit push - ve sonra geçmişinizdeki her şeye geri dönersiniz.

Dolayısıyla, geçmişi "başlangıç ​​noktasından" silmek istiyorsanız - GitHub, Bitbucket, vb. Öğesinden silmek anlamına gelir - repo'yu silmeniz ve repo'nun budanmış bir kopyasını yeniden itmeniz gerekir. Ama bekleyin - dahası var ! - Gerçekten bir parola veya benzeri bir şeyden kurtulmaktan endişe ediyorsanız, yedeklemeyi budamanız gerekir (aşağıya bakın).

Yapımı .git küçük

Yukarıda belirtilen silme geçmişi komutu hala bir grup yedekleme dosyasının ardında kalıyor - çünkü Git yanlışlıkla deponuzu yanlışlıkla mahvetmemenize yardımcı olmak için çok nazik. Sonunda yetim olmayan dosyaları günler ve aylar içinde siler, ancak istemediğiniz bir şeyi yanlışlıkla sildiğinizi fark etmeniz durumunda bir süre orada bırakır.

Bu nedenle , bir repo'nun klon boyutunu hemen azaltmak için çöp kutusunu gerçekten boşaltmak istiyorsanız, tüm bu gerçekten garip şeyleri yapmanız gerekir:

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

Bununla birlikte, yanlış alt dizini budamak durumunda olmanız gerektiğini bilmiyorsanız, bu adımları uygulamamanızı tavsiye ederim. Repoyu bastığınızda yedek dosyalar klonlanmamalıdır, sadece yerel kopyanızda olurlar.

Kredi


16
git subtreehala 'katkıda bulun' klasörünün bir parçasıdır ve varsayılan olarak tüm dağıtımlara yüklenmez. github.com/git/git/blob/master/contrib/subtree
onionjake

11
@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

41
Herkese açık bir depoya bir şifre ittiyseniz, şifreyi değiştirmelisiniz, herkese açık depodan kaldırmaya çalışmayın ve hiç kimsenin görmediğini ummayın.
Miles Rout

8
Bu çözüm tarihi korumaz.
Cœur

18
popdVe pushdkomut yapmak bu oldukça örtük ve daha zor yapmayı planladıklarını grok ...
jones77

133

Paul'ün yanıtı / ABC içeren yeni bir havuz oluşturur, ancak / ABC'yi / XYZ içinden kaldırmaz. Aşağıdaki komut / ABC / XYZ içinden kaldırılır:

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

Tabii ki, önce bir 'klon - no-hardlinks' deposunda test edin ve Paul'un listelediği reset, gc ve erik komutlarıyla izleyin.


53
bunu yap git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch ABC" --prune-empty HEADve çok daha hızlı olacak . index-filter dizin üzerinde çalışır; tree-filter ise her taahhüt için her şeyi kullanıma sunmalı ve hazırlamalıdır .
fmarc

51
bazı durumlarda depo tarihini karıştırmak XYZ aşırıya kaçar ... sadece basit bir "rm -rf ABC; git rm -r ABC; git taahhüt -m 'ABC'yi kendi repo'suna çıkardı'" çoğu insan için daha iyi çalışır.
Evgeny

2
Örneğin, iki dizini ayrıldıktan sonra kaldırmak için bir kereden fazla yaparsanız, muhtemelen bu komutta -f (force) kullanmak istersiniz. Aksi takdirde "Yeni bir yedek oluşturulamaz" iletisini alırsınız.
Brian Carlton

4
--index-filterYöntemi yapıyorsanız , bunu yapmak isteyebilirsiniz git rm -q -r -f, böylece her çağrı, sildiği her dosya için bir satır yazdırmaz.
Eric Naeseth

1
Paul'ün cevabını düzenlemeyi öneririm, sadece Paul'un çok kapsamlı olması nedeniyle.
Erik Aronesty

96

Eski geçmişi yeni depodan düzgün bir şekilde silmek için, filter-branchadımdan sonra biraz daha fazla iş yapmanız gerektiğini buldum .

  1. Klonu ve filtreyi yapın:

    git clone --no-hardlinks foo bar; cd bar
    git filter-branch --subdirectory-filter subdir/you/want
    
  2. Eski geçmişe ilişkin tüm referansları kaldırın. “Kökeni” klonunuzu takip ediyordu ve “orijinal” filtre dalı eski şeyleri kurtarıyor:

    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'in dokunmayacağı bir paket dosyasına sıkışmış olabilir. Yeni bir paket dosyası oluşturarak ve kullanılmayan nesneleri silerek parçalara ayırın:

    git repack -ad
    

Orada bu bir açıklama içinde filtre-şube için kılavuzun .


3
Sanırım bir şey git gc --aggressive --prune=nowhala eksik, değil mi?
Albert

1
@Albert Yeniden paketleme komutu bununla ilgilenir ve gevşek nesneler olmaz.
Josh Lee

Evet, git gc --aggressive --prune=nowyeni repo çok azaltılmış
Tomek Wyderka

Basit ve zarif. Teşekkürler!
Marco Pelegrini

40

Düzenleme: Bash betiği eklendi.

Burada verilen cevaplar benim için kısmen işe yaradı; Önbellekte bir sürü büyük dosya kaldı. Sonunda ne işe yaradı (freenode üzerinde #git içinde saatler 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 yaklaşık 100 MB idi. Bu onu 1.7 MB'a indirdi. Belki birine yardımcı olur :)


Aşağıdaki bash 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

26

Bu artık o kadar karmaşık değil, sadece istemediğiniz alt dizinleri kaldırmak için repo klonundaki git filter-branch komutunu kullanabilir ve yeni uzaktan kumandayı kullanabilirsiniz.

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

3
Bu bir cazibe gibi çalıştı. Yukarıdaki örnekteki YOUR_SUBDIR, TUTMAK istediğiniz alt dizindir, diğer her şey kaldırılacaktır
JT Taylor

1
Yorumlara dayalı güncellemeler.
jeremyjjbrown

2
Bu soruya cevap vermiyor. Dokümanlardan diyor The result will contain that directory (and only that) as its project root.ve gerçekten bu alacağınız şey, yani orijinal proje yapısı korunmuyor.
NicBright

2
@NicBright Sorunun ne olduğunu göstermek için XYZ ve ABC ile ilgili sorunuzu olduğu gibi açıklayabilir misiniz?
Adam

@jeremyjjbrown klonlanmış repo'yu yeniden kullanmak ve yeni bir repo kullanmamak mümkündür, yani sorum burada stackoverflow.com/questions/49269602/…
Qiulang

19

Güncelleme : git-subtree modülü o kadar kullanışlıdır ki git takımı onu çekirdeğe çekti ve yaptı git subtree. Buraya bakın: Alt dizini ayrı Git deposuna ayırın (taşıyın)

git-subtree bunun için yararlı olabilir

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

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


1
git-subtree artık katkıda bulunma ağacında olmasına rağmen Git'in bir parçasıdır, bu nedenle her zaman varsayılan olarak yüklenmez. Homebrew git formülü tarafından yüklendiğini biliyorum, ama man sayfası olmadan. apenwarr böylece versiyonunu eski olarak adlandırır.
echristopherson

19

İşte küçük bir değişikliktir CoolAJ86 'ın 'basit yol ™' cevabını bölmek için birden çok alt klasörleri (let diyelim sub1ve sub2yeni bir git deposunda içine).

Easy Way ™ (birden çok alt klasör)

  1. Eski repoyu hazırla

    pushd <big-repo>
    git filter-branch --tree-filter "mkdir <name-of-folder>; mv <sub1> <sub2> <name-of-folder>/" HEAD
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    popd
    

    Not: <name-of-folder> önde gelen veya arkadaki karakterleri İÇERMEMELİDİR. Örneğin, adlı klasör subprojectZORUNLU subprojectDEĞİL olarak geçirilmelidir ZORUNLU./subproject/

    Windows kullanıcıları için not: klasör derinliğiniz> 1 olduğunda, <name-of-folder>* nix tarzı klasör ayırıcıya (/) sahip olmalıdır. Örneğin, adlı klasör path1\path2\subprojectZORUNLU olarak iletilmelidir path1/path2/subproject. Üstelik mvkomut kullanmayın ama move.

    Son not: temel cevap ile benzersiz ve büyük fark " git filter-branch..." senaryosunun ikinci satırıdır

  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 repoyu Github'a veya herhangi bir yere bağlayın

    git remote add origin <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
  4. İsterseniz temizleme

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

    Not : Bu, depodaki tüm geçmiş referansları bırakır.Girişte bir şifre işlemekten endişe duyuyorsanız veya klasörünüzün dosya boyutunu küçültmeniz gerekiyorsa orijinal yanıttaki Ek'e bakın .git.


1
Bu benim için küçük bir değişiklikle işe yaradı. Benim Çünkü sub1ve sub2klasörleri başlangıç sürümü ile olmasaydı, benim değiştirmek zorunda --tree-filterşöyle senaryoyu: "mkdir <name-of-folder>; if [ -d sub1 ]; then mv <sub1> <name-of-folder>/; fi". İkinci filter-branchkomut için <sub1> yerine <sub2>, <name-of-folder> oluşturulmasını atladım ve -fsonra filter-branchvar olan bir yedeklemenin uyarısını geçersiz kılmak için dahil ettim .
pglezen

Git'teki geçmiş sırasında alt dizinlerden herhangi biri değiştiyse bu çalışmaz. Bu nasıl çözülebilir?
nietralar

@nietras rogerdpack'in cevabına bakınız. Bu diğer cevaplardaki tüm bilgileri okuduktan ve özledikten sonra bulmam biraz zaman aldı.
Adam

12

Orijinal soru XYZ / ABC / (* dosyaları) 'nin ABC / ABC / (* dosyaları) olmasını ister. Kendi kodum için kabul edilen cevabı uyguladıktan sonra, aslında XYZ / ABC / (* dosyaları) ABC / (* dosyaları) olarak değiştirdiğini fark ettim. Filtre kolu kılavuz sayfası bile,

Sonuç, bu dizini (ve yalnızca dizini) proje kökü olarak içerecektir . "

Başka bir deyişle, bir üst düzey klasörü "yukarı" yükseltir. Bu önemli bir ayrım çünkü örneğin geçmişimde üst düzey bir klasörü yeniden adlandırmıştım. Klasörleri bir düzey "yukarı" terfi ederek git, yeniden adlandırma yaptığım işlemde sürekliliği kaybeder.

Filtre kolundan sonra sürekliliği kaybettim

O zaman soruya cevabım, deponun 2 kopyasını oluşturmak ve her birinde saklamak istediğiniz klasörleri manuel olarak silmek. Man sayfası beni bununla destekliyor:

[...] basit bir tek işlem sorununuzu çözmek için yeterliyse [bu komut] kullanmaktan kaçının


1
Bu grafiğin stilini seviyorum. Hangi aleti kullandığınızı sorabilir miyim?
Slipp D.Thompson 30'13

3
Mac için Tower. Gerçekten beğendim. Kendisi için neredeyse Mac'e geçmeye değer.
MM.

2
Evet, benim durumumda olsa benim subfoldered targetdirolmuştu değiştirildi bir noktada ve git filter-branchtüm kaydedilmesini yeniden adlandırma önce yapılan silme, sadece bir gün denir! Şok edici, Git'in bu tür şeyleri takip etme konusunda ne kadar becerikli olduğunu ve hatta bireysel içerik parçalarının göçünü göz önünde bulundurarak!
Jay Allen

1
Oh, ayrıca, eğer biri kendini aynı teknede bulursa, işte benim kullandığım komut. Bunun git rmçoklu argümanlar gerektirdiğini unutmayın , bu yüzden her dosya / klasör için çalıştırmak için bir neden yoktur: BYEBYE="dir/subdir2 dir2 file1 dir/file2"; git filter-branch -f --index-filter "git rm -q -r -f --cached --ignore-unmatch $BYEBYE" --prune-empty -- --all
Jay Allen

7

Paul'ün cevabına eklemek için , nihayetinde alanı kurtarmak için, HEAD'i temiz bir depoya itmem gerektiğini ve bunun .git / objects / pack dizininin boyutunu azalttığını buldum.

yani

$ mkdir ... ABC.git
$ cd ... ABC.git
$ git init - çıplak

Gc budamadan sonra şunları da yapın:

$ git push ... ABC.git KAFA

Sonra yapabilirsin

$ git clone ... ABC.git

ve ABC / .git boyutu küçülür

Aslında, depoyu temizlemek için push ile zaman alan bazı adımlara (örn. Git gc) ihtiyaç duyulmaz, yani:

$ git clone - no-hardlinks / XYZ / ABC
$ git filter-branch - alt dizin filtresi ABC HEAD
$ git reset --hard
$ git push ... ABC.git KAFA

6

Şimdi doğru yol şudur:

git filter-branch --prune-empty --subdirectory-filter FOLDER_NAME [first_branch] [another_branch]

GitHub'ın bu tür vakalar hakkında küçük bir makalesi bile var .

Ancak orijinal deponuzu önce ayrı bir dizine kopyaladığınızdan emin olun (tüm dosyaları ve diğer dizinleri sileceği ve onlarla çalışmanız gerekebilir).

Yani algoritmanız:

  1. uzak deponuzu başka bir dizine kopyala
  2. git filter-branchbazı alt dizinin altındaki yalnızca soldaki dosyaları kullanarak yeni uzaktan kumandaya aktarın
  3. Bu alt dizini orijinal uzak deponuzdan kaldırma taahhüdü oluşturun

6

Buradaki cevapların çoğunun (hepsi?) Bir tür git filter-branch --subdirectory-filterve onun ilkine bağlı olduğu anlaşılıyor. Bu "çoğu kez" işe yarayabilir, ancak bazı durumlarda, örneğin klasörü yeniden adlandırdığınızda, örneğin:

 ABC/
    /move_this_dir # did some work here, then renamed it to

ABC/
    /move_this_dir_renamed

"Move_me_renamed" dosyasını ayıklamak için normal bir git filtresi stili yaparsanız, başlangıçta move_this_dir ( ref ) olduğunda arkadan meydana gelen dosya değişiklik geçmişini kaybedersiniz .

Bu nedenle, tüm değişiklik geçmişini gerçekten korumanın tek yolunun (sizinki böyle bir durumsa), özünde, depoyu kopyalamak (yeni bir repo oluşturmak, bunu başlangıç ​​noktası olacak şekilde ayarlamak), sonra diğer her şeyi nuke ve alt dizini üst öğe olarak şu şekilde yeniden adlandırın:

  1. Çok modüllü projeyi yerel olarak klonlayın
  2. Şubeler - orada ne olduğunu kontrol edin: git branch -a
  3. İş istasyonunuzda yerel bir kopya almak için bölmeye dahil edilecek her şubeye bir ödeme yapın: git checkout --track origin/branchABC
  4. Yeni bir dizinde kopya oluşturun: cp -r oldmultimod simple
  5. Yeni proje kopyasına gidin: cd simple
  6. Bu projede gerekli olmayan diğer modüllerden kurtulun:
  7. git rm otherModule1 other2 other3
  8. Artık sadece hedef modülün alt dizini kalıyor
  9. Modül kökünün yeni proje kökü olması için modül alt dizininden kurtulun
  10. git mv moduleSubdir1/* .
  11. Kalıntı alt dizinini silin: rmdir moduleSubdir1
  12. Herhangi bir noktada değişiklikleri kontrol edin: git status
  13. Yeni git repo'yu oluşturun ve bu projeyi projeye yönlendirmek için URL'sini kopyalayın:
  14. git remote set-url origin http://mygithost:8080/git/our-splitted-module-repo
  15. Bunun iyi olduğunu doğrulayın: git remote -v
  16. Değişiklikleri uzak repoya kadar itin: git push
  17. Uzak repoya git ve hepsinin orada olduğunu kontrol et
  18. Gerekli diğer dallar için tekrarlayın: git checkout branch2

Bu , modülü yeni bir repoya itmek için "Bir alt klasörü yeni bir depoya bölme" adım 6-11 arasındaki github belgesini izler .

Bu, .git klasörünüzde size herhangi bir alan kazandırmaz, ancak yeniden adlandırmalar arasında bile bu dosyalar için tüm değişiklik geçmişinizi korur. Ve eğer tarihin "çok" kaybı yoksa, buna değmeyebilir. Ama en azından daha eski taahhütleri kaybetmemeniz garantilidir!


1
Git samanlıkta iğne bulundu! Şimdi TÜM taahhüt geçmişimi saklayabilirim .
Adam

5

GitHub'ın alt klasörleri yeni bir depoya bölme kılavuzunu öneririm . Adımlar Paul'un cevabına benzer , ancak talimatlarını daha kolay anladım.

Talimatları, GitHub'da barındırılandan ziyade yerel bir depoya başvurmak üzere değiştirdim.


Bir alt klasörü yeni bir depoya bölme

  1. Git Bash'i açın.

  2. Geçerli çalışma dizinini, yeni deponuzu oluşturmak istediğiniz konuma değiştirin.

  3. Alt klasörü içeren havuzu klonlayın.

git clone OLD-REPOSITORY-FOLDER NEW-REPOSITORY-FOLDER
  1. Geçerli çalışma dizinini klonlanan deponuzla değiştirin.

cd REPOSITORY-NAME
  1. Alt klasörü depodaki dosyaların geri kalanından filtrelemek için aşağıdaki git filter-branchbilgileri sağlayarak çalıştırın :
    • FOLDER-NAME: Projenizde, ayrı bir depo oluşturmak istediğiniz klasör.
      • İpucu: Windows kullanıcıları /klasörleri sınırlamak için kullanmalıdır .
    • BRANCH-NAME: Mevcut projeniz için varsayılan şube, masterveya gh-pages.

git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME  BRANCH-NAME 
# Filter the specified branch in your directory and remove empty commits
Rewrite 48dc599c80e20527ed902928085e7861e6b3cbe6 (89/89)
Ref 'refs/heads/BRANCH-NAME' was rewritten

Güzel yazı, ama bağladığınız dokümanın ilk paragrafını görüyorum diyor If you create a new clone of the repository, you won't lose any of your Git history or changes when you split a folder into a separate repository.Yine de buradaki tüm cevaplar filter-branchve subtreesenaryodaki yorumlara göre bir alt dizinin yeniden adlandırıldığı her yerde tarih kaybına neden oluyor. Bunu ele almak için yapılabilecek bir şey var mı?
Adam

Önceki dizin yeniden adlandırma / taşıma işlemleri de dahil olmak üzere tüm işlemlerin korunmasına yönelik çözüm bulundu - bu rogerdpack'in bu soruya cevabı.
Adam

Tek sorun, artık klonlanmış
repoyu kullanamayacağım

5

( Belki?) 'İn git filter-branchdaha yeni bir sürümünü kullanarak çalışırken , bu yeni aracı git-filter-repo kullandığını söyler . Bu araç kesinlikle benim için işleri basitleştirdi.git2.22+

Filtre deposu ile filtreleme

XYZOrijinal sorudan repo oluşturma komutları :

# create local clone of original repo in directory XYZ
tmp $ git clone git@github.com:user/original.git XYZ

# switch to working in XYZ
tmp $ cd XYZ

# keep subdirectories XY1 and XY2 (dropping ABC)
XYZ $ git filter-repo --path XY1 --path XY2

# note: original remote origin was dropped
# (protecting against accidental pushes overwriting original repo data)

# XYZ $ ls -1
# XY1
# XY2

# XYZ $ git log --oneline
# last commit modifying ./XY1 or ./XY2
# first commit modifying ./XY1 or ./XY2

# point at new hosted, dedicated repo
XYZ $ git remote add origin git@github.com:user/XYZ.git

# push (and track) remote master
XYZ $ git push -u origin master

varsayımlar: * Uzak XYZ repo itmeden önce yeni ve boştu

Filtreleme ve taşıma

Benim durumumda, daha tutarlı bir yapı için birkaç dizin taşımak istedim. Başlangıçta, o basit filter-repokomutu takip git mv dir-to-renameettim, ama --path-renameseçeneği kullanarak biraz "daha iyi" bir geçmiş bulabileceğimi gördüm . 5 hours agoYeni repoda taşınan dosyalarda son değiştirileni görmek yerine, şimdi last year(RepoUU UI'de) orijinal repodaki değiştirilen zamanlarla eşleştiğini görüyorum .

Onun yerine...

git filter-repo --path XY1 --path XY2 --path inconsistent
git mv inconsistent XY3  # which updates last modification time

Sonunda koştum ...

git filter-repo --path XY1 --path XY2 --path inconsistent --path-rename inconsistent:XY3
Notlar:
  • Git Rev News blog yazısının , başka bir repo filtreleme aracı oluşturmanın ardındaki nedeni iyi açıkladığını düşündüm .
  • Başlangıçta orijinal depodaki hedef repo adıyla eşleşen bir alt dizin oluşturma ve ardından filtreleme (kullanarak git filter-repo --subdirectory-filter dir-matching-new-repo-name) yolunu denedim . Bu komut, bu alt dizini kopyalanan yerel repo'nun köküne doğru bir şekilde dönüştürdü, ancak alt dizini oluşturmak için yalnızca üç taahhüdün geçmişiyle de sonuçlandı. (Bunun --pathbirden çok kez belirtilebileceğini fark etmemiştim ; bu nedenle kaynak deposunda bir alt dizin yaratma ihtiyacını ortadan kaldırmıştım.) Birisi kaynak repoya bağlı kaldığımı fark ettiğim zamana kadar geçmişi, sadece komuttan git reset commit-before-subdir-move --hardsonra kullandım ve biraz değiştirilmiş yerel klon üzerinde çalışmasını sağlamak için komuta cloneekledim .--forcefilter-repo
git clone ...
git reset HEAD~7 --hard      # roll back before mistake
git filter-repo ... --force  # tell filter-repo the alterations are expected
  • Uzantı deseninin farkında olmadığımdan beri kurulumda sıkıştım git, ama sonunda git-filter- repo'yu klonladım ve symlinked $(git --exec-path):
ln -s ~/github/newren/git-filter-repo/git-filter-repo $(git --exec-path)

1
Yeni filter-repoaracı önerdiğim için seçildi (geçen ay stackoverflow.com/a/58251653/6309 adresinde sundum )
VonC

git-filter-repoBu noktada kullanmak kesinlikle tercih edilen yaklaşım olmalıdır. Daha çok, çok daha hızlı ve daha güvenlidir git-filter-branchve kişinin git tarihini yeniden yazarken karşılaşabileceği bir çok gotkaya karşı koruma sağlar. Umarım bu cevap biraz daha dikkat çeker, çünkü ele alınacak olan budur git-filter-repo.
Jeremy Caney

4

Ben tam olarak bu sorun vardı ama git filtre-şube dayalı tüm standart çözümler son derece yavaş. Küçük bir deponuz varsa, bu bir sorun olmayabilir, benim içindi. İlk adım olarak birincil deponun her filtrelemesi için dallar oluşturan libgit2'ye dayalı başka bir git filtreleme programı yazdım ve daha sonra bunları bir sonraki adım olarak depoları temizlemek için itti. Depomda (500Mb 100000 taahhüt) standart git filtre dalı yöntemleri günler sürdü. Programım aynı filtrelemeyi yapmak için dakikalar alıyor.

Git_filter'in muhteşem adı var ve burada yaşıyor:

https://github.com/slobobaby/git_filter

GitHub'da.

Umarım birisi için faydalıdır.


4

Etiketlerinizi ve dallarınızı korurken bir alt dizini kaldırmak için bu filtre komutunu kullanın:

git filter-branch --index-filter \
"git rm -r -f --cached --ignore-unmatch DIR" --prune-empty \
--tag-name-filter cat -- --all

Burada kedi nedir?
rogerdpack

4

Değeri için, GitHub'ı bir Windows makinesinde nasıl kullanacağınız aşağıda açıklanmıştır. Diyelim ki ikametinizde klonlanmış bir repo var C:\dir1. Dizin yapısı aşağıdaki gibidir: C:\dir1\dir2\dir3. dir3Dizin Yeni bir ayrı repo olmak istiyorum biridir.

Github:

  1. Yeni deponuzu oluşturun: MyTeam/mynewrepo

Bash İstemi:

  1. $ cd c:/Dir1
  2. $ git filter-branch --prune-empty --subdirectory-filter dir2/dir3 HEAD
    Döndü: Ref 'refs/heads/master' was rewritten(fyi: dir2 / dir3 büyük / küçük harfe duyarlıdır.)

  3. $ git remote add some_name git@github.com:MyTeam/mynewrepo.git
    git remote add origin etc. işe yaramadı, döndü " remote origin already exists"

  4. $ git push --progress some_name master


3

Yukarıda bahsettiğim gibi , taahhütlerin dir/subdir/targetdiryaklaşık% 95'ini (istendiği gibi) kaldırarak iyi işleyen bir çözüm gibi görünen ters çözümü (benim dokunmadan tüm taahhütleri silmek) kullanmak zorunda kaldım . Ancak geriye kalan iki küçük sorun var.

BİRİNCİ , filter-branchkodu tanıtan veya değiştiren komisyonları kaldırma işi yaptı, ancak görünüşe göre birleştirme komisyonları Gitiverse'deki istasyonunun altında.

Bu muhtemelen yaşayabileceğim kozmetik bir konudur (diyor ki ... gözlerin ters çevrilmiş şekilde yavaşça geri çekilmesi) .

İKİNCİ kalır birkaç onaylatabilirsiniz hemen hemen edilir TÜM çoğaltılamaz! Projenin neredeyse tüm tarihini kapsayan ikinci, yedekli bir zaman çizelgesi edindim. İlginç olan (aşağıdaki resimden de görebileceğiniz gibi), üç yerel şubemin hepsinin aynı zaman çizelgesinde olmaması (yani, neden var olduğunu ve sadece çöp toplanmadığını).

Hayal edebileceğim tek şey, silinen taahhütlerden birinin belki de filter-branch gerçekten silen tek birleştirme taahhüdü olması ve şimdi birleştirilmemiş her bir ipin kendi kopyalarını almasıyla paralel zaman çizelgesini oluşturmasıydı. ( Omuz silkme nerede TARDIS'in?) Ben istiyorum gerçi emin Ben, bu sorunu çözebilirsiniz güzelim gerçekten nasıl olduğunu anlamak için seviyorum.

Çılgın birleşme-O-RAMA durumunda, büyük olasılıkla tek başına bırakacağım çünkü taahhüt tarihime çok sıkı bir şekilde yerleşmişti - yaklaştığımda bana tehdit ediyor - aslında neden gibi görünmüyor kozmetik olmayan herhangi bir sorun ve çünkü Tower.app oldukça güzel.


3

Daha Kolay Yol

  1. yükleyin git splits. Onu jkeating çözümüne dayanan bir git uzantısı olarak yarattım .
  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 XY1 XY2

  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


"Kolay Yol" ile karşılaştırıldığında bu yöntemin bir avantajı, uzaktan kumandanın yeni repo için önceden ayarlanmış olmasıdır, böylece hemen bir alt ağaç ekleme yapabilirsiniz. Aslında bu şekilde benim için daha kolay görünüyor (onsuz bile git splits)
MM

Bu çözümü yayınladığı için AndrewD'den destek alın. Ben repo OSX ( github.com/ricardoespsanto/git-splits ) üzerinde çalışmasını sağlamak için başkalarına yararlı
olduysa çatalladım

2

Çöp toplamadan önce dosyaları gerçekten temizlemek için "git reflog expire --expire = now --all" gibi bir şeye ihtiyacınız olabilir. git filter-branch yalnızca geçmişteki başvuruları kaldırır, ancak verileri tutan reflog girdilerini kaldırmaz. Tabii ki, önce bunu test edin.

İlk koşulum biraz farklı olsa da, disk kullanımım bunu önemli ölçüde düşürdü. Belki --subdirectory-filter bu ihtiyacı ortadan kaldırır, ama bundan şüpheliyim.


2

Https://github.com/vangorra/git_split adresinden git_split projesine göz atın

Git dizinlerini kendi konumlarındaki kendi depolarına dönüştürün. Subtree komik iş yok. Bu komut dosyası git deponuzda var olan bir dizini alır ve bu dizini kendi bağımsız bir deposuna dönüştürür. Yol boyunca, sağladığınız dizinin tüm değişiklik geçmişini kopyalar.

./git_split.sh <src_repo> <src_branch> <relative_dir_path> <dest_repo>
        src_repo  - The source repo to pull from.
        src_branch - The branch of the source repo to pull from. (usually master)
        relative_dir_path   - Relative path of the directory in the source repo to split.
        dest_repo - The repo to push to.

1

Bunu gitconfig'nize koyun:

reduce-to-subfolder = !sh -c 'git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter cookbooks/unicorn HEAD && git reset --hard && git for-each-ref refs/original/ | cut -f 2 | xargs -n 1 git update-ref -d && git reflog expire --expire=now --all && git gc --aggressive --prune=now && git remote rm origin'

1

Eminim git subtree tüm iyi ve harika, ama gitmek istedim git yönetilen kod benim alt dizinleri tüm tutulma oldu. Yani egit kullanıyorsanız, acı vermek kolaydır. Taşımak istediğiniz projeyi alın ve ekip-> bağlantıyı kesin, sonra ekip-> yeni konumla paylaşın. Varsayılan olarak eski repo konumunu kullanmaya çalışmak gerekir, ancak mevcut kullanımı kullan seçiminin işaretini kaldırabilir ve taşımak için yeni yeri seçebilirsiniz. Selam olsun.


3
Alt ağacın "güzel ve harika" kısmı, altdizinin geçmişinin yolculuk için gelmesidir. Tarihe ihtiyacınız yoksa, acı verici kolay yönteminiz gitmenin yoludur.
pglezen

0

Https://help.github.com/enterprise/2.15/user/articles/splitting-a-subfolder-out-into-a-new-repository/ adresini kolayca deneyebilirsiniz.

Bu benim için çalıştı. Yukarıda verilen adımlarda karşılaştığım sorunlar

  1. Bu komuta olan ustagit filter-branch --prune-empty --subdirectory-filter FOLDER-NAME BRANCH-NAMEBRANCH-NAME

  2. koruma sorunu nedeniyle işlem yaparken son adım başarısız olursa - https://docs.gitlab.com/ee/user/project/protected_branches.html


0

Oldukça basit bir çözüm buldum, Fikir deposu kopyalamak ve sonra sadece gereksiz kısmı kaldırmak. Bu nasıl çalışır:

1) Bölmek istediğiniz bir havuzu klonlayın

git clone git@git.thehost.io:testrepo/test.git

2) git klasörüne git

cd test/

2) Gereksiz klasörleri kaldırın ve kaydedin

rm -r ABC/
git add .
enter code here
git commit -m 'Remove ABC'

3) BFG ile gereksiz klasör (ler) form geçmişini kaldırma

cd ..
java -jar bfg.jar --delete-folders "{ABC}" test
cd test/
git reflog expire --expire=now --all && git gc --prune=now --aggressive

klasörleri çarpma için virgül kullanabilirsiniz

java -jar bfg.jar --delete-folders "{ABC1,ABC2}" metric.git

4) Geçmişin yeni sildiğiniz dosyaları / klasörleri içermediğini kontrol edin

git log --diff-filter=D --summary | grep delete

5) Şimdi ABC olmadan temiz bir havuzunuz var, bu yüzden yeni kökene itin

remote add origin git@github.com:username/new_repo
git push -u origin master

Bu kadar. Başka bir depo almak için adımları tekrarlayabilirsiniz,

3. adımda XY1, XY2'yi kaldırın ve XYZ -> ABC'yi yeniden adlandırın


Neredeyse mükemmel ... ama şimdi boş olan tüm eski taahhütleri kaldırmak için "git filter-branch --prune-empty" u unuttun. Önce kökeni master itmek için yapmak!
ZettaCircl

Hatayı yaptıysanız ve eski boş taahhüdü kaldırdıktan sonra "tekrar" yeniden yayınlamak istiyorsanız, şunları gerçekleştirin: "git push -u origin master - force-with-
rent
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.