¿Qué es un submódulo?
Con los submódulos puedes incorporar un repositorio de Git dentro de otro. Es una forma de agregar a tu repositorio lo que parece una carpeta, pero que en realidad es una conexión con otro repositorio de Git. Los submódulos son útiles cuando tienes varios repositorios relacionados entre sí, y quieres usar uno en otro.
Un ejemplo de uso: un submódulo podría ser el repositorio de una biblioteca que quieras utilizar en un producto de software. Si en el repositorio de tu producto agregas ese submódulo, lo que harás es crear un directorio dentro de tu proyecto, que contendrá una conexión con ese otro repositorio importado.
Internamente, no se crea una copia del mismo. Git lo único que almacena es la URL del repositorio que agregas como submódulo. Sin embargo, cuando clonas el repositorio, o si lo haces manualmente, Git hará una expansión de esa referencia para transformarlo en el resultado de hacer git clone de ese otro repositorio también.
Como un submódulo de Git no es más que otro repositorio, los submódulos están fijados a un commit concreto. Y esta es la auténtica clave de los submódulos, porque te permiten apuntar precisamente a un commit concreto del repositorio que estés importando. El repositorio original podría recibir nuevos commits, pero como tu submódulo apunta a un commit exacto, no te afecta. Siempre puedes más adelante cambiar precisamente el commit al que apuntas, por ejemplo para ponerlo al día y así apuntar a una versión más moderna de tu submódulo.
Los submódulos es a su vez una de las partes menos apreciadas de Git debido a la complejidad que a veces tiene y a cómo en ocasiones se puede llegar a romper el repositorio y provocar situaciones difíciles de reparar. En general, a pesar de su utilidad, mucha gente coincide en que no deberías emplear los submódulos a no ser que sea exactamente lo que resuelve tu problema, pero que si, por ejemplo, tienes otra forma de traer código externo (como puede ser usar un gestor de dependencias, como npm o cargo), lo utilices en su lugar al ser más flexible.
¿Cómo interactuar con submódulos en Git?
Con el comando git submodule puedes hacer esto. Este subcomando tiene a su vez más operaciones internas para cada una de las cosas que puedes hacer con un submódulo. En esta lección y la siguiente te voy a mostrar cada una de estas operaciones para que te conviertas en una persona experta en trabajar con submódulos.
¿Cómo agregar un submódulo a un repositorio?
El comando es:
git submodule add [URL del repositorio] [lugar donde clonarlo]
Vamos a poner un ejemplo. Imagina que para tu página web quieres usar Bootstrap. No es el mejor ejemplo, porque podrías clonarlo con NPM (y de hecho, deberías). Sin embargo, una forma de traértelo para hacer tu aplicación web podría ser importarlo mediante módulos.
Vamos a suponer que ya estamos en el directorio de nuestra aplicación web, es decir, que el repositorio ahora mismo tiene el siguiente aspecto:
|- css
| |- main.css
|- js
| |- main.js
|- index.html
Si ahora quiero traerme Bootstrap a mi repositorio y dejarlo en el directorio external/bootstrap, puedo hacerlo con el siguiente comando:
git submodule add https://github.com/twbs/bootstrap external/bootstrap
Eso deja el repositorio con el siguiente aspecto:
|- css
| |- main.css
|- external
| |- bootstrap
| |- ...
|- js
| |- main.js
|- index.html
El contenido de la carpeta external/bootstrap será tal cual un clonado del repositorio que he importado, incluso estaremos usando el sistema de ramas de ese repositorio en vez del nuestro.
Sin embargo, importar un repositorio usando submódulos no tiene el mismo efecto que tal cual descargar Bootstrap manualmente y copiar los archivos al directorio de trabajo. Si hubiese hecho eso, Git ahora me ofrecería agregar los cientos de archivos que forman parte del código de Bootstrap porque los vería como archivos nuevos, igual que cualquier otro archivo que haya creado para mi proyecto.
Pero como en su lugar he agregado un submódulo, Git guarda una conexión que le permite saber que este repositorio ahora tiene un submódulo llamado external/bootstrap, que debe apuntar a https://github.com/twbs/bootstrap. Y por lo tanto Git ahora lo único que deberá guardar será ese metadato, en vez de todo el repositorio.
Esto es algo que podemos ver cuando hacemos git status en la raíz de nuestro repositorio principal. Veremos que aparecen dos ficheros nuevos:
new file: .gitmodules
new file: external/bootstrap
El primero de estos archivos es el .gitmodules. Es precisamente ese archivo que va a usar Git para identificar qué repositorios estamos importando como submódulos en el proyecto, para que al clonar en el futuro nuestro repositorio, si encuentra este archivo los pueda clonar también y así asegurarse de que el proyecto que estamos clonando tiene recursivamente todas las dependencias resueltas.
Por ejemplo, el contenido del archivo .gitmodules ahora podría ser el siguiente:
[submodule "external/bootstrap"]
path = external/bootstrap
url = https://github.com/twbs/bootstrap
En cuanto al otro archivo, external/bootstrap, es la otra parte de la referencia. Si examinas con git diff el contenido de este archivo, todo lo que verás es algo como Subproject commit aaaabbbcccdddeeefff, donde la tercera palabra es un código hexadecimal muy largo. Este código hexadecimal se va a corresponder precisamente con el ID del commit de ese submódulo al que quieras que apunte.
En definitiva, ambos archivos en conjunto permiten reconstruir luego el contenido auténtico del directorio al clonar el repositorio, porque el archivo .gitmodules le dice que debe clonarse el repositorio de Bootstrap en la ruta especificada, y a su vez el archivo indica a qué commit hay que hacer git switch luego de desempaquetarlo.
¿Cómo clono un repositorio con submódulos?
Cuando hagas git clone de un repositorio que tenga submódulos, por defecto no se van a inicializar los submódulos. Esto quiere decir que los directorios donde debería haber un submódulo de Git van a estar vacíos.
Para descargar los submódulos, el primer paso sería inicializarlos. Para ello, usaremos el siguiente comando:
git submodule init
Esto lo que hace es escanear el contenido del .gitmodules y así identificar qué módulos son los que hay que traerse. Después de eso, usamos el siguiente comando para hacer el pull de todos los submódulos.
git submodule update
Con ambos comandos, tendrás tu repositorio ya listo con todos los módulos, y si vuelves a visitar el contenido de la carpeta verás que ya no está vacía, sino que ahora tiene el contenido del repositorio.