2009-05-28 8 views
105

Ho avviato un progetto alcuni mesi fa e ho archiviato tutto all'interno di una directory principale. Nella mia directory principale "Progetto" ci sono diverse sottodirectory che contengono cose diverse: Progetto/carta contiene un documento scritto in LaTeX Progetto/codice sorgente/RailsApp contiene la mia app per rotaie.Come estrarre una sottodirectory git e farne un sottomodulo?

"Project" è GITified e ci sono stati molti commit nella directory "paper" e "RailsApp". Ora, poiché mi piacerebbe utilizzare cruisecontrol.rb per la mia "RailsApp", mi chiedo se c'è un modo per creare un sottomodulo da "RailsApp" senza perdere la cronologia.

Qualche suggerimento?

+2

Anche una buona risposta: http://stackoverflow.com/questions/359424/detach-subdirectory-into-separate-git-repository –

risposta

3

Se si desidera trasferire un sottoinsieme di file in un nuovo repository ma mantenere la cronologia, si sta per finire con una cronologia completamente nuova. Il modo in cui funzionerebbe è fondamentalmente il seguente:

  1. Creare un nuovo repository.
  2. Per ogni revisione del vecchio repository, unire le modifiche al modulo nel nuovo repository. Questo creerà una "copia" della cronologia del tuo progetto esistente.

Dovrebbe essere un po 'semplice automatizzare questo se non ti dispiace scrivere una sceneggiatura piccola ma pelosa. Semplice, sì, ma anche doloroso. In passato la gente ha scritto la riscrittura in Git, puoi fare una ricerca per questo.

In alternativa: clonare il repository ed eliminare la carta nel clone, eliminare l'app nell'originale. Ci vorrebbe un minuto, è garantito che funzioni, e puoi tornare a cose più importanti che cercare di purificare la tua storia git. E non preoccuparti dello spazio del disco rigido occupato da copie ridondanti della cronologia.

35

Acquista git filter-branch.

Il numero Examples section della pagina man mostra come estrarre una sottodirectory nel proprio progetto mantenendo tutta la sua cronologia e la cronologia degli scarti di altri file/directory (proprio quello che stai cercando).

di riscrivere il repository di guardare come se foodir/ era stata la sua radice del progetto, ed eliminare tutti gli altri la storia:

git filter-branch --subdirectory-filter foodir -- --all 

questo modo è possibile, ad esempio, trasformare una sottodirectory libreria in un repository della propria .
Notare che separa le opzioni filter-branch dalle opzioni di revisione e lo --all per riscrivere tutti i rami e i tag.

+1

http://linux.die.net/man/1/git-filter -branch – schoetbi

+1

Questo ha funzionato bene per me. L'unico inconveniente che ho notato è stato il risultato di un singolo master branch con tutti i commit. – aceofspades

+0

@aceofspades: perché è un aspetto negativo? – naught101

13

Un modo per fare ciò è l'inverso: rimuovere tutto tranne il file che si desidera conservare.

Fondamentalmente, fare una copia del repository, quindi utilizzare git filter-branch per rimuovere tutto tranne il file/le cartelle che si desidera conservare.

Per esempio, ho un progetto da cui vorrei estrarre il file tvnamer.py ad un nuovo repository:

git filter-branch --tree-filter 'for f in *; do if [ $f != "tvnamer.py" ]; then rm -rf $f; fi; done' HEAD 

che utilizza git filter-branch --tree-filter a passare attraverso ogni commit, eseguire il comando e reimpegnarci il contenuto delle directory risultante . Questo è estremamente distruttivo (quindi dovresti farlo solo su una copia del tuo repository!), E può impiegare un po '(circa 1 minuto su un repository con 300 commit e circa 20 file)

Il comando precedente esegue solo il comando seguente shell script su ogni revisione, che si sarebbe necessario modificare naturalmente (per farla escludere vostro sub-directory invece di tvnamer.py):

for f in *; do 
    if [ $f != "tvnamer.py" ]; then 
     rm -rf $f; 
    fi; 
done 

Il più grande problema evidente è che lascia tutti messaggi di commit, anche se non sono collegati al file rimanente. Lo script git-remove-empty-commits, risolve questo ..

git filter-branch --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "[email protected]"; else git commit-tree "[email protected]"; fi' 

È necessario utilizzare la forza -f argomento run filter-branch di nuovo con qualcosa in refs/original/ (che fondamentalmente un backup)

Naturalmente questo non sarà mai perfetta, ad esempio, se i tuoi messaggi di commit menzionano altri file, ma è più vicino possibile a una corrente di git (per quanto ne sappia comunque).

Ancora, eseguirlo sempre su una copia del repository! - ma in sintesi, per rimuovere tutti i file, ma "thisismyfilename.txt":

git filter-branch --tree-filter 'for f in *; do if [ $f != "thisismyfilename.txt" ]; then rm -rf $f; fi; done' HEAD 
git filter-branch -f --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "[email protected]"; else git commit-tree "[email protected]"; fi' 
+4

'git filter-branch' ha (al giorno d'oggi?) Un'opzione built-in per rimuovere i commit vuoti, ovvero' --prune-empty'. Una guida migliore per 'git filter-branch' è la risposta a questa domanda: http://stackoverflow.com/questions/359424/detach-subdirectory-into-separate-git-repository – Blaisorblade

108

Al giorno d'oggi c'è un modo molto più semplice per farlo che manualmente usando git filter-branch: git subtree

installazione

git clone https://github.com/apenwarr/git-subtree.git 

cd git-subtree 
sudo rsync -a ./git-subtree.sh /usr/local/bin/git-subtree 

Oppure, se si desidera che le pagine di manuale e tutti

make doc 
make install 

Uso

Split una più grande in blocchi più piccoli:

# Go into the project root 
cd ~/my-project 

# Create a branch which only contains commits for the children of 'foo' 
git subtree split --prefix=foo --branch=foo-only 

# Remove 'foo' from the project 
git rm -rf ./foo 

# Create a git repo for 'foo' (assuming we already created it on github) 
mkdir foo 
pushd foo 
git init 
git remote add origin [email protected]:my-user/new-project.git 
git pull ../ foo-only 
git push origin -u master 
popd 

# Add 'foo' as a git submodule to `my-project` 
git submodule add [email protected]:my-user/new-project.git foo 

Per la documentazione dettagliata (man page), si prega di leggere git-subtree.txt.

+9

git sottotree rocks! –

+3

Ma non è il punto di git-sottostruttura di evitare l'uso di sottomoduli?Voglio dire, tu sei davvero l'autore del git-subtree (a meno che non ci sia una collisione di nickname), ma sembra che la struttura di git-subtree sia cambiata, anche se il comando che mostri sembra ancora valido. Sto ottenendo questo giusto? – Blaisorblade

+15

git-subtree è ora parte di git (se installi contrib) a partire dal 1.7.11 – Jeremy