¿Cómo extraer un subdirectorio git y hacer un submódulo con él?

119

Comencé un proyecto hace algunos meses y guardé todo en un directorio principal. En mi directorio principal "Proyecto" hay varios subdirectorios que contienen diferentes cosas: Proyecto / papel contiene un documento escrito en LaTeX Proyecto / código fuente / RailsApp contiene mi aplicación rails.

"Proyecto" está GITified y ha habido muchas confirmaciones tanto en el directorio "papel" como en el directorio "RailsApp". Ahora, como me gustaría usar cruisecontrol.rb para mi "RailsApp", me pregunto si hay una manera de hacer un submódulo de "RailsApp" sin perder el historial.

Cœur
fuente
2
También una muy buena respuesta: stackoverflow.com/questions/359424/…
Rehno Lindeque
Posible duplicado del subdirectorio Separar (mover) en un repositorio de Git separado
Coronel Treinta y Dos

Respuestas:

122

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

Instalación

NOTA git-subtree ahora es parte de git(si instala contrib) a partir de 1.7.11, por lo que es posible que ya lo tenga instalado. Puede verificar ejecutando git subtree.


Para instalar git-subtree desde la fuente (para versiones anteriores de 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 si quieres las páginas man y todo

make doc
make install

Uso

Divida una 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 [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

Para obtener documentación detallada (página de manual), lea git-subtree.txt.

apenwarr
fuente
10
git subtree rocks!
Simon Woodside
3
¿Pero no es el objetivo de git-subtree evitar el uso de submódulos? Quiero decir, eres de hecho el autor del git-subtree (a menos que haya una colisión de apodos), pero parece que git-subtree ha cambiado, aunque el comando que muestra sigue siendo válido. ¿Estoy entendiendo esto bien?
Blaisorblade
17
git-subtree ahora es parte de git (si instala contrib) a partir del 1.7.11
Jeremy
8
Así git rm -rf ./fooelimina fooa partir HEAD, pero no lo hace de filtro my-project's historia completa. Entonces, git submodule add [email protected]:my-user/new-project.git foosolo hace fooun submódulo a partir de HEAD. En ese sentido, la secuencia de comandos filter-branches superior, ya que permite lograr "hacer como si subdir fuera un submódulo desde el principio"
Gregory Pakosz
gracias por esto: los documentos de git subtree son un poco desconcertantes, y esto es (para mí) lo más obviamente útil que quería hacer con él ...
hwjp
38

Verifique git filter-branch .

La Examplessección de la página de manual muestra cómo extraer un subdirectorio en su propio proyecto mientras se mantiene todo su historial y se descarta el historial de otros archivos / directorios (justo lo que está buscando).

Para reescribir el repositorio para que se vea como si foodir/hubiera sido la raíz del proyecto y descartar el resto del historial:

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

Por lo tanto, puede, por ejemplo, convertir un subdirectorio de biblioteca en un repositorio propio.
Tenga en cuenta el --que separa las filter-branchopciones de las opciones de revisión y el --allpara reescribir todas las ramas y etiquetas.

Pat Notz
fuente
1
Esto funcionó bien para mí. El único inconveniente que noté fue que el resultado fue una sola rama maestra con todas las confirmaciones.
aceofspades
@aceofspades: ¿por qué es eso un inconveniente?
naught101
2
Para mí, el objetivo de extraer confirmaciones de un repositorio de git es que quiero conservar el historial.
aceofspades
13

Una forma de hacerlo es a la inversa: elimine todo menos el archivo que desea conservar.

Básicamente, haga una copia del repositorio, luego use git filter-branchpara eliminar todo menos los archivos / carpetas que desea conservar.

Por ejemplo, tengo un proyecto del que deseo extraer el archivo tvnamer.pya un nuevo repositorio:

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

Eso suele git filter-branch --tree-filterpasar por cada confirmación, ejecutar el comando y volver a comprometer el contenido de los directorios resultantes. Esto es extremadamente destructivo (¡por lo que solo debe hacer esto en una copia de su repositorio!), Y puede tomar un tiempo (aproximadamente 1 minuto en un repositorio con 300 confirmaciones y aproximadamente 20 archivos)

El comando anterior solo ejecuta el siguiente script de shell en cada revisión, que tendría que modificar, por supuesto (para que excluya su subdirectorio 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 mensajes de confirmación, incluso si no están relacionados con el archivo 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'

Debe usar el -fargumento de fuerza para ejecutar filter-branchnuevamente con cualquier cosa en refs/original/(que básicamente es una copia de seguridad)

Por supuesto, esto nunca será perfecto, por ejemplo, si sus mensajes de confirmación mencionan otros archivos, pero es lo más cercano que permite una corriente de git (que yo sepa de todos modos).

Nuevamente, ¡solo ejecute esto en una copia de su repositorio! - pero en resumen, para eliminar todos los archivos 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'
dbr
fuente
4
git filter-branchtiene (¿hoy en día?) una opción incorporada para eliminar confirmaciones vacías, a saber --prune-empty. Una mejor guía git filter-branchestá en las respuestas a esta pregunta: stackoverflow.com/questions/359424/…
Blaisorblade
4

Las respuestas de CoolAJ86 y apenwarr son muy similares. Fui y retrocedí entre los dos tratando de entender los bits que faltaban en cualquiera de ellos. A continuación se muestra una combinación de ellos.

Primero navegue Git Bash a la raíz del repositorio de git que se dividirá. En mi ejemplo aquí es~/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 [email protected]:myUsername/newRepo.git
git push -u origin master
popd

# replace the folder with a submodule
git rm -rf ./SubFolderName/FolderToBeNewRepo
git submodule add [email protected]:myUsername/newRepo.git SubFolderName/FolderToBeNewRepo
git branch --delete --force to-be-new-repo

A continuación se muestra una copia de lo anterior con los nombres que se pueden personalizar reemplazados y usando https en su lugar. La carpeta raíz es ahora~/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
ShawnFeatherly
fuente
3

Si desea transferir algún subconjunto de archivos a un nuevo repositorio 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. Crea un nuevo repositorio.
  2. Para cada revisión de su antiguo repositorio, combine los cambios de su módulo en el nuevo repositorio. Esto creará una "copia" del historial de su proyecto existente.

Debería ser algo sencillo automatizar esto si no le importa escribir un guión pequeño pero complicado. Sencillo, 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 repositorio y elimine el papel en el clon, elimine la aplicación en el original. Esto tomaría un minuto, está garantizado que funcionará y puede volver a cosas más importantes que tratar de purificar su historial de git. Y no se preocupe por el espacio en el disco duro que ocupan las copias redundantes del historial.

Dietrich Epp
fuente