Domanda Scollega (sposta) la sottodirectory in un repository Git separato


Ho un Idiota repository che contiene un numero di sottodirectory. Ora ho trovato che una delle sottodirectory non è correlata all'altra e deve essere scollegata in un repository separato.

Come posso fare questo mantenendo la cronologia dei file all'interno della sottodirectory?

Immagino che potrei fare un clone e rimuovere le parti indesiderate di ogni clone, ma suppongo che questo mi darebbe l'albero completo quando verificherei una revisione più vecchia ecc. Questo potrebbe essere accettabile, ma preferirei poter fingere che il due repository non hanno una cronologia condivisa.

Giusto per chiarire, ho la seguente struttura:

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

Ma mi piacerebbe questo invece:

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

1595
2017-12-11 13:57


origine


risposte:


Aggiornare: Questo processo è così comune che il team git ha reso molto più semplice con un nuovo strumento, git subtree. Vedere qui: Scollega (sposta) la sottodirectory in un repository Git separato


Vuoi clonare il tuo repository e poi usarlo git filter-branch per contrassegnare tutto tranne la sottodirectory che si desidera nel nuovo repository da raccogliere.

  1. Per clonare il tuo repository locale:

    git clone /XYZ /ABC
    

    (Nota: il repository sarà clonato usando i collegamenti fisici, ma questo non è un problema dato che i file hard-linked non saranno modificati di per sé - ne verranno creati di nuovi.)

  2. Ora, preserviamo i rami interessanti che vogliamo riscrivere, quindi rimuoviamo l'origine per evitare di spingerli e per assicurarci che i vecchi commit non siano referenziati dall'origine:

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

    o per tutte le filiali remote:

    cd /ABC
    for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
    git remote rm origin
    
  3. Ora potresti voler rimuovere anche i tag che non hanno alcuna relazione con il sottoprogetto; puoi farlo anche dopo, ma potresti aver bisogno di potare di nuovo il tuo repo. Non l'ho fatto e ho ottenuto un WARNING: Ref 'refs/tags/v0.1' is unchanged per tutti i tag (poiché erano tutti non correlati al sottoprogetto); inoltre, dopo aver rimosso tali tag, verrà recuperato più spazio. Apparentemente git filter-branch dovrebbe essere in grado di riscrivere altri tag, ma non ho potuto verificarlo. Se si desidera rimuovere tutti i tag, utilizzare git tag -l | xargs git tag -d.

  4. Quindi utilizzare filter-branch e resettare per escludere gli altri file, in modo che possano essere eliminati. Aggiungiamo anche --tag-name-filter cat --prune-empty per rimuovere i commit vuoti e per riscrivere i tag (nota che questo dovrà togliere la firma):

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

    o in alternativa, per riscrivere solo il ramo HEAD e ignorare i tag e altri rami:

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
  5. Quindi elimina i diagrammi di backup in modo che lo spazio possa essere effettivamente recuperato (anche se ora l'operazione è distruttiva)

    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
    

    e ora hai un repository git locale della sottodirectory ABC con tutta la sua storia preservata.

Nota: per la maggior parte degli usi, git filter-branch dovrebbe infatti avere il parametro aggiunto -- --all. Sì, questo è davvero --spazio--  all. Questo deve essere l'ultimo parametro per il comando. Come ha scoperto Matli, questo mantiene i rami e le etichette del progetto inclusi nel nuovo repository.

Modifica: sono stati incorporati vari suggerimenti dai commenti riportati di seguito per accertare, ad esempio, che il repository sia effettivamente ridotto (cosa che non era sempre avvenuto prima).


1155
2017-07-25 17:10



The Easy Way ™

Si scopre che questa pratica è così comune e utile che i signori di git lo hanno reso davvero facile, ma devi avere una versione più recente di git (> = 1.7.11 maggio 2012). Vedere il appendice per come installare l'ultimo git. Inoltre, c'è un esempio del mondo reale nel Procedura dettagliata sotto.

  1. Prepara il vecchio repository

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

    Nota:  <name-of-folder> NON deve contenere caratteri iniziali o finali. Ad esempio, la cartella denominata subproject DEVE essere passato come subproject, NON ./subproject/

    Nota per gli utenti di Windows: quando la profondità della cartella è> 1, <name-of-folder> deve avere * nix style folder separator (/). Ad esempio, la cartella denominata path1\path2\subproject DEVE essere passato come path1/path2/subproject

  2. Crea il nuovo repository

    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Collega il nuovo repository a Github o ovunque

    git remote add origin <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
  4. Pulire, se desiderato

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

    Nota: Questo lascia tutti i riferimenti storici nel repository. Vedi Appendice di seguito se sei effettivamente preoccupato di aver commesso una password o hai bisogno di ridurre la dimensione del tuo file .git cartella.

...

Procedura dettagliata

Queste sono le stessi passi come sopra, ma seguendo i miei passi esatti per il mio repository invece di usare <meta-named-things>.

Ecco un progetto che ho per l'implementazione dei moduli del browser JavaScript nel nodo:

tree ~/Code/node-browser-compat

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

Voglio dividere una singola cartella, btoa, in un repository git separato

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

Ora ho una nuova filiale, btoa-only, che ha solo impegni per btoa e voglio creare un nuovo repository.

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

Successivamente creo un nuovo repository su Github o bitbucket, o qualsiasi altra cosa e aggiungilo è il origin (btw, "origin" è solo una convenzione, non parte del comando - potresti chiamarlo "server remoto" o qualsiasi cosa tu voglia)

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

Giorno felice!

Nota: Se hai creato un repo con a README.md, .gitignore e LICENSE, dovrai prima tirare:

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

Infine, voglio rimuovere la cartella dal repository più grande

git rm -rf btoa

...

Appendice

Ultimo git su OS X

Per ottenere l'ultima versione di git:

brew install git

Per preparare la preparazione per OS X:

http://brew.sh

Ultimo git su Ubuntu

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

Se questo non funziona (hai una versione molto vecchia di Ubuntu), prova

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

Se ancora non funziona, prova

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

Grazie a rui.araujo dai commenti.

cancellare la tua storia

Per impostazione predefinita, la rimozione dei file da git non li rimuove realmente da git, ma impegna semplicemente che non ci sono più. Se vuoi rimuovere effettivamente i riferimenti storici (ad esempio hai una password impegnata), devi fare questo:

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

Dopodiché puoi controllare che il tuo file o la tua cartella non vengano più mostrati nella cronologia git

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

Tuttavia, tu impossibile "push" cancella su github e simili. Se provi, riceverai un errore e dovrai farlo git pull prima che tu possa git push - e poi sei tornato ad avere tutto nella tua storia.

Quindi, se si desidera eliminare la cronologia dall'origine, ovvero eliminarla da github, bitbucket, ecc., Sarà necessario eliminare il repository e reinserire una copia eliminata del repository. Ma aspetta - C'è più! - Se sei davvero preoccupato di eliminare una password o qualcosa del genere, devi eseguire il taglio del backup (vedi sotto).

fabbricazione .git più piccola

Il comando di cancellazione della storia di cui sopra lascia ancora un sacco di file di backup - perché git è fin troppo gentile per aiutarti a non rovinare il tuo repository per sbaglio. Alla fine cancellerà i file orfani nel corso dei giorni e dei mesi, ma li lascerà lì per un po 'nel caso in cui ti accorgi di aver cancellato per errore qualcosa che non volevi.

Quindi se lo vuoi davvero svuota il cestino a ridurre la dimensione del clone di un repository immediatamente devi fare tutta questa roba davvero strana:

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

Detto questo, raccomanderei di non eseguire questi passaggi a meno che non si sappia che è necessario - nel caso in cui si sia potata la sottodirectory sbagliata, sai? I file di backup non dovrebbero essere clonati quando si preme il repository, saranno solo nella tua copia locale.

Credito


1122
2018-06-05 13:15



La risposta di Paolo crea un nuovo repository contenente / ABC, ma non rimuove / ABC da / XYZ. Il seguente comando rimuoverà / ABC da / XYZ:

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

Naturalmente, testalo prima in un repository 'clone --no-hardlinks' e seguilo con i comandi di reset, gc e prune di Paul lists.


131
2017-10-19 21:10



Ho scoperto che per eliminare correttamente la vecchia cronologia dal nuovo repository, devi fare un po 'più di lavoro dopo il filter-branch passo.

  1. Fai il clone e il filtro:

    git clone --no-hardlinks foo bar; cd bar
    git filter-branch --subdirectory-filter subdir/you/want
    
  2. Rimuovi ogni riferimento alla vecchia storia. "Origin" stava tenendo traccia del tuo clone e "original" è il punto in cui il branch-branch salva le vecchie cose:

    git remote rm origin
    git update-ref -d refs/original/refs/heads/master
    git reflog expire --expire=now --all
    
  3. Anche ora, la tua cronologia potrebbe essere bloccata in un pacchetto che fsck non toccherà. Strappalo a brandelli, creando un nuovo packfile ed eliminando gli oggetti non utilizzati:

    git repack -ad
    

C'è una spiegazione di questo nel manuale per filtro-ramo.


94
2018-06-09 15:41



Modifica: aggiunto script Bash.

Le risposte date qui hanno funzionato solo parzialmente per me; Un sacco di grandi file sono rimasti nella cache. Cosa ha funzionato alla fine (dopo ore su #git su 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

Con le soluzioni precedenti, la dimensione del repository era di circa 100 MB. Questo ha portato a 1,7 MB. Forse aiuta qualcuno :)


Il seguente script bash automatizza l'attività:

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



Non è più così complesso che puoi semplicemente usare il git filter-branch comando su un clone di te repo per selezionare le sottodirectory che non vuoi e poi spingere sul nuovo telecomando.

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



Aggiornare: Il modulo git-subtree era così utile che il team git lo ha inserito nel core e realizzato git subtree. Vedere qui: Scollega (sposta) la sottodirectory in un repository Git separato

git-sottostruttura può essere utile per questo

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

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


19
2017-08-06 15:26



Ecco una piccola modifica a CoolAJ86'S Risposta "The Easy Way ™" per dividere più sottocartelle (diciamo sub1e sub2) in un nuovo repository git.

The Easy Way ™ (più sottocartelle)

  1. Prepara il vecchio repository

    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
    

    Nota:  <name-of-folder> NON deve contenere caratteri iniziali o finali. Ad esempio, la cartella denominata subproject DEVE essere passato come subproject, NON ./subproject/

    Nota per gli utenti di Windows: quando la profondità della cartella è> 1, <name-of-folder> deve avere * nix style folder separator (/). Ad esempio, la cartella denominata path1\path2\subproject DEVE essere passato come path1/path2/subproject. Inoltre non usare mvcomando ma move.

    Nota finale: la differenza unica e grande con la risposta di base è la seconda riga della sceneggiatura "git filter-branch..."

  2. Crea il nuovo repository

    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Collega il nuovo repository a Github o ovunque

    git remote add origin <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
  4. Pulire, se desiderato

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

    Nota: Questo lascia tutti i riferimenti storici nel repository. Vedi Appendice nella risposta originale se sei effettivamente preoccupato di aver commesso una password o hai bisogno di ridurre la dimensione del file del tuo .git cartella.


13
2018-04-17 05:12



La domanda originale vuole che i file XYZ / ABC / (*) diventino ABC / ABC / (file *). Dopo aver implementato la risposta accettata per il mio codice, ho notato che in realtà cambia XYZ / ABC / (file *) in ABC / (* file). La pagina man di filtro-ramo dice anche,

Il risultato conterrà quella directory (e solo quella) come la radice del progetto".

In altre parole, promuove la cartella di livello superiore "su" di un livello. Questa è una distinzione importante perché, ad esempio, nella mia storia ho rinominato una cartella di livello superiore. Promuovendo le cartelle "su" di un livello, git perde continuità nel commit in cui ho fatto il cambio di nome.

I lost contiuity after filter-branch

La mia risposta alla domanda è quindi di creare 2 copie del repository e cancellare manualmente le cartelle che si desidera conservare ciascuna. La pagina man mi supporta in questo modo:

[...] evitare di usare [questo comando] se un singolo commit semplice sarebbe sufficiente per risolvere il tuo problema


11
2017-07-25 10:01



Da aggiungere a La risposta di Paolo, Ho scoperto che per recuperare definitivamente lo spazio, devo spingere HEAD in un repository pulito e che riduce le dimensioni della directory .git / objects / pack.

cioè

$ mkdir ... ABC.git
$ cd ... ABC.git
$ git init --bare

Dopo la gc prune, anche fare:

$ git push ... ABC.git HEAD

Allora puoi farlo

$ git clone ... ABC.git

e la dimensione di ABC / .git è ridotta

In realtà, alcuni passaggi che richiedono molto tempo (ad es. Git gc) non sono necessari con il repository push to clean, cioè:

$ git clone --no-hardlinks / XYZ / ABC
$ git filter-branch --subdirectory-filter ABC HEAD
$ git reset --hard
$ git push ... ABC.git HEAD

7
2017-11-12 13:22



Modo corretto ora è il seguente:

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

GitHub ora ha anche piccolo articolo su questi casi.

Ma assicurati di clonare il repository originale per separare prima la directory (in quanto eliminerebbe tutti i file e altre directory e probabilmente dovrai lavorare con loro).

Quindi il tuo algoritmo dovrebbe essere:

  1. clonare il repository remoto in un'altra directory
  2. utilizzando git filter-branch lasciato solo i file sotto qualche sottodirectory, spingere sul nuovo telecomando
  3. creare commit per rimuovere questa sottodirectory dal repository remoto originale

5
2017-09-19 18:46