¿Cómo extraer un subdirectory git y crear un submodule?

Comencé un proyecto hace algunos meses y almacené todo dentro de un directory principal. En mi directory principal "Proyecto" hay varios subdirectorys que contienen cosas diferentes: Proyecto / papel contiene un documento escrito en LaTeX Proyecto / código fuente / RailsApp contiene mi aplicación de Rails.

"Proyecto" está GITificado y ha habido muchas confirmaciones en el directory "papel" y "RailsApp". Ahora, como me gustaría usar cruisecontrol.rb para mi "RailsApp", me pregunto si hay una forma de hacer un submodule de "RailsApp" sin perder el historial.

¿Alguna sugerencia?

Hoy en día hay una manera mucho más fácil de hacerlo que manualmente usando git filter-branch: git subtree

Instalación

git clone https://github.com/apenwarr/git-subtree.git cd git-subtree sudo rsync -a ./git-subtree.sh /usr/local/bin/git-subtree 

O si quieres las páginas man y todo

 make doc make install 

Uso

Divida un trozo más grande en trozos más pequeños:

 # 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 

Para get documentation detallada (página man), lea git-subtree.txt .

Checkout git filter-branch .

La sección de Examples de la página man muestra cómo extraer un subdirectory en su propio proyecto, conservando todo su historial y descartando el historial de otros files / directorys (justo lo que está buscando).

Para reescribir el repository para ver si foodir/ había sido su raíz de proyecto, y descartar el rest de la historia:

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

Por lo tanto, puede, por ejemplo, convertir un subdirectory de biblioteca en un repository propio.
Tenga en count que -- separa filter-branch opciones de la filter-branch opciones de revisión, y --all para reescribir todas las twigs y tags.

Una forma de hacerlo es la inversa: elimine todo less el file que desea conservar.

Básicamente, haga una copy del repository, luego use git filter-branch para eliminar todo less el file / carpetas que desea conservar.

Por ejemplo, tengo un proyecto desde el cual deseo extraer el file tvnamer.py a un nuevo repository:

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

Utiliza git filter-branch --tree-filter para pasar por cada confirmación, ejecutar el command y volver a enviar el contenido de los directorys resultantes. Esto es extremadamente destructivo (¡así que solo debe hacer esto en una copy de su repository!), Y puede tomar un time (aproximadamente 1 minuto en un repository con 300 confirmaciones y aproximadamente 20 files)

El command anterior solo ejecuta el siguiente script de shell en cada revisión, que deberá modificar por supuesto (para que excluya su subdirectory en lugar de tvnamer.py ):

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

El mayor problema obvio es que deja todos los posts de confirmación, incluso si no están relacionados con el file restante. El script git-remove-empty-commits , corrige esto ..

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

Necesita usar el argumento -f force ejecutar filter-branch nuevamente con cualquier cosa en refs/original/ (que básicamente es una copy de security)

Por supuesto, esto nunca será perfecto, por ejemplo, si sus posts de confirmación mencionan otros files, pero es lo más aproximado que permite una stream de git (hasta donde yo sé).

Nuevamente, ¡solo ejecute esto en una copy de su repository! – pero en resumen, para eliminar todos los files excepto "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' 

Si desea transferir un subset de files a un nuevo repository pero conservar el historial, básicamente terminará con un historial completamente nuevo. La forma en que esto funcionaría es básicamente la siguiente:

  1. Crear nuevo repository.
  2. Para cada revisión de su antiguo repository, combine los cambios de su module en el nuevo repository. Esto creará una "copy" de su historial de proyectos existente.

Sería algo sencillo automatizar esto si no te importa escribir un script pequeño pero peludo. Directo, sí, pero también doloroso. La gente ha reescrito la historia en Git en el pasado, puedes hacer una búsqueda para eso.

Alternativamente: clone el repository, y elimine el papel en el clon, elimine la aplicación en el original. Esto tomaría un minuto, está garantizado que funciona, y puedes volver a cosas más importantes que tratar de purificar tu historial de git. Y no se preocupe por el espacio en el disco duro ocupado por las copys networkingundantes del historial.