Come estrarre una sottodirectory git e farne un sottomodulo?


119

Ho iniziato un progetto alcuni mesi fa e ho archiviato tutto in una directory principale. Nella mia directory principale "Project" ci sono diverse sottodirectory contenenti cose diverse: Project / paper contiene un documento scritto in LaTeX Project / sourcecode / RailsApp contiene la mia app rails.

"Project" è GITificato e ci sono stati molti commit sia nella directory "paper" che nella directory "RailsApp". Ora, poiché vorrei utilizzare cruisecontrol.rb per la mia "RailsApp", mi chiedo se esista un modo per creare un sottomodulo da "RailsApp" senza perdere la cronologia.


2
Anche una risposta molto buona: stackoverflow.com/questions/359424/...
Rehno Lindeque

Risposte:


122

Al giorno d'oggi c'è un modo molto più semplice per farlo rispetto all'utilizzo manuale di git filter-branch: git subtree

Installazione

NOTA git-subtree ora fa parte di git(se installi contrib) dalla 1.7.11, quindi potresti già averlo installato. Puoi controllare eseguendo git subtree.


Per installare git-subtree dal sorgente (per versioni precedenti di git):

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

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

O se vuoi le pagine man e tutto il resto

make doc
make install

uso

Dividi un più grande in pezzi 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 git@github.com: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 git@github.com:my-user/new-project.git foo

Per una documentazione dettagliata (pagina man), leggere git-subtree.txt.


10
git subtree rocks!
Simon Woodside,

3
Ma il punto di git-subtree non è evitare di usare i sottomoduli? Voglio dire, sei davvero l'autore di git-subtree (a meno che non ci sia una collisione di nickname), ma sembra che git-subtree sia cambiato, anche se il comando che mostri sembra ancora valido. Sto capendo bene?
Blaisorblade

17
git-subtree ora fa parte di git (se installi contrib) a partire dal 1.7.11
Jeremy,

8
Well git rm -rf ./foorimuove foodalla cronologia completa HEADma non filtra my-project. Quindi, git submodule add git@github.com:my-user/new-project.git foocrea solo fooun sottomodulo a partire da HEAD. Sotto questo aspetto, lo scripting filter-branchè superiore in quanto consente di ottenere "fai come se subdir fosse un sottomodulo fin dall'inizio"
Gregory Pakosz

grazie per questo - i documenti della sottostruttura git sono solo un po 'sconcertanti, e questa è (per me) la cosa più ovviamente utile che volevo farci ...
hwjp

38

Checkout git filter-branch .

La Examplessezione della pagina man mostra come estrarre una sottodirectory nel proprio progetto mantenendo tutta la sua cronologia e scartando la cronologia di altri file / directory (proprio quello che stai cercando).

Per riscrivere il repository in modo che appaia come se foodir/fosse la radice del progetto e scartare tutta la cronologia:

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

Così è possibile, ad esempio, trasformare una sottodirectory di libreria in un proprio repository.
Nota --che separa le filter-branchopzioni dalle opzioni di revisione e --allriscrive tutti i rami e le etichette.


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

@aceofspades: perché è uno svantaggio?
nought101

2
Per me il punto centrale dell'estrazione di commit da un repository git è che voglio conservare la cronologia.
aceofspades

13

Un modo per farlo è il contrario: rimuovere tutto tranne il file che si desidera conservare.

Fondamentalmente, fai una copia del repository, quindi usa git filter-branchper rimuovere tutto tranne il file / le cartelle che vuoi conservare.

Ad esempio, ho un progetto da cui desidero estrarre il file tvnamer.pyin un nuovo repository:

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

Questo utilizza git filter-branch --tree-filterper eseguire ogni commit, eseguire il comando e ripetere il contenuto delle directory risultanti. Questo è estremamente distruttivo (quindi dovresti farlo solo su una copia del tuo repository!) E può richiedere del tempo (circa 1 minuto su un repository con 300 commit e circa 20 file)

Il comando precedente esegue semplicemente il seguente script di shell su ogni revisione, che dovresti ovviamente modificare (per escludere la tua sottodirectory invece di tvnamer.py):

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

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

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

È necessario utilizzare l' -fargomento force run filter-branchagain con qualsiasi cosa in refs/original/(che fondamentalmente un backup)

Ovviamente questo non sarà mai perfetto, ad esempio se i tuoi messaggi di commit menzionano altri file, ma è quanto di più vicino consentito da una git current (per quanto ne so comunque).

Di nuovo, eseguilo sempre e solo su una copia del tuo repository! - ma in sintesi, per rimuovere tutti i file tranne "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 "$@"; else git commit-tree "$@"; fi'

4
git filter-branchha (al giorno d'oggi?) un'opzione incorporata per rimuovere i commit vuoti, vale a dire --prune-empty. Una guida meglio git filter-branchè nelle risposte a questa domanda: stackoverflow.com/questions/359424/...
Blaisorblade

4

Entrambe le risposte CoolAJ86 e apenwarr sono molto simili. Sono andato avanti e indietro tra i due cercando di capire i bit che mancavano da nessuno dei due. Di seguito è una combinazione di loro.

Prima naviga in Git Bash alla radice del repository git da dividere. Nel mio esempio eccolo qui~/Documents/OriginalRepo (master)

# move the folder at prefix to a new branch
git subtree split --prefix=SubFolderName/FolderToBeNewRepo --branch=to-be-new-repo

# create a new repository out of the newly made branch
mkdir ~/Documents/NewRepo
pushd ~/Documents/NewRepo
git init
git pull ~/Documents/OriginalRepo to-be-new-repo

# upload the new repository to a place that should be referenced for submodules
git remote add origin git@github.com:myUsername/newRepo.git
git push -u origin master
popd

# replace the folder with a submodule
git rm -rf ./SubFolderName/FolderToBeNewRepo
git submodule add git@github.com:myUsername/newRepo.git SubFolderName/FolderToBeNewRepo
git branch --delete --force to-be-new-repo

Di seguito è riportata una copia di quanto sopra con i nomi personalizzabili sostituiti e utilizzando invece https. La cartella principale è ora~/Documents/_Shawn/UnityProjects/SoProject (master)

# move the folder at prefix to a new branch
git subtree split --prefix=Assets/SoArchitecture --branch=so-package

# create a new repository out of the newly made branch
mkdir ~/Documents/_Shawn/UnityProjects/SoArchitecture
pushd ~/Documents/_Shawn/UnityProjects/SoArchitecture
git init
git pull ~/Documents/_Shawn/UnityProjects/SoProject so-package

# upload the new repository to a place that should be referenced for submodules
git remote add origin https://github.com/Feddas/SoArchitecture.git
git push -u origin master
popd

# replace the folder with a submodule
git rm -rf ./Assets/SoArchitecture
git submodule add https://github.com/Feddas/SoArchitecture.git
git branch --delete --force so-package

3

Se vuoi trasferire un sottoinsieme di file in un nuovo repository ma mantenere la cronologia, in pratica finirai con una cronologia completamente nuova. Il modo in cui funzionerebbe è fondamentalmente il seguente:

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

Dovrebbe essere piuttosto semplice automatizzare questo se non ti dispiace scrivere una sceneggiatura piccola ma pelosa. Semplice, sì, ma anche doloroso. Le persone hanno riscritto la storia in Git in passato, puoi fare una ricerca per quello.

In alternativa: clona il repository ed elimina la carta nel clone, elimina l'app nell'originale. Questo richiederebbe 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 sul disco rigido occupato da copie ridondanti della cronologia.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.