Vraag Ontkoppel (verplaats) submap naar afzonderlijke Git-repository


ik heb een Git repository die een aantal subdirectories bevat. Nu heb ik geconstateerd dat een van de subdirectories geen verband houdt met de andere en moet worden losgemaakt in een afzonderlijke repository.

Hoe kan ik dit doen terwijl ik de geschiedenis van de bestanden in de subdirectory bewaar?

Ik denk dat ik een kloon kon maken en de ongewenste delen van elke kloon kon verwijderen, maar ik veronderstel dat dit me de volledige boom zou opleveren bij het uitchecken van een oudere revisie enz. Dit zou acceptabel kunnen zijn, maar ik zou liever kunnen doen alsof de twee repositories hebben geen gedeelde geschiedenis.

Om het duidelijk te maken, heb ik de volgende structuur:

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

Maar ik zou dit in plaats daarvan graag willen:

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

1595
2017-12-11 13:57


oorsprong


antwoorden:


Bijwerken: Dit proces is zo gewoon dat het git-team het veel eenvoudiger maakte met een nieuwe tool, git subtree. Kijk hier: Ontkoppel (verplaats) submap naar afzonderlijke Git-repository


U wilt uw repository klonen en vervolgens gebruiken git filter-branch om alles te markeren, behalve de subdirectory die u in uw nieuwe repo wilt hebben om vuilnis verzameld te worden.

  1. Om uw lokale repository te klonen:

    git clone /XYZ /ABC
    

    (Opmerking: de repository wordt gekloond met behulp van hardlinks, maar dat is geen probleem, aangezien de hard gelinkte bestanden niet op zichzelf worden aangepast - nieuwe worden gemaakt.)

  2. Laten we nu de interessante vertakkingen behouden die we ook willen herschrijven, en dan de oorsprong verwijderen om te voorkomen dat we daarheen gaan en ervoor zorgen dat er niet naar de oude commits wordt verwezen door de oorsprong:

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

    of voor alle externe takken:

    cd /ABC
    for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
    git remote rm origin
    
  3. Nu wilt u wellicht ook tags verwijderen die geen relatie hebben met het subproject; je kunt dat ook later doen, maar je moet misschien je repo opnieuw snoeien. Ik deed dit niet en kreeg een WARNING: Ref 'refs/tags/v0.1' is unchanged voor alle tags (omdat ze allemaal niet gerelateerd waren aan het subproject); bovendien zal na het verwijderen van dergelijke tags meer ruimte worden teruggewonnen. Blijkbaar git filter-branch moet andere tags kunnen herschrijven, maar ik kon dit niet verifiëren. Als u alle tags wilt verwijderen, gebruikt u git tag -l | xargs git tag -d.

  4. Gebruik vervolgens filter-branch en reset om de andere bestanden uit te sluiten, zodat ze kunnen worden gesnoeid. Laten we ook toevoegen --tag-name-filter cat --prune-empty om lege commits te verwijderen en om tags te herschrijven (merk op dat deze hun handtekening moeten verwijderen):

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

    of als alternatief om alleen de HEAD branch te herschrijven en tags en andere branches te negeren:

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
  5. Verwijder vervolgens de reserve-reflogs zodat de ruimte echt kan worden teruggevorderd (hoewel de bewerking nu destructief is)

    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
    

    en nu heb je een lokale git-repository van de ABC-subdirectory met al zijn geschiedenis bewaard.

Opmerking: voor de meeste toepassingen, git filter-branch zou inderdaad de toegevoegde parameter moeten hebben -- --all. Ja dat is echt --ruimte--  all. Dit moeten de laatste parameters zijn voor de opdracht. Zoals Matli ontdekte, houdt dit de projecttakken en tags bij de nieuwe repo.

Bewerken: verschillende suggesties uit onderstaande opmerkingen zijn verwerkt om er bijvoorbeeld zeker van te zijn dat de repository daadwerkelijk is gekrompen (wat niet altijd het geval was).


1155
2017-07-25 17:10



De Easy Way ™

Het blijkt dat dit zo'n algemene en nuttige praktijk is dat de opgevers van git het heel gemakkelijk hebben gemaakt, maar je moet een nieuwere versie van git hebben (> = 1.7.11 mei 2012). Zie de bijlage voor het installeren van de nieuwste git. Ook is er een real-world voorbeeld in de walkthrough hieronder.

  1. Bereid de oude repo voor

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

    Notitie:  <name-of-folder> mag GEEN voorloop- of volgletter bevatten. Bijvoorbeeld de map met de naam subproject MOET worden doorgegeven als subproject, NIET ./subproject/

    Opmerking voor Windows-gebruikers: wanneer uw mapdiepte> 1 is, <name-of-folder> moet een mapscheidingsteken * nix-stijl hebben (/). Bijvoorbeeld de map met de naam path1\path2\subproject MOET worden doorgegeven als path1/path2/subproject

  2. Maak de nieuwe repo

    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Koppel de nieuwe repo aan Github of waar dan ook

    git remote add origin <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
  4. Opruimen, indien gewenst

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

    Notitie: Dit laat alle historische referenties in de repository. Zie de Bijlage hieronder als je je echt zorgen maakt over het hebben van een wachtwoord of als je de bestandsgrootte van je wachtwoord wilt verkleinen .git map.

...

walkthrough

Dit zijn de dezelfde stappen als hierboven, maar volgens mijn exacte stappen voor mijn repository in plaats van te gebruiken <meta-named-things>.

Hier is een project dat ik heb voor het implementeren van JavaScript-browsermodules in een knooppunt:

tree ~/Code/node-browser-compat

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

Ik wil een enkele map splitsen, btoa, in een aparte git repository

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

Ik heb nu een nieuwe tak, btoa-only, dat heeft alleen commits voor btoa en ik wil een nieuwe repository maken.

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

Vervolgens maak ik een nieuwe repo op Github of bitbucket, of wat dan ook en voeg het toe origin (tussen haakjes, "oorsprong" is slechts een conventie, geen onderdeel van het commando - je zou het "remote-server" kunnen noemen of wat je maar wilt)

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

Vrolijke dag!

Notitie: Als u een repo heeft gemaakt met een README.md, .gitignore en LICENSE, je moet eerst trekken:

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

Ten slotte wil ik de map van de grotere repo verwijderen

git rm -rf btoa

...

Bijlage

Laatste git op OS X

Om de nieuwste versie van git te krijgen:

brew install git

Om brouwsel voor OS X te krijgen:

http://brew.sh

Laatste git op Ubuntu

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

Als dat niet werkt (je hebt een hele oude versie van ubuntu), probeer dan

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

Als dat nog steeds niet werkt, probeer het dan

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

Bedankt aan rui.araujo van de commentaren.

je geschiedenis wissen

Standaard verwijdert het verwijderen van bestanden van git ze niet echt van git, het houdt gewoon in dat ze er niet meer zijn. Als u de historische referenties daadwerkelijk wilt verwijderen (dat wil zeggen dat u een wachtwoord hebt toegewezen), moet u dit doen:

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

Daarna kun je controleren of je bestand of map helemaal niet meer in de geschiedenis van git voorkomt

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

Jij echter kan geen duwen naar github "duwen" en dergelijke. Als je het probeert, krijg je een foutmelding en zul je het moeten doen git pull voordat je kunt git push - en dan ben je terug om alles in je geschiedenis te hebben.

Dus als u geschiedenis wilt verwijderen van de "oorsprong" - wat betekent dat u de geschiedenis wilt verwijderen van github, bitbucket, enz. - moet u de repo verwijderen en een gesnoeide kopie van de repo opnieuw indrukken. Maar wacht - er is meer! - Als u zich echt zorgen maakt over het verwijderen van een wachtwoord of iets dergelijks, moet u de back-up snoeien (zie hieronder).

making .git kleiner

De eerder genoemde opdracht voor het verwijderen van geschiedenis laat nog steeds een aantal back-upbestanden achter - omdat git al te vriendelijk is om u te helpen uw repo niet per ongeluk te verpesten. Uiteindelijk worden er tijdens de dagen en maanden losse bestanden verwijderd, maar deze blijven daar een tijdje liggen, voor het geval je je realiseert dat je per ongeluk iets hebt verwijderd dat je niet wilde.

Dus als je het echt wilt Leeg de prullenbak naar verklein de kloongrootte van een repo moet je onmiddellijk al deze rare dingen doen:

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

Dat gezegd hebbende, raad ik aan om deze stappen niet uit te voeren, tenzij je weet dat je dat moet doen - voor het geval je de verkeerde submap hebt gesnoeid, weet je dat? De back-upbestanden mogen niet worden gekloond wanneer u op de repo drukt, ze staan ​​gewoon in uw lokale kopie.

Credit


1122
2018-06-05 13:15



Het antwoord van Paul maakt een nieuwe repository aan met / ABC, maar verwijdert niet / ABC van binnen / XYZ. Met de volgende opdracht wordt / ABC van binnenuit / XYZ verwijderd:

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

Test het natuurlijk eerst in een 'clone - no-hardlinks'-repository en volg het met de reset-, gc- en prune-opdrachten die Paul opslaat.


131
2017-10-19 21:10



Ik heb geconstateerd dat je, om de oude geschiedenis correct uit de nieuwe repository te verwijderen, na de filter-branch stap.

  1. Voer de kloon en het filter uit:

    git clone --no-hardlinks foo bar; cd bar
    git filter-branch --subdirectory-filter subdir/you/want
    
  2. Verwijder elke verwijzing naar de oude geschiedenis. "Origin" hield je kloon bij, en "original" is waar filter branch de oude dingen opslaat:

    git remote rm origin
    git update-ref -d refs/original/refs/heads/master
    git reflog expire --expire=now --all
    
  3. Zelfs nu kan uw geschiedenis vastzitten in een pakketbestand dat fsck niet zal aanraken. Scheur het aan flarden, maak een nieuw pakketbestand en verwijder de ongebruikte objecten:

    git repack -ad
    

Er bestaat een verklaring hiervoor in de handleiding voor filter-aftakking.


94
2018-06-09 15:41



Bewerken: Bash script toegevoegd.

De antwoorden die hier werden gegeven, werkten slechts gedeeltelijk voor mij; Veel grote bestanden bleven in de cache. Wat uiteindelijk heeft gewerkt (na uren in #git on freenode):

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

Met de vorige oplossingen was de opslagplaatsgrootte ongeveer 100 MB. Deze bracht het terug naar 1,7 MB. Misschien helpt het iemand :)


Het volgende bash-script automatiseert de taak:

!/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



Dit is niet langer zo complex dat je gewoon de git filter-branch Geef een opdracht aan een kloon van je repo om de submappen die je niet nodig hebt op te ruimen en druk vervolgens op de nieuwe afstandsbediening.

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



Bijwerken: De git-subtree module was zo handig dat het git-team het in de kern heeft getrokken en het heeft gehaald git subtree. Kijk hier: Ontkoppel (verplaats) submap naar afzonderlijke Git-repository

git-subtree kan hiervoor nuttig zijn

http://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt (verouderd)

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


19
2017-08-06 15:26



Hier is een kleine aanpassing aan CoolAJ86's "The Easy Way ™" antwoord om te splitsen meerdere submappen (laten we zeggen sub1en sub2) in een nieuwe git-repository.

De Easy Way ™ (meerdere submappen)

  1. Bereid de oude repo voor

    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
    

    Notitie:  <name-of-folder> mag GEEN voorloop- of volgletter bevatten. Bijvoorbeeld de map met de naam subproject MOET worden doorgegeven als subproject, NIET ./subproject/

    Opmerking voor Windows-gebruikers: wanneer uw mapdiepte> 1 is, <name-of-folder> moet een mapscheidingsteken * nix-stijl hebben (/). Bijvoorbeeld de map met de naam path1\path2\subproject MOET worden doorgegeven als path1/path2/subproject. Gebruik bovendien niet mvcommando maar move.

    Laatste opmerking: het unieke en grote verschil met het basisantwoord is de tweede regel van het script "git filter-branch..."

  2. Maak de nieuwe repo

    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Koppel de nieuwe repo aan Github of waar dan ook

    git remote add origin <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
  4. Opruimen, indien gewenst

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

    Notitie: Dit laat alle historische referenties in de repository. Zie de Bijlage in het oorspronkelijke antwoord als u zich echt zorgen maakt over het hebben van een wachtwoord of als u de bestandsgrootte van uw wachtwoord wilt verkleinen .git map.


13
2018-04-17 05:12



De oorspronkelijke vraag wil dat XYZ / ABC / (* bestanden) ABC / ABC / (* bestanden) worden. Na het implementeren van het geaccepteerde antwoord voor mijn eigen code, merkte ik dat het XYZ / ABC / (* bestanden) feitelijk in ABC / (* bestanden) verandert. De man-pagina van de filtertak zegt zelfs:

Het resultaat zal die map bevatten (en alleen dat) als de project root."

Met andere woorden, het promoot de map op het hoogste niveau één niveau omhoog. Dat is een belangrijk onderscheid, omdat ik in mijn geschiedenis bijvoorbeeld een map op het hoogste niveau had hernoemd. Door mappen één niveau hoger te "verhogen", verliest git continuïteit bij de commit waar ik de naam heb gewijzigd.

I lost contiuity after filter-branch

Mijn antwoord op de vraag is dan om 2 exemplaren van de repository te maken en handmatig de mappen te verwijderen die je in elke repository wilt bewaren. De man-pagina ondersteunt me hierbij:

[...] vermijd het gebruik van [deze opdracht] als een eenvoudige enkele commit voldoende zou zijn om uw probleem op te lossen


11
2017-07-25 10:01